summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:54 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-11-30 16:29:54 +0000
commit92d7ce512fa88d6e3ffb4c4da4aebeb7e529035a (patch)
tree0e99a3e46337b152a92f8a565f0c10591e85fb91
parent18746bafae9ac426a512f15c780b5911a8c7eaed (diff)
parent83ab35a5d0568717451cfd328f60fc15995a9f7d (diff)
Merge remote-tracking branch 'qt4/testing'
-rw-r--r--qt4/.gitignore10
-rw-r--r--qt4/AUTHORS3
-rw-r--r--qt4/CMakeLists.txt229
-rw-r--r--qt4/COPYING510
-rw-r--r--qt4/ChangeLog2
-rw-r--r--qt4/HACKING115
-rw-r--r--qt4/NEWS1478
-rw-r--r--qt4/README96
-rw-r--r--qt4/TelepathyQt4/AbstractClient13
-rw-r--r--qt4/TelepathyQt4/AbstractClientApprover13
-rw-r--r--qt4/TelepathyQt4/AbstractClientHandler13
-rw-r--r--qt4/TelepathyQt4/AbstractClientObserver13
-rw-r--r--qt4/TelepathyQt4/AbstractInterface13
-rw-r--r--qt4/TelepathyQt4/Account13
-rw-r--r--qt4/TelepathyQt4/AccountCapabilityFilter13
-rw-r--r--qt4/TelepathyQt4/AccountFactory13
-rw-r--r--qt4/TelepathyQt4/AccountFilter13
-rw-r--r--qt4/TelepathyQt4/AccountInterface13
-rw-r--r--qt4/TelepathyQt4/AccountInterfaceAddressingInterface13
-rw-r--r--qt4/TelepathyQt4/AccountInterfaceAvatarInterface13
-rw-r--r--qt4/TelepathyQt4/AccountManager13
-rw-r--r--qt4/TelepathyQt4/AccountManagerInterface13
-rw-r--r--qt4/TelepathyQt4/AccountPropertyFilter13
-rw-r--r--qt4/TelepathyQt4/AccountSet13
-rw-r--r--qt4/TelepathyQt4/AndFilter13
-rw-r--r--qt4/TelepathyQt4/AuthenticationTLSCertificateInterface13
-rw-r--r--qt4/TelepathyQt4/AvatarData13
-rw-r--r--qt4/TelepathyQt4/AvatarSpec13
-rw-r--r--qt4/TelepathyQt4/CMakeLists.txt694
-rw-r--r--qt4/TelepathyQt4/CapabilitiesBase13
-rw-r--r--qt4/TelepathyQt4/Channel13
-rw-r--r--qt4/TelepathyQt4/ChannelClassFeatures13
-rw-r--r--qt4/TelepathyQt4/ChannelClassSpec13
-rw-r--r--qt4/TelepathyQt4/ChannelClassSpecList13
-rw-r--r--qt4/TelepathyQt4/ChannelDispatchOperation13
-rw-r--r--qt4/TelepathyQt4/ChannelDispatchOperationInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelDispatcher13
-rw-r--r--qt4/TelepathyQt4/ChannelDispatcherInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelFactory13
-rw-r--r--qt4/TelepathyQt4/ChannelInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceAnonymityInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceCallStateInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceChatStateInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceConferenceInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceDTMFInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceGroupInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceHoldInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceMediaSignallingInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceMessagesInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfacePasswordInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceSASLAuthenticationInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceSecurableInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceServicePointInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelInterfaceTubeInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelRequest13
-rw-r--r--qt4/TelepathyQt4/ChannelRequestHints13
-rw-r--r--qt4/TelepathyQt4/ChannelRequestInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeContactListInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeContactSearchInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeFileTransferInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeRoomListInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeServerAuthenticationInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeServerTLSConnectionInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeStreamTubeInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeStreamedMediaInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeTextInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeTubeInterface13
-rw-r--r--qt4/TelepathyQt4/ChannelTypeTubesInterface13
-rw-r--r--qt4/TelepathyQt4/Client13
-rw-r--r--qt4/TelepathyQt4/ClientApproverInterface13
-rw-r--r--qt4/TelepathyQt4/ClientHandlerInterface13
-rw-r--r--qt4/TelepathyQt4/ClientInterface13
-rw-r--r--qt4/TelepathyQt4/ClientInterfaceRequestsInterface13
-rw-r--r--qt4/TelepathyQt4/ClientObserverInterface13
-rw-r--r--qt4/TelepathyQt4/ClientRegistrar13
-rw-r--r--qt4/TelepathyQt4/Connection13
-rw-r--r--qt4/TelepathyQt4/ConnectionCapabilities13
-rw-r--r--qt4/TelepathyQt4/ConnectionFactory13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceAliasingInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceAnonymityInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceAvatarsInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceBalanceInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceCapabilitiesInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceCellularInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceClientTypes17
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceClientTypesInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactBlockingInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactCapabilitiesInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactGroups17
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactGroupsInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactInfoInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactList17
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactListInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceContactsInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceLocationInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceMailNotificationInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfacePowerSaving17
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfacePowerSavingInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfacePresenceInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceRequestsInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceServicePointInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionInterfaceSimplePresenceInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionLowlevel13
-rw-r--r--qt4/TelepathyQt4/ConnectionManager13
-rw-r--r--qt4/TelepathyQt4/ConnectionManagerInterface13
-rw-r--r--qt4/TelepathyQt4/ConnectionManagerLowlevel13
-rw-r--r--qt4/TelepathyQt4/Constants13
-rw-r--r--qt4/TelepathyQt4/Contact13
-rw-r--r--qt4/TelepathyQt4/ContactCapabilities13
-rw-r--r--qt4/TelepathyQt4/ContactFactory13
-rw-r--r--qt4/TelepathyQt4/ContactManager13
-rw-r--r--qt4/TelepathyQt4/ContactMessenger13
-rw-r--r--qt4/TelepathyQt4/ContactSearchChannel13
-rw-r--r--qt4/TelepathyQt4/DBus13
-rw-r--r--qt4/TelepathyQt4/DBusDaemonInterface13
-rw-r--r--qt4/TelepathyQt4/DBusProxy13
-rw-r--r--qt4/TelepathyQt4/DBusProxyFactory13
-rw-r--r--qt4/TelepathyQt4/Debug13
-rw-r--r--qt4/TelepathyQt4/Farsight/CMakeLists.txt52
-rw-r--r--qt4/TelepathyQt4/Farsight/Channel13
-rw-r--r--qt4/TelepathyQt4/Farsight/channel.cpp87
-rw-r--r--qt4/TelepathyQt4/Farsight/channel.h43
-rw-r--r--qt4/TelepathyQt4/Farsight/global.h46
-rw-r--r--qt4/TelepathyQt4/Feature13
-rw-r--r--qt4/TelepathyQt4/Features13
-rw-r--r--qt4/TelepathyQt4/FileTransferChannel13
-rw-r--r--qt4/TelepathyQt4/FileTransferChannelCreationProperties13
-rw-r--r--qt4/TelepathyQt4/Filter13
-rw-r--r--qt4/TelepathyQt4/FixedFeatureFactory13
-rw-r--r--qt4/TelepathyQt4/GenericCapabilityFilter13
-rw-r--r--qt4/TelepathyQt4/GenericPropertyFilter13
-rw-r--r--qt4/TelepathyQt4/Global13
-rw-r--r--qt4/TelepathyQt4/HandledChannelNotifier13
-rw-r--r--qt4/TelepathyQt4/IncomingFileTransferChannel13
-rw-r--r--qt4/TelepathyQt4/IncomingStreamTubeChannel13
-rw-r--r--qt4/TelepathyQt4/IntrospectableInterface13
-rw-r--r--qt4/TelepathyQt4/KeyFile13
-rw-r--r--qt4/TelepathyQt4/LocationInfo13
-rw-r--r--qt4/TelepathyQt4/ManagerFile13
-rw-r--r--qt4/TelepathyQt4/MediaSessionHandler13
-rw-r--r--qt4/TelepathyQt4/MediaSessionHandlerInterface13
-rw-r--r--qt4/TelepathyQt4/MediaStreamHandler13
-rw-r--r--qt4/TelepathyQt4/MediaStreamHandlerInterface13
-rw-r--r--qt4/TelepathyQt4/Message13
-rw-r--r--qt4/TelepathyQt4/MessageContentPart13
-rw-r--r--qt4/TelepathyQt4/MessageContentPartList13
-rw-r--r--qt4/TelepathyQt4/MethodInvocationContext13
-rw-r--r--qt4/TelepathyQt4/NotFilter13
-rw-r--r--qt4/TelepathyQt4/Object13
-rw-r--r--qt4/TelepathyQt4/OptionalInterfaceFactory13
-rw-r--r--qt4/TelepathyQt4/OrFilter13
-rw-r--r--qt4/TelepathyQt4/OutgoingFileTransferChannel13
-rw-r--r--qt4/TelepathyQt4/OutgoingStreamTubeChannel13
-rw-r--r--qt4/TelepathyQt4/PeerInterface13
-rw-r--r--qt4/TelepathyQt4/PendingAccount13
-rw-r--r--qt4/TelepathyQt4/PendingChannel13
-rw-r--r--qt4/TelepathyQt4/PendingChannelRequest13
-rw-r--r--qt4/TelepathyQt4/PendingComposite13
-rw-r--r--qt4/TelepathyQt4/PendingConnection13
-rw-r--r--qt4/TelepathyQt4/PendingContactAttributes13
-rw-r--r--qt4/TelepathyQt4/PendingContactInfo13
-rw-r--r--qt4/TelepathyQt4/PendingContacts13
-rw-r--r--qt4/TelepathyQt4/PendingFailure13
-rw-r--r--qt4/TelepathyQt4/PendingHandles13
-rw-r--r--qt4/TelepathyQt4/PendingOperation13
-rw-r--r--qt4/TelepathyQt4/PendingReady13
-rw-r--r--qt4/TelepathyQt4/PendingSendMessage13
-rw-r--r--qt4/TelepathyQt4/PendingStreamTubeConnection13
-rw-r--r--qt4/TelepathyQt4/PendingStreamedMediaStreams13
-rw-r--r--qt4/TelepathyQt4/PendingStringList13
-rw-r--r--qt4/TelepathyQt4/PendingSuccess13
-rw-r--r--qt4/TelepathyQt4/PendingVariant13
-rw-r--r--qt4/TelepathyQt4/PendingVariantMap13
-rw-r--r--qt4/TelepathyQt4/PendingVoid13
-rw-r--r--qt4/TelepathyQt4/Presence13
-rw-r--r--qt4/TelepathyQt4/PresenceSpec13
-rw-r--r--qt4/TelepathyQt4/PresenceSpecList13
-rw-r--r--qt4/TelepathyQt4/Profile13
-rw-r--r--qt4/TelepathyQt4/ProfileManager13
-rw-r--r--qt4/TelepathyQt4/Properties13
-rw-r--r--qt4/TelepathyQt4/PropertiesInterface13
-rw-r--r--qt4/TelepathyQt4/PropertiesInterfaceInterface13
-rw-r--r--qt4/TelepathyQt4/ProtocolInfo13
-rw-r--r--qt4/TelepathyQt4/ProtocolParameter13
-rw-r--r--qt4/TelepathyQt4/ReadinessHelper13
-rw-r--r--qt4/TelepathyQt4/ReadyObject13
-rw-r--r--qt4/TelepathyQt4/ReceivedMessage13
-rw-r--r--qt4/TelepathyQt4/RefCounted13
-rw-r--r--qt4/TelepathyQt4/ReferencedHandles12
-rw-r--r--qt4/TelepathyQt4/ReferencedHandlesIterator6
-rw-r--r--qt4/TelepathyQt4/RequestableChannelClassSpec13
-rw-r--r--qt4/TelepathyQt4/RequestableChannelClassSpecList13
-rw-r--r--qt4/TelepathyQt4/RoomListChannel13
-rw-r--r--qt4/TelepathyQt4/SharedPtr13
-rw-r--r--qt4/TelepathyQt4/SimpleCallObserver13
-rw-r--r--qt4/TelepathyQt4/SimpleObserver13
-rw-r--r--qt4/TelepathyQt4/SimpleTextObserver13
-rw-r--r--qt4/TelepathyQt4/StatefulDBusProxy13
-rw-r--r--qt4/TelepathyQt4/StatelessDBusProxy13
-rw-r--r--qt4/TelepathyQt4/StreamTubeChannel13
-rw-r--r--qt4/TelepathyQt4/StreamTubeClient13
-rw-r--r--qt4/TelepathyQt4/StreamTubeServer13
-rw-r--r--qt4/TelepathyQt4/StreamedMediaChannel13
-rw-r--r--qt4/TelepathyQt4/StreamedMediaStream13
-rw-r--r--qt4/TelepathyQt4/TelepathyQt4-uninstalled.pc.in11
-rw-r--r--qt4/TelepathyQt4/TelepathyQt4.pc.in11
-rw-r--r--qt4/TelepathyQt4/TelepathyQt4Farsight-uninstalled.pc.in11
-rw-r--r--qt4/TelepathyQt4/TelepathyQt4Farsight.pc.in11
-rw-r--r--qt4/TelepathyQt4/TextChannel13
-rw-r--r--qt4/TelepathyQt4/TubeChannel13
-rw-r--r--qt4/TelepathyQt4/Types13
-rw-r--r--qt4/TelepathyQt4/Utils13
-rw-r--r--qt4/TelepathyQt4/abstract-client.cpp988
-rw-r--r--qt4/TelepathyQt4/abstract-client.h323
-rw-r--r--qt4/TelepathyQt4/abstract-interface.cpp136
-rw-r--r--qt4/TelepathyQt4/abstract-interface.h76
-rw-r--r--qt4/TelepathyQt4/account-capability-filter.dox30
-rw-r--r--qt4/TelepathyQt4/account-capability-filter.h39
-rw-r--r--qt4/TelepathyQt4/account-factory.cpp157
-rw-r--r--qt4/TelepathyQt4/account-factory.h79
-rw-r--r--qt4/TelepathyQt4/account-filter.h39
-rw-r--r--qt4/TelepathyQt4/account-manager.cpp1115
-rw-r--r--qt4/TelepathyQt4/account-manager.h152
-rw-r--r--qt4/TelepathyQt4/account-manager.xml9
-rw-r--r--qt4/TelepathyQt4/account-property-filter.cpp94
-rw-r--r--qt4/TelepathyQt4/account-property-filter.h59
-rw-r--r--qt4/TelepathyQt4/account-set-internal.h82
-rw-r--r--qt4/TelepathyQt4/account-set.cpp418
-rw-r--r--qt4/TelepathyQt4/account-set.h79
-rw-r--r--qt4/TelepathyQt4/account.cpp4472
-rw-r--r--qt4/TelepathyQt4/account.h598
-rw-r--r--qt4/TelepathyQt4/account.xml13
-rw-r--r--qt4/TelepathyQt4/and-filter.dox33
-rw-r--r--qt4/TelepathyQt4/and-filter.h83
-rw-r--r--qt4/TelepathyQt4/async-model.dox56
-rw-r--r--qt4/TelepathyQt4/avatar.cpp172
-rw-r--r--qt4/TelepathyQt4/avatar.h86
-rw-r--r--qt4/TelepathyQt4/capabilities-base.cpp348
-rw-r--r--qt4/TelepathyQt4/capabilities-base.h85
-rw-r--r--qt4/TelepathyQt4/channel-class-features.h45
-rw-r--r--qt4/TelepathyQt4/channel-class-spec.cpp555
-rw-r--r--qt4/TelepathyQt4/channel-class-spec.h277
-rw-r--r--qt4/TelepathyQt4/channel-dispatch-operation-internal.h51
-rw-r--r--qt4/TelepathyQt4/channel-dispatch-operation.cpp623
-rw-r--r--qt4/TelepathyQt4/channel-dispatch-operation.h114
-rw-r--r--qt4/TelepathyQt4/channel-dispatch-operation.xml9
-rw-r--r--qt4/TelepathyQt4/channel-dispatcher.cpp26
-rw-r--r--qt4/TelepathyQt4/channel-dispatcher.h32
-rw-r--r--qt4/TelepathyQt4/channel-dispatcher.xml9
-rw-r--r--qt4/TelepathyQt4/channel-factory.cpp529
-rw-r--r--qt4/TelepathyQt4/channel-factory.h305
-rw-r--r--qt4/TelepathyQt4/channel-internal.h50
-rw-r--r--qt4/TelepathyQt4/channel-request.cpp803
-rw-r--r--qt4/TelepathyQt4/channel-request.h154
-rw-r--r--qt4/TelepathyQt4/channel-request.xml9
-rw-r--r--qt4/TelepathyQt4/channel.cpp3596
-rw-r--r--qt4/TelepathyQt4/channel.h256
-rw-r--r--qt4/TelepathyQt4/channel.xml37
-rw-r--r--qt4/TelepathyQt4/client-registrar-internal.h365
-rw-r--r--qt4/TelepathyQt4/client-registrar.cpp1038
-rw-r--r--qt4/TelepathyQt4/client-registrar.h97
-rw-r--r--qt4/TelepathyQt4/client.cpp26
-rw-r--r--qt4/TelepathyQt4/client.h32
-rw-r--r--qt4/TelepathyQt4/client.xml14
-rw-r--r--qt4/TelepathyQt4/connection-capabilities.cpp303
-rw-r--r--qt4/TelepathyQt4/connection-capabilities.h77
-rw-r--r--qt4/TelepathyQt4/connection-factory.cpp150
-rw-r--r--qt4/TelepathyQt4/connection-factory.h78
-rw-r--r--qt4/TelepathyQt4/connection-internal.h61
-rw-r--r--qt4/TelepathyQt4/connection-lowlevel.h96
-rw-r--r--qt4/TelepathyQt4/connection-manager-internal.h159
-rw-r--r--qt4/TelepathyQt4/connection-manager-lowlevel.h64
-rw-r--r--qt4/TelepathyQt4/connection-manager.cpp1078
-rw-r--r--qt4/TelepathyQt4/connection-manager.h125
-rw-r--r--qt4/TelepathyQt4/connection-manager.xml12
-rw-r--r--qt4/TelepathyQt4/connection.cpp2578
-rw-r--r--qt4/TelepathyQt4/connection.h249
-rw-r--r--qt4/TelepathyQt4/connection.xml30
-rw-r--r--qt4/TelepathyQt4/constants.h285
-rw-r--r--qt4/TelepathyQt4/contact-capabilities.cpp127
-rw-r--r--qt4/TelepathyQt4/contact-capabilities.h66
-rw-r--r--qt4/TelepathyQt4/contact-factory.cpp146
-rw-r--r--qt4/TelepathyQt4/contact-factory.h76
-rw-r--r--qt4/TelepathyQt4/contact-manager-internal.h389
-rw-r--r--qt4/TelepathyQt4/contact-manager-roster.cpp2217
-rw-r--r--qt4/TelepathyQt4/contact-manager.cpp1592
-rw-r--r--qt4/TelepathyQt4/contact-manager.h201
-rw-r--r--qt4/TelepathyQt4/contact-messenger.cpp257
-rw-r--r--qt4/TelepathyQt4/contact-messenger.h78
-rw-r--r--qt4/TelepathyQt4/contact-search-channel-internal.h52
-rw-r--r--qt4/TelepathyQt4/contact-search-channel.cpp676
-rw-r--r--qt4/TelepathyQt4/contact-search-channel.h114
-rw-r--r--qt4/TelepathyQt4/contact.cpp1352
-rw-r--r--qt4/TelepathyQt4/contact.h246
-rw-r--r--qt4/TelepathyQt4/dbus-daemon.xml80
-rw-r--r--qt4/TelepathyQt4/dbus-introspectable.xml16
-rw-r--r--qt4/TelepathyQt4/dbus-peer.xml19
-rw-r--r--qt4/TelepathyQt4/dbus-properties.xml29
-rw-r--r--qt4/TelepathyQt4/dbus-proxy-factory-internal.h58
-rw-r--r--qt4/TelepathyQt4/dbus-proxy-factory.cpp295
-rw-r--r--qt4/TelepathyQt4/dbus-proxy-factory.h85
-rw-r--r--qt4/TelepathyQt4/dbus-proxy.cpp393
-rw-r--r--qt4/TelepathyQt4/dbus-proxy.h122
-rw-r--r--qt4/TelepathyQt4/dbus.cpp26
-rw-r--r--qt4/TelepathyQt4/dbus.h54
-rw-r--r--qt4/TelepathyQt4/dbus.xml12
-rw-r--r--qt4/TelepathyQt4/debug-internal.h170
-rw-r--r--qt4/TelepathyQt4/debug.cpp187
-rw-r--r--qt4/TelepathyQt4/debug.h46
-rw-r--r--qt4/TelepathyQt4/examples.dox154
-rw-r--r--qt4/TelepathyQt4/fake-handler-manager-internal.cpp165
-rw-r--r--qt4/TelepathyQt4/fake-handler-manager-internal.h90
-rw-r--r--qt4/TelepathyQt4/feature.cpp89
-rw-r--r--qt4/TelepathyQt4/feature.h94
-rw-r--r--qt4/TelepathyQt4/file-transfer-channel-creation-properties.cpp433
-rw-r--r--qt4/TelepathyQt4/file-transfer-channel-creation-properties.h96
-rw-r--r--qt4/TelepathyQt4/file-transfer-channel.cpp703
-rw-r--r--qt4/TelepathyQt4/file-transfer-channel.h108
-rw-r--r--qt4/TelepathyQt4/filter.dox30
-rw-r--r--qt4/TelepathyQt4/filter.h63
-rw-r--r--qt4/TelepathyQt4/fixed-feature-factory.cpp116
-rw-r--r--qt4/TelepathyQt4/fixed-feature-factory.h69
-rw-r--r--qt4/TelepathyQt4/future-channel-dispatcher.xml20
-rw-r--r--qt4/TelepathyQt4/future-channel.xml13
-rw-r--r--qt4/TelepathyQt4/future-interfaces.xml14
-rw-r--r--qt4/TelepathyQt4/future-internal.h36
-rw-r--r--qt4/TelepathyQt4/future-misc.xml7
-rw-r--r--qt4/TelepathyQt4/future.cpp32
-rw-r--r--qt4/TelepathyQt4/generic-capability-filter.dox36
-rw-r--r--qt4/TelepathyQt4/generic-capability-filter.h114
-rw-r--r--qt4/TelepathyQt4/generic-property-filter.dox33
-rw-r--r--qt4/TelepathyQt4/generic-property-filter.h77
-rw-r--r--qt4/TelepathyQt4/global.h103
-rw-r--r--qt4/TelepathyQt4/groups.dox115
-rw-r--r--qt4/TelepathyQt4/handled-channel-notifier.cpp103
-rw-r--r--qt4/TelepathyQt4/handled-channel-notifier.h75
-rw-r--r--qt4/TelepathyQt4/incoming-file-transfer-channel.cpp390
-rw-r--r--qt4/TelepathyQt4/incoming-file-transfer-channel.h81
-rw-r--r--qt4/TelepathyQt4/incoming-stream-tube-channel.cpp435
-rw-r--r--qt4/TelepathyQt4/incoming-stream-tube-channel.h80
-rw-r--r--qt4/TelepathyQt4/key-file.cpp582
-rw-r--r--qt4/TelepathyQt4/key-file.h91
-rw-r--r--qt4/TelepathyQt4/location-info.cpp221
-rw-r--r--qt4/TelepathyQt4/location-info.h95
-rw-r--r--qt4/TelepathyQt4/main.dox133
-rw-r--r--qt4/TelepathyQt4/manager-file.cpp618
-rw-r--r--qt4/TelepathyQt4/manager-file.h78
-rw-r--r--qt4/TelepathyQt4/media-session-handler.cpp26
-rw-r--r--qt4/TelepathyQt4/media-session-handler.h50
-rw-r--r--qt4/TelepathyQt4/media-session-handler.xml9
-rw-r--r--qt4/TelepathyQt4/media-stream-handler.cpp26
-rw-r--r--qt4/TelepathyQt4/media-stream-handler.h50
-rw-r--r--qt4/TelepathyQt4/media-stream-handler.xml9
-rw-r--r--qt4/TelepathyQt4/message-content-part.cpp96
-rw-r--r--qt4/TelepathyQt4/message-content-part.h96
-rw-r--r--qt4/TelepathyQt4/message.cpp911
-rw-r--r--qt4/TelepathyQt4/message.h173
-rw-r--r--qt4/TelepathyQt4/method-invocation-context.dox41
-rw-r--r--qt4/TelepathyQt4/method-invocation-context.h192
-rw-r--r--qt4/TelepathyQt4/not-filter.dox32
-rw-r--r--qt4/TelepathyQt4/not-filter.h73
-rw-r--r--qt4/TelepathyQt4/object.cpp65
-rw-r--r--qt4/TelepathyQt4/object.h63
-rw-r--r--qt4/TelepathyQt4/optional-interface-factory.cpp178
-rw-r--r--qt4/TelepathyQt4/optional-interface-factory.h140
-rw-r--r--qt4/TelepathyQt4/or-filter.dox33
-rw-r--r--qt4/TelepathyQt4/or-filter.h83
-rw-r--r--qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp371
-rw-r--r--qt4/TelepathyQt4/outgoing-file-transfer-channel.h78
-rw-r--r--qt4/TelepathyQt4/outgoing-stream-tube-channel-internal.h122
-rw-r--r--qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp821
-rw-r--r--qt4/TelepathyQt4/outgoing-stream-tube-channel.h89
-rw-r--r--qt4/TelepathyQt4/pending-account.cpp184
-rw-r--r--qt4/TelepathyQt4/pending-account.h75
-rw-r--r--qt4/TelepathyQt4/pending-channel-request-internal.h73
-rw-r--r--qt4/TelepathyQt4/pending-channel-request.cpp276
-rw-r--r--qt4/TelepathyQt4/pending-channel-request.h84
-rw-r--r--qt4/TelepathyQt4/pending-channel.cpp555
-rw-r--r--qt4/TelepathyQt4/pending-channel.h103
-rw-r--r--qt4/TelepathyQt4/pending-connection.cpp171
-rw-r--r--qt4/TelepathyQt4/pending-connection.h73
-rw-r--r--qt4/TelepathyQt4/pending-contact-attributes.cpp219
-rw-r--r--qt4/TelepathyQt4/pending-contact-attributes.h77
-rw-r--r--qt4/TelepathyQt4/pending-contact-info.cpp128
-rw-r--r--qt4/TelepathyQt4/pending-contact-info.h66
-rw-r--r--qt4/TelepathyQt4/pending-contacts.cpp468
-rw-r--r--qt4/TelepathyQt4/pending-contacts.h108
-rw-r--r--qt4/TelepathyQt4/pending-handles.cpp490
-rw-r--r--qt4/TelepathyQt4/pending-handles.h96
-rw-r--r--qt4/TelepathyQt4/pending-operation.cpp424
-rw-r--r--qt4/TelepathyQt4/pending-operation.h89
-rw-r--r--qt4/TelepathyQt4/pending-ready.cpp148
-rw-r--r--qt4/TelepathyQt4/pending-ready.h71
-rw-r--r--qt4/TelepathyQt4/pending-send-message.cpp153
-rw-r--r--qt4/TelepathyQt4/pending-send-message.h77
-rw-r--r--qt4/TelepathyQt4/pending-stream-tube-connection.cpp272
-rw-r--r--qt4/TelepathyQt4/pending-stream-tube-connection.h81
-rw-r--r--qt4/TelepathyQt4/pending-string-list.cpp100
-rw-r--r--qt4/TelepathyQt4/pending-string-list.h63
-rw-r--r--qt4/TelepathyQt4/pending-variant-map.cpp91
-rw-r--r--qt4/TelepathyQt4/pending-variant-map.h60
-rw-r--r--qt4/TelepathyQt4/pending-variant.cpp91
-rw-r--r--qt4/TelepathyQt4/pending-variant.h60
-rw-r--r--qt4/TelepathyQt4/presence.cpp335
-rw-r--r--qt4/TelepathyQt4/presence.h135
-rw-r--r--qt4/TelepathyQt4/profile-manager.cpp332
-rw-r--r--qt4/TelepathyQt4/profile-manager.h75
-rw-r--r--qt4/TelepathyQt4/profile.cpp1216
-rw-r--r--qt4/TelepathyQt4/profile.h176
-rw-r--r--qt4/TelepathyQt4/properties.cpp26
-rw-r--r--qt4/TelepathyQt4/properties.h51
-rw-r--r--qt4/TelepathyQt4/properties.xml9
-rw-r--r--qt4/TelepathyQt4/protocol-info.cpp374
-rw-r--r--qt4/TelepathyQt4/protocol-info.h102
-rw-r--r--qt4/TelepathyQt4/protocol-parameter.cpp176
-rw-r--r--qt4/TelepathyQt4/protocol-parameter.h87
-rw-r--r--qt4/TelepathyQt4/readiness-helper.cpp684
-rw-r--r--qt4/TelepathyQt4/readiness-helper.h130
-rw-r--r--qt4/TelepathyQt4/ready-object.cpp161
-rw-r--r--qt4/TelepathyQt4/ready-object.h69
-rw-r--r--qt4/TelepathyQt4/referenced-handles.cpp333
-rw-r--r--qt4/TelepathyQt4/referenced-handles.h264
-rw-r--r--qt4/TelepathyQt4/request-temporary-handler-internal.cpp135
-rw-r--r--qt4/TelepathyQt4/request-temporary-handler-internal.h91
-rw-r--r--qt4/TelepathyQt4/requestable-channel-class-spec.cpp494
-rw-r--r--qt4/TelepathyQt4/requestable-channel-class-spec.h134
-rw-r--r--qt4/TelepathyQt4/room-list-channel.cpp106
-rw-r--r--qt4/TelepathyQt4/room-list-channel.h59
-rw-r--r--qt4/TelepathyQt4/shared-ptr.dox111
-rw-r--r--qt4/TelepathyQt4/shared-ptr.h152
-rw-r--r--qt4/TelepathyQt4/simple-call-observer.cpp298
-rw-r--r--qt4/TelepathyQt4/simple-call-observer.h95
-rw-r--r--qt4/TelepathyQt4/simple-observer-internal.h261
-rw-r--r--qt4/TelepathyQt4/simple-observer.cpp643
-rw-r--r--qt4/TelepathyQt4/simple-observer.h105
-rw-r--r--qt4/TelepathyQt4/simple-pending-operations.h110
-rw-r--r--qt4/TelepathyQt4/simple-stream-tube-handler.cpp254
-rw-r--r--qt4/TelepathyQt4/simple-stream-tube-handler.h120
-rw-r--r--qt4/TelepathyQt4/simple-text-observer-internal.h70
-rw-r--r--qt4/TelepathyQt4/simple-text-observer.cpp298
-rw-r--r--qt4/TelepathyQt4/simple-text-observer.h80
-rw-r--r--qt4/TelepathyQt4/stable-interfaces.xml26
-rw-r--r--qt4/TelepathyQt4/stream-tube-channel.cpp740
-rw-r--r--qt4/TelepathyQt4/stream-tube-channel.h108
-rw-r--r--qt4/TelepathyQt4/stream-tube-client-internal.h61
-rw-r--r--qt4/TelepathyQt4/stream-tube-client.cpp1048
-rw-r--r--qt4/TelepathyQt4/stream-tube-client.h217
-rw-r--r--qt4/TelepathyQt4/stream-tube-server-internal.h56
-rw-r--r--qt4/TelepathyQt4/stream-tube-server.cpp1134
-rw-r--r--qt4/TelepathyQt4/stream-tube-server.h253
-rw-r--r--qt4/TelepathyQt4/streamed-media-channel.cpp1539
-rw-r--r--qt4/TelepathyQt4/streamed-media-channel.h228
-rw-r--r--qt4/TelepathyQt4/test-backdoors.cpp50
-rw-r--r--qt4/TelepathyQt4/test-backdoors.h59
-rw-r--r--qt4/TelepathyQt4/text-channel.cpp1277
-rw-r--r--qt4/TelepathyQt4/text-channel.h139
-rw-r--r--qt4/TelepathyQt4/tls-certificate.cpp26
-rw-r--r--qt4/TelepathyQt4/tls-certificate.h31
-rw-r--r--qt4/TelepathyQt4/tls-certificate.xml9
-rw-r--r--qt4/TelepathyQt4/tube-channel.cpp281
-rw-r--r--qt4/TelepathyQt4/tube-channel.h80
-rw-r--r--qt4/TelepathyQt4/types-internal.h156
-rw-r--r--qt4/TelepathyQt4/types.cpp73
-rw-r--r--qt4/TelepathyQt4/types.h171
-rw-r--r--qt4/TelepathyQt4/utils.cpp120
-rw-r--r--qt4/TelepathyQt4/utils.h41
-rw-r--r--qt4/cmake/modules/CompilerWarnings.cmake63
-rw-r--r--qt4/cmake/modules/Doxygen.cmake37
-rw-r--r--qt4/cmake/modules/FindDBus.cmake72
-rw-r--r--qt4/cmake/modules/FindDBusGLib.cmake48
-rw-r--r--qt4/cmake/modules/FindGIO.cmake37
-rw-r--r--qt4/cmake/modules/FindGIOUnix.cmake31
-rw-r--r--qt4/cmake/modules/FindGLIB2.cmake52
-rw-r--r--qt4/cmake/modules/FindGObject.cmake75
-rw-r--r--qt4/cmake/modules/FindGStreamer.cmake80
-rw-r--r--qt4/cmake/modules/FindLibPython.py12
-rw-r--r--qt4/cmake/modules/FindLibXml2.cmake57
-rw-r--r--qt4/cmake/modules/FindPythonLibrary.cmake83
-rw-r--r--qt4/cmake/modules/FindTelepathyFarsight.cmake49
-rw-r--r--qt4/cmake/modules/FindTelepathyGlib.cmake54
-rw-r--r--qt4/cmake/modules/MacroLogFeature.cmake146
-rw-r--r--qt4/cmake/modules/TelepathyDefaults.cmake146
-rw-r--r--qt4/cmake/modules/TelepathyDist.cmake110
-rw-r--r--qt4/cmake/modules/TpQt4Macros.cmake435
-rw-r--r--qt4/cmake_uninstall.cmake.in21
-rw-r--r--qt4/config-version.h.in1
-rw-r--r--qt4/config.h.in1
-rw-r--r--qt4/doxygen-footer.html7
-rw-r--r--qt4/doxygen-header.html28
-rw-r--r--qt4/doxygen.cfg.in1474
-rw-r--r--qt4/doxygen.css458
-rw-r--r--qt4/examples/CMakeLists.txt11
-rw-r--r--qt4/examples/accounts/CMakeLists.txt20
-rw-r--r--qt4/examples/accounts/account-item.cpp176
-rw-r--r--qt4/examples/accounts/account-item.h89
-rw-r--r--qt4/examples/accounts/accounts-window.cpp100
-rw-r--r--qt4/examples/accounts/accounts-window.h55
-rw-r--r--qt4/examples/accounts/main.cpp21
-rw-r--r--qt4/examples/contact-messenger/CMakeLists.txt14
-rw-r--r--qt4/examples/contact-messenger/sender.cpp82
-rw-r--r--qt4/examples/contact-messenger/sender.h51
-rw-r--r--qt4/examples/extensions/CMakeLists.txt76
-rw-r--r--qt4/examples/extensions/Connection_Interface_Hats.xml166
-rw-r--r--qt4/examples/extensions/all.xml18
-rw-r--r--qt4/examples/extensions/cli-connection.cpp2
-rw-r--r--qt4/examples/extensions/cli-connection.h6
-rw-r--r--qt4/examples/extensions/connection.xml9
-rw-r--r--qt4/examples/extensions/types.cpp1
-rw-r--r--qt4/examples/extensions/types.h6
-rw-r--r--qt4/examples/file-transfer/CMakeLists.txt39
-rw-r--r--qt4/examples/file-transfer/file-receiver-handler.cpp96
-rw-r--r--qt4/examples/file-transfer/file-receiver-handler.h64
-rw-r--r--qt4/examples/file-transfer/file-receiver.cpp78
-rw-r--r--qt4/examples/file-transfer/file-receiver.h46
-rw-r--r--qt4/examples/file-transfer/file-sender.cpp273
-rw-r--r--qt4/examples/file-transfer/file-sender.h66
-rw-r--r--qt4/examples/file-transfer/pending-file-receive.cpp71
-rw-r--r--qt4/examples/file-transfer/pending-file-receive.h51
-rw-r--r--qt4/examples/file-transfer/pending-file-send.cpp76
-rw-r--r--qt4/examples/file-transfer/pending-file-send.h51
-rw-r--r--qt4/examples/file-transfer/pending-file-transfer.cpp93
-rw-r--r--qt4/examples/file-transfer/pending-file-transfer.h56
-rw-r--r--qt4/examples/protocols/CMakeLists.txt18
-rw-r--r--qt4/examples/protocols/cm-wrapper.cpp65
-rw-r--r--qt4/examples/protocols/cm-wrapper.h59
-rw-r--r--qt4/examples/protocols/main.cpp21
-rw-r--r--qt4/examples/protocols/protocols.cpp73
-rw-r--r--qt4/examples/protocols/protocols.h57
-rw-r--r--qt4/examples/roster/CMakeLists.txt34
-rw-r--r--qt4/examples/roster/main.cpp48
-rw-r--r--qt4/examples/roster/roster-item.cpp78
-rw-r--r--qt4/examples/roster/roster-item.h52
-rw-r--r--qt4/examples/roster/roster-widget.cpp380
-rw-r--r--qt4/examples/roster/roster-widget.h91
-rw-r--r--qt4/examples/roster/roster-window.cpp102
-rw-r--r--qt4/examples/roster/roster-window.h55
-rw-r--r--qt4/examples/stream-tubes/CMakeLists.txt29
-rw-r--r--qt4/examples/stream-tubes/tube-initiator.cpp274
-rw-r--r--qt4/examples/stream-tubes/tube-initiator.h79
-rw-r--r--qt4/examples/stream-tubes/tube-receiver.cpp99
-rw-r--r--qt4/examples/stream-tubes/tube-receiver.h55
-rw-r--r--qt4/spec/Account.xml713
-rw-r--r--qt4/spec/Account_Interface_Addressing.xml76
-rw-r--r--qt4/spec/Account_Interface_Avatar.xml72
-rw-r--r--qt4/spec/Account_Interface_External_Password_Storage.xml58
-rw-r--r--qt4/spec/Account_Interface_Hidden.xml65
-rw-r--r--qt4/spec/Account_Interface_Minimum_Presence.xml108
-rw-r--r--qt4/spec/Account_Interface_Storage.xml169
-rw-r--r--qt4/spec/Account_Manager.xml296
-rw-r--r--qt4/spec/Account_Manager_Interface_Hidden.xml100
-rw-r--r--qt4/spec/Authentication_TLS_Certificate.xml305
-rw-r--r--qt4/spec/Call_Content.xml255
-rw-r--r--qt4/spec/Call_Content_Codec_Offer.xml87
-rw-r--r--qt4/spec/Call_Content_Interface_Media.xml367
-rw-r--r--qt4/spec/Call_Content_Interface_Mute.xml85
-rw-r--r--qt4/spec/Call_Content_Interface_Video_Control.xml137
-rw-r--r--qt4/spec/Call_Stream.xml261
-rw-r--r--qt4/spec/Call_Stream_Endpoint.xml182
-rw-r--r--qt4/spec/Call_Stream_Interface_Media.xml473
-rw-r--r--qt4/spec/Channel.xml557
-rw-r--r--qt4/spec/Channel_Bundle.xml48
-rw-r--r--qt4/spec/Channel_Dispatch_Operation.xml483
-rw-r--r--qt4/spec/Channel_Dispatcher.xml595
-rw-r--r--qt4/spec/Channel_Dispatcher_Future.xml377
-rw-r--r--qt4/spec/Channel_Dispatcher_Interface_Messages.xml49
-rw-r--r--qt4/spec/Channel_Dispatcher_Interface_Operation_List.xml140
-rw-r--r--qt4/spec/Channel_Future.xml68
-rw-r--r--qt4/spec/Channel_Handler.xml78
-rw-r--r--qt4/spec/Channel_Interface_Addressing.xml107
-rw-r--r--qt4/spec/Channel_Interface_Anonymity.xml68
-rw-r--r--qt4/spec/Channel_Interface_Call_State.xml147
-rw-r--r--qt4/spec/Channel_Interface_Chat_State.xml144
-rw-r--r--qt4/spec/Channel_Interface_Conference.xml628
-rw-r--r--qt4/spec/Channel_Interface_Credentials_Storage.xml59
-rw-r--r--qt4/spec/Channel_Interface_DTMF.xml342
-rw-r--r--qt4/spec/Channel_Interface_Destroyable.xml82
-rw-r--r--qt4/spec/Channel_Interface_Group.xml1166
-rw-r--r--qt4/spec/Channel_Interface_HTML.xml86
-rw-r--r--qt4/spec/Channel_Interface_Hold.xml222
-rw-r--r--qt4/spec/Channel_Interface_Media_Signalling.xml235
-rw-r--r--qt4/spec/Channel_Interface_Mergeable_Conference.xml110
-rw-r--r--qt4/spec/Channel_Interface_Messages.xml1433
-rw-r--r--qt4/spec/Channel_Interface_Password.xml104
-rw-r--r--qt4/spec/Channel_Interface_Room.xml373
-rw-r--r--qt4/spec/Channel_Interface_SASL_Authentication.xml723
-rw-r--r--qt4/spec/Channel_Interface_SMS.xml179
-rw-r--r--qt4/spec/Channel_Interface_Securable.xml78
-rw-r--r--qt4/spec/Channel_Interface_Service_Point.xml86
-rw-r--r--qt4/spec/Channel_Interface_Splittable.xml71
-rw-r--r--qt4/spec/Channel_Interface_Transfer.xml53
-rw-r--r--qt4/spec/Channel_Interface_Tube.xml258
-rw-r--r--qt4/spec/Channel_Request.xml303
-rw-r--r--qt4/spec/Channel_Request_Future.xml98
-rw-r--r--qt4/spec/Channel_Type_Call.xml1429
-rw-r--r--qt4/spec/Channel_Type_Contact_List.xml104
-rw-r--r--qt4/spec/Channel_Type_Contact_Search.xml462
-rw-r--r--qt4/spec/Channel_Type_DBus_Tube.xml189
-rw-r--r--qt4/spec/Channel_Type_File_Transfer.xml580
-rw-r--r--qt4/spec/Channel_Type_Room_List.xml166
-rw-r--r--qt4/spec/Channel_Type_Server_Authentication.xml121
-rw-r--r--qt4/spec/Channel_Type_Server_TLS_Connection.xml117
-rw-r--r--qt4/spec/Channel_Type_Stream_Tube.xml292
-rw-r--r--qt4/spec/Channel_Type_Streamed_Media.xml853
-rw-r--r--qt4/spec/Channel_Type_Text.xml669
-rw-r--r--qt4/spec/Channel_Type_Tubes.xml615
-rw-r--r--qt4/spec/Client.xml122
-rw-r--r--qt4/spec/Client_Approver.xml201
-rw-r--r--qt4/spec/Client_Handler.xml337
-rw-r--r--qt4/spec/Client_Handler_Future.xml88
-rw-r--r--qt4/spec/Client_Interface_Requests.xml175
-rw-r--r--qt4/spec/Client_Observer.xml447
-rw-r--r--qt4/spec/Connection.xml1299
-rw-r--r--qt4/spec/Connection_Future.xml110
-rw-r--r--qt4/spec/Connection_Interface_Addressing.xml258
-rw-r--r--qt4/spec/Connection_Interface_Aliasing.xml185
-rw-r--r--qt4/spec/Connection_Interface_Anonymity.xml165
-rw-r--r--qt4/spec/Connection_Interface_Avatars.xml516
-rw-r--r--qt4/spec/Connection_Interface_Balance.xml111
-rw-r--r--qt4/spec/Connection_Interface_Capabilities.xml254
-rw-r--r--qt4/spec/Connection_Interface_Cellular.xml157
-rw-r--r--qt4/spec/Connection_Interface_Client_Types.xml218
-rw-r--r--qt4/spec/Connection_Interface_Communication_Policy.xml163
-rw-r--r--qt4/spec/Connection_Interface_Contact_Blocking.xml207
-rw-r--r--qt4/spec/Connection_Interface_Contact_Capabilities.xml306
-rw-r--r--qt4/spec/Connection_Interface_Contact_Groups.xml549
-rw-r--r--qt4/spec/Connection_Interface_Contact_Info.xml550
-rw-r--r--qt4/spec/Connection_Interface_Contact_List.xml1085
-rw-r--r--qt4/spec/Connection_Interface_Contacts.xml191
-rw-r--r--qt4/spec/Connection_Interface_Forwarding.xml346
-rw-r--r--qt4/spec/Connection_Interface_Keepalive.xml73
-rw-r--r--qt4/spec/Connection_Interface_Location.xml464
-rw-r--r--qt4/spec/Connection_Interface_Mail_Notification.xml624
-rw-r--r--qt4/spec/Connection_Interface_Power_Saving.xml109
-rw-r--r--qt4/spec/Connection_Interface_Presence.xml346
-rw-r--r--qt4/spec/Connection_Interface_Privacy.xml94
-rw-r--r--qt4/spec/Connection_Interface_Renaming.xml98
-rw-r--r--qt4/spec/Connection_Interface_Requests.xml628
-rw-r--r--qt4/spec/Connection_Interface_Resources.xml212
-rw-r--r--qt4/spec/Connection_Interface_Service_Point.xml136
-rw-r--r--qt4/spec/Connection_Interface_Simple_Presence.xml634
-rw-r--r--qt4/spec/Connection_Manager.xml619
-rw-r--r--qt4/spec/Connection_Manager_Interface_Account_Storage.xml120
-rw-r--r--qt4/spec/Debug.xml165
-rw-r--r--qt4/spec/Media_Session_Handler.xml81
-rw-r--r--qt4/spec/Media_Stream_Handler.xml725
-rw-r--r--qt4/spec/Properties_Interface.xml194
-rw-r--r--qt4/spec/Protocol.xml485
-rw-r--r--qt4/spec/Protocol_Interface_Addressing.xml300
-rw-r--r--qt4/spec/Protocol_Interface_Avatars.xml157
-rw-r--r--qt4/spec/Protocol_Interface_Presence.xml113
-rw-r--r--qt4/spec/all.xml313
-rw-r--r--qt4/spec/errors.xml601
-rw-r--r--qt4/spec/generic-types.xml215
-rw-r--r--qt4/spec/template.xml33
-rw-r--r--qt4/tests/CMakeLists.txt46
-rw-r--r--qt4/tests/README13
-rw-r--r--qt4/tests/capabilities.cpp398
-rw-r--r--qt4/tests/channel-class-spec.cpp153
-rw-r--r--qt4/tests/dbus-1/CMakeLists.txt4
-rw-r--r--qt4/tests/dbus-1/services/CMakeLists.txt4
-rw-r--r--qt4/tests/dbus-1/services/account-manager.service.in3
-rw-r--r--qt4/tests/dbus-1/services/spurious.service.in3
-rw-r--r--qt4/tests/dbus-1/session.conf.in30
-rw-r--r--qt4/tests/dbus/CMakeLists.txt67
-rw-r--r--qt4/tests/dbus/account-basics.cpp592
-rw-r--r--qt4/tests/dbus/account-channel-dispatcher.cpp1244
-rw-r--r--qt4/tests/dbus/account-connection-factory.cpp449
-rw-r--r--qt4/tests/dbus/account-set.cpp416
-rw-r--r--qt4/tests/dbus/chan-basics.cpp286
-rw-r--r--qt4/tests/dbus/chan-conference.cpp288
-rw-r--r--qt4/tests/dbus/chan-group.cpp511
-rw-r--r--qt4/tests/dbus/client-factories.cpp1184
-rw-r--r--qt4/tests/dbus/client.cpp858
-rw-r--r--qt4/tests/dbus/cm-basics.cpp308
-rw-r--r--qt4/tests/dbus/conn-basics.cpp302
-rw-r--r--qt4/tests/dbus/conn-capabilities.cpp107
-rw-r--r--qt4/tests/dbus/conn-introspect-cornercases.cpp504
-rw-r--r--qt4/tests/dbus/conn-requests.cpp165
-rw-r--r--qt4/tests/dbus/conn-roster-groups-legacy.cpp849
-rw-r--r--qt4/tests/dbus/conn-roster-groups.cpp856
-rw-r--r--qt4/tests/dbus/conn-roster-legacy.cpp475
-rw-r--r--qt4/tests/dbus/conn-roster.cpp496
-rw-r--r--qt4/tests/dbus/contact-factory.cpp87
-rw-r--r--qt4/tests/dbus/contact-messenger.cpp685
-rw-r--r--qt4/tests/dbus/contact-search-chan.cpp310
-rw-r--r--qt4/tests/dbus/contacts-avatar.cpp331
-rw-r--r--qt4/tests/dbus/contacts-capabilities.cpp161
-rw-r--r--qt4/tests/dbus/contacts-info.cpp247
-rw-r--r--qt4/tests/dbus/contacts-location.cpp147
-rw-r--r--qt4/tests/dbus/contacts.cpp814
-rw-r--r--qt4/tests/dbus/dbus-properties.cpp165
-rw-r--r--qt4/tests/dbus/dbus-proxy-factory.cpp361
-rw-r--r--qt4/tests/dbus/do-nothing.cpp64
-rw-r--r--qt4/tests/dbus/handles.cpp128
-rw-r--r--qt4/tests/dbus/profile-manager.cpp86
-rw-r--r--qt4/tests/dbus/simple-observer.cpp745
-rw-r--r--qt4/tests/dbus/stateful-proxy.cpp313
-rw-r--r--qt4/tests/dbus/stream-tube-chan.cpp907
-rw-r--r--qt4/tests/dbus/stream-tube-handlers.cpp1939
-rw-r--r--qt4/tests/dbus/streamed-media-chan.cpp1374
-rw-r--r--qt4/tests/dbus/text-chan.cpp557
-rw-r--r--qt4/tests/dbus/types.cpp141
-rw-r--r--qt4/tests/features.cpp73
-rw-r--r--qt4/tests/file-transfer-channel-creation-properties.cpp145
-rw-r--r--qt4/tests/key-file.cpp111
-rw-r--r--qt4/tests/lib/CMakeLists.txt12
-rw-r--r--qt4/tests/lib/glib-helpers/CMakeLists.txt22
-rw-r--r--qt4/tests/lib/glib-helpers/test-conn-helper.cpp415
-rw-r--r--qt4/tests/lib/glib-helpers/test-conn-helper.h102
-rw-r--r--qt4/tests/lib/glib/CMakeLists.txt69
-rw-r--r--qt4/tests/lib/glib/bug16307-conn.c220
-rw-r--r--qt4/tests/lib/glib/bug16307-conn.h61
-rw-r--r--qt4/tests/lib/glib/callable/CMakeLists.txt17
-rw-r--r--qt4/tests/lib/glib/callable/conn.c421
-rw-r--r--qt4/tests/lib/glib/callable/conn.h78
-rw-r--r--qt4/tests/lib/glib/callable/connection-manager.c130
-rw-r--r--qt4/tests/lib/glib/callable/connection-manager.h73
-rw-r--r--qt4/tests/lib/glib/callable/manager-file.py23
-rw-r--r--qt4/tests/lib/glib/callable/media-channel.c1467
-rw-r--r--qt4/tests/lib/glib/callable/media-channel.h74
-rw-r--r--qt4/tests/lib/glib/callable/media-manager.c496
-rw-r--r--qt4/tests/lib/glib/callable/media-manager.h71
-rw-r--r--qt4/tests/lib/glib/callable/media-stream.c650
-rw-r--r--qt4/tests/lib/glib/callable/media-stream.h88
-rw-r--r--qt4/tests/lib/glib/contact-list-manager.c745
-rw-r--r--qt4/tests/lib/glib/contact-list-manager.h69
-rw-r--r--qt4/tests/lib/glib/contact-search-chan.c708
-rw-r--r--qt4/tests/lib/glib/contact-search-chan.h74
-rw-r--r--qt4/tests/lib/glib/contactlist/CMakeLists.txt15
-rw-r--r--qt4/tests/lib/glib/contactlist/conn.c606
-rw-r--r--qt4/tests/lib/glib/contactlist/conn.h65
-rw-r--r--qt4/tests/lib/glib/contactlist/connection-manager.c117
-rw-r--r--qt4/tests/lib/glib/contactlist/connection-manager.h62
-rw-r--r--qt4/tests/lib/glib/contactlist/contact-list-manager.c1774
-rw-r--r--qt4/tests/lib/glib/contactlist/contact-list-manager.h108
-rw-r--r--qt4/tests/lib/glib/contactlist/contact-list.c636
-rw-r--r--qt4/tests/lib/glib/contactlist/contact-list.h118
-rw-r--r--qt4/tests/lib/glib/contactlist/manager-file.py23
-rw-r--r--qt4/tests/lib/glib/contactlist2/CMakeLists.txt14
-rw-r--r--qt4/tests/lib/glib/contactlist2/conn.c604
-rw-r--r--qt4/tests/lib/glib/contactlist2/conn.h68
-rw-r--r--qt4/tests/lib/glib/contactlist2/connection-manager.c73
-rw-r--r--qt4/tests/lib/glib/contactlist2/connection-manager.h62
-rw-r--r--qt4/tests/lib/glib/contactlist2/contact-list.c1735
-rw-r--r--qt4/tests/lib/glib/contactlist2/contact-list.h78
-rw-r--r--qt4/tests/lib/glib/contactlist2/example_contact_list.manager23
-rw-r--r--qt4/tests/lib/glib/contactlist2/protocol.c186
-rw-r--r--qt4/tests/lib/glib/contactlist2/protocol.h68
-rw-r--r--qt4/tests/lib/glib/contacts-conn.c1335
-rw-r--r--qt4/tests/lib/glib/contacts-conn.h191
-rw-r--r--qt4/tests/lib/glib/contacts-noroster-conn.c50
-rw-r--r--qt4/tests/lib/glib/contacts-noroster-conn.h52
-rw-r--r--qt4/tests/lib/glib/csh/CMakeLists.txt15
-rw-r--r--qt4/tests/lib/glib/csh/conn.c292
-rw-r--r--qt4/tests/lib/glib/csh/conn.h58
-rw-r--r--qt4/tests/lib/glib/csh/connection-manager.c133
-rw-r--r--qt4/tests/lib/glib/csh/connection-manager.h61
-rw-r--r--qt4/tests/lib/glib/csh/manager-file.py23
-rw-r--r--qt4/tests/lib/glib/csh/room-manager.c384
-rw-r--r--qt4/tests/lib/glib/csh/room-manager.h55
-rw-r--r--qt4/tests/lib/glib/csh/room.c696
-rw-r--r--qt4/tests/lib/glib/csh/room.h64
-rw-r--r--qt4/tests/lib/glib/debug.h3
-rw-r--r--qt4/tests/lib/glib/echo/CMakeLists.txt18
-rw-r--r--qt4/tests/lib/glib/echo/chan.c528
-rw-r--r--qt4/tests/lib/glib/echo/chan.h58
-rw-r--r--qt4/tests/lib/glib/echo/conn.c192
-rw-r--r--qt4/tests/lib/glib/echo/conn.h55
-rw-r--r--qt4/tests/lib/glib/echo/connection-manager.c87
-rw-r--r--qt4/tests/lib/glib/echo/connection-manager.h59
-rw-r--r--qt4/tests/lib/glib/echo/im-manager.c378
-rw-r--r--qt4/tests/lib/glib/echo/im-manager.h54
-rw-r--r--qt4/tests/lib/glib/echo/manager-file.py19
-rw-r--r--qt4/tests/lib/glib/echo2/CMakeLists.txt17
-rw-r--r--qt4/tests/lib/glib/echo2/chan.c661
-rw-r--r--qt4/tests/lib/glib/echo2/chan.h58
-rw-r--r--qt4/tests/lib/glib/echo2/conn.c194
-rw-r--r--qt4/tests/lib/glib/echo2/conn.h59
-rw-r--r--qt4/tests/lib/glib/echo2/connection-manager.c81
-rw-r--r--qt4/tests/lib/glib/echo2/connection-manager.h64
-rw-r--r--qt4/tests/lib/glib/echo2/im-manager.c377
-rw-r--r--qt4/tests/lib/glib/echo2/im-manager.h54
-rw-r--r--qt4/tests/lib/glib/echo2/manager-file.py19
-rw-r--r--qt4/tests/lib/glib/echo2/protocol.c293
-rw-r--r--qt4/tests/lib/glib/echo2/protocol.h67
-rw-r--r--qt4/tests/lib/glib/future/CMakeLists.txt4
-rw-r--r--qt4/tests/lib/glib/future/conference/CMakeLists.txt12
-rw-r--r--qt4/tests/lib/glib/future/conference/chan.c665
-rw-r--r--qt4/tests/lib/glib/future/conference/chan.h80
-rw-r--r--qt4/tests/lib/glib/future/extensions/CMakeLists.txt118
-rw-r--r--qt4/tests/lib/glib/future/extensions/all.xml11
-rw-r--r--qt4/tests/lib/glib/future/extensions/channel.xml12
-rw-r--r--qt4/tests/lib/glib/future/extensions/extensions.c6
-rw-r--r--qt4/tests/lib/glib/future/extensions/extensions.h18
-rw-r--r--qt4/tests/lib/glib/future/extensions/misc.xml14
-rw-r--r--qt4/tests/lib/glib/params-cm.c208
-rw-r--r--qt4/tests/lib/glib/params-cm.h95
-rw-r--r--qt4/tests/lib/glib/simple-account-manager.c180
-rw-r--r--qt4/tests/lib/glib/simple-account-manager.h58
-rw-r--r--qt4/tests/lib/glib/simple-account.c301
-rw-r--r--qt4/tests/lib/glib/simple-account.h56
-rw-r--r--qt4/tests/lib/glib/simple-channel-dispatch-operation.c297
-rw-r--r--qt4/tests/lib/glib/simple-channel-dispatch-operation.h74
-rw-r--r--qt4/tests/lib/glib/simple-client.c243
-rw-r--r--qt4/tests/lib/glib/simple-client.h59
-rw-r--r--qt4/tests/lib/glib/simple-conn.c453
-rw-r--r--qt4/tests/lib/glib/simple-conn.h77
-rw-r--r--qt4/tests/lib/glib/simple-manager.c97
-rw-r--r--qt4/tests/lib/glib/simple-manager.h58
-rw-r--r--qt4/tests/lib/glib/stream-tube-chan.c778
-rw-r--r--qt4/tests/lib/glib/stream-tube-chan.h141
-rw-r--r--qt4/tests/lib/glib/textchan-group.c439
-rw-r--r--qt4/tests/lib/glib/textchan-group.h65
-rw-r--r--qt4/tests/lib/glib/textchan-null.c582
-rw-r--r--qt4/tests/lib/glib/textchan-null.h139
-rw-r--r--qt4/tests/lib/glib/util.c249
-rw-r--r--qt4/tests/lib/glib/util.h51
-rwxr-xr-xqt4/tests/lib/python/account-manager.py394
-rw-r--r--qt4/tests/lib/test.cpp132
-rw-r--r--qt4/tests/lib/test.h88
-rw-r--r--qt4/tests/manager-file.cpp227
-rw-r--r--qt4/tests/presence.cpp131
-rw-r--r--qt4/tests/profile.cpp135
-rw-r--r--qt4/tests/ptr.cpp151
-rw-r--r--qt4/tests/rccspec.cpp141
-rw-r--r--qt4/tests/telepathy/managers/spurious.manager23
-rw-r--r--qt4/tests/telepathy/managers/test-manager-file-invalid-signature.manager86
-rw-r--r--qt4/tests/telepathy/managers/test-manager-file-malformed-keyfile.manager4
-rw-r--r--qt4/tests/telepathy/managers/test-manager-file.manager115
-rw-r--r--qt4/tests/telepathy/profiles/test-profile-invalid-service-id.profile33
-rw-r--r--qt4/tests/telepathy/profiles/test-profile-malformed.profile4
-rw-r--r--qt4/tests/telepathy/profiles/test-profile-no-icon-and-provider.profile7
-rw-r--r--qt4/tests/telepathy/profiles/test-profile-non-im-type.profile11
-rw-r--r--qt4/tests/telepathy/profiles/test-profile.profile39
-rw-r--r--qt4/tests/test-key-file-format-error.ini4
-rw-r--r--qt4/tests/test-key-file.ini9
-rw-r--r--qt4/tests/utils.cpp34
-rw-r--r--qt4/tools/CMakeLists.txt124
-rw-r--r--qt4/tools/c-constants-gen.py154
-rw-r--r--qt4/tools/check-misc.sh13
-rw-r--r--qt4/tools/check-whitespace.sh17
-rw-r--r--qt4/tools/git-which-branch.sh25
-rw-r--r--qt4/tools/glib-ginterface-gen.py802
-rw-r--r--qt4/tools/glib-gtypes-generator.py291
-rw-r--r--qt4/tools/glib-interfaces-gen.py119
-rw-r--r--qt4/tools/glib-signals-marshal-gen.py55
-rw-r--r--qt4/tools/libglibcodegen.py172
-rw-r--r--qt4/tools/libqt4codegen.py499
-rw-r--r--qt4/tools/libtpcodegen.py215
-rw-r--r--qt4/tools/manager-file.py175
-rw-r--r--qt4/tools/qt4-client-gen.py547
-rw-r--r--qt4/tools/qt4-constants-gen.py310
-rw-r--r--qt4/tools/qt4-types-gen.py557
-rwxr-xr-xqt4/tools/repeat-tests.sh23
-rw-r--r--qt4/tools/telepathy-glib.supp390
-rw-r--r--qt4/tools/tp-qt4-tests.supp53
-rw-r--r--qt4/tools/with-session-bus.sh94
-rw-r--r--qt4/tools/xincludator.py39
859 files changed, 163935 insertions, 0 deletions
diff --git a/qt4/.gitignore b/qt4/.gitignore
new file mode 100644
index 000000000..51b2d6235
--- /dev/null
+++ b/qt4/.gitignore
@@ -0,0 +1,10 @@
+# For a project mostly in C, the following would be a good set of
+# Lines that start with '#' are comments.
+# exclude patterns (uncomment them if you want to use them):
+# git-ls-files --others --exclude-from=.git/info/exclude
+*.directory
+*.py[co]
+*~
+.*.sw[p,o]
+/build
+tags
diff --git a/qt4/AUTHORS b/qt4/AUTHORS
new file mode 100644
index 000000000..023e0ef8f
--- /dev/null
+++ b/qt4/AUTHORS
@@ -0,0 +1,3 @@
+Andre Moreira Magalhaes (andrunko) <andre.magalhaes@collabora.co.uk>
+Olli Salli (oggis) <olli.salli@collabora.co.uk>
+Simon McVittie (smcv) <simon.mcvittie@collabora.co.uk>
diff --git a/qt4/CMakeLists.txt b/qt4/CMakeLists.txt
new file mode 100644
index 000000000..2b0787f8c
--- /dev/null
+++ b/qt4/CMakeLists.txt
@@ -0,0 +1,229 @@
+project(TelepathyQt4)
+
+cmake_minimum_required(VERSION 2.6)
+
+# CMake policies are used for backwards compatibilty. Setting a policy to a behavior lets newer
+# CMake versions where some behaviors changed behave in a way or another. In our specific case,
+# From CMake's documentation:
+#
+# In CMake 2.6.2 and below, CMake Policy settings in scripts loaded by
+# the include() and find_package() commands would affect the includer.
+# Explicit invocations of cmake_policy(PUSH) and cmake_policy(POP) were
+# required to isolate policy changes and protect the includer. While
+# some scripts intend to affect the policies of their includer, most do
+# not. In CMake 2.6.3 and above, include() and find_package() by
+# default PUSH and POP an entry on the policy stack around an included
+# script, but provide a NO_POLICY_SCOPE option to disable it. This
+# policy determines whether or not to imply NO_POLICY_SCOPE for
+# compatibility. The OLD behavior for this policy is to imply
+# NO_POLICY_SCOPE for include() and find_package() commands. The NEW
+# behavior for this policy is to allow the commands to do their default
+# cmake_policy PUSH and POP.
+#
+# This policy was introduced in CMake version 2.6.3. CMake version
+# 2.8.2 warns when the policy is not set and uses OLD behavior. Use the
+# cmake_policy command to set it to OLD or NEW explicitly.
+#
+# Whenever our cmake_minimum_required version bumps up to 2.7 or 2.6.3, this policy setting can
+# hence be removed.
+if(POLICY CMP0011)
+ cmake_policy(SET CMP0011 NEW)
+endif(POLICY CMP0011)
+
+# Making releases:
+# set the new version number:
+# odd minor -> development series
+# even minor -> stable series
+# increment micro for each release within a series
+# set nano_version to 0
+# make the release, tag it
+# set nano_version to 1
+set(TP_QT4_MAJOR_VERSION 0)
+set(TP_QT4_MINOR_VERSION 8)
+set(TP_QT4_MICRO_VERSION 9999)
+set(TP_QT4_NANO_VERSION 1)
+
+# This value contains the library's SOVERSION. This value is to be increased everytime an API/ABI break
+# occurs, and will be used for the SOVERSION of the generated shared libraries.
+set(TP_QT4_ABI_VERSION 1)
+# This variable is used for the library's long version. It is generated dynamically, so don't change its
+# value! Change TP_QT4_ABI_VERSION and TP_QT4_*_VERSION instead.
+if (${TP_QT4_NANO_VERSION} EQUAL 0)
+ set(TP_QT4_LIBRARY_VERSION ${TP_QT4_ABI_VERSION}.${TP_QT4_MAJOR_VERSION}.${TP_QT4_MINOR_VERSION}.${TP_QT4_MICRO_VERSION})
+else (${TP_QT4_NANO_VERSION} EQUAL 0)
+ set(TP_QT4_LIBRARY_VERSION ${TP_QT4_ABI_VERSION}.${TP_QT4_MAJOR_VERSION}.${TP_QT4_MINOR_VERSION}.${TP_QT4_MICRO_VERSION}.${TP_QT4_NANO_VERSION})
+endif (${TP_QT4_NANO_VERSION} EQUAL 0)
+
+set(PACKAGE_NAME telepathy-qt4)
+
+if (${TP_QT4_NANO_VERSION} EQUAL 0)
+ set(PACKAGE_VERSION ${TP_QT4_MAJOR_VERSION}.${TP_QT4_MINOR_VERSION}.${TP_QT4_MICRO_VERSION})
+else (${TP_QT4_NANO_VERSION} EQUAL 0)
+ set(PACKAGE_VERSION ${TP_QT4_MAJOR_VERSION}.${TP_QT4_MINOR_VERSION}.${TP_QT4_MICRO_VERSION}.${TP_QT4_NANO_VERSION})
+endif (${TP_QT4_NANO_VERSION} EQUAL 0)
+
+# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is
+# checked
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
+
+# Default build type is RelWithDebInfo for release versions and Debug for developement
+# versions
+if(NOT CMAKE_BUILD_TYPE)
+ if(TP_QT4_NANO_VERSION EQUAL 0)
+ set(CMAKE_BUILD_TYPE RelWithDebInfo)
+ else(TP_QT4_NANO_VERSION EQUAL 0)
+ set(CMAKE_BUILD_TYPE Debug)
+ endif(TP_QT4_NANO_VERSION EQUAL 0)
+endif(NOT CMAKE_BUILD_TYPE)
+
+# This file contains all the needed initialization macros
+include(TelepathyDefaults)
+
+# This file contains all the tpqt4 macros used in the buildsystem
+include(TpQt4Macros)
+
+include(MacroLogFeature)
+
+# external dependencies
+
+# Required dependencies
+# Find qt4 version >= 4.5
+set (QT_MIN_VERSION "4.6.0")
+find_package(Qt4 REQUIRED)
+
+# The doxygen macro requires Qt to have been looked up to enable crosslinking
+include(Doxygen)
+
+include_directories(${CMAKE_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}
+ ${QT_INCLUDES})
+
+add_definitions(-DQT_NO_CAST_FROM_ASCII)
+
+set(ENABLE_DEBUG_OUTPUT ON CACHE BOOL "If activated, compiles support for printing debug output to stderr")
+if (ENABLE_DEBUG_OUTPUT)
+ add_definitions(-DENABLE_DEBUG)
+endif (ENABLE_DEBUG_OUTPUT)
+
+# Check for Qt4 Glib support
+include(CheckCXXSourceCompiles)
+set(CMAKE_REQUIRED_INCLUDES ${QT_INCLUDES})
+set(CMAKE_REQUIRED_DEFINITIONS "")
+set(CMAKE_REQUIRED_FLAGS "")
+
+CHECK_CXX_SOURCE_COMPILES("
+#include <QtCore/QtGlobal>
+int main()
+{
+#if defined(QT_NO_GLIB)
+#error \"Qt was compiled with Glib disabled\"
+#endif
+return 0;
+}"
+QT4_GLIB_SUPPORT)
+macro_log_feature(QT4_GLIB_SUPPORT "Qt4 Glib Support"
+ "QtCore library using Glib's main event loop"
+ "http://qt.nokia.com/" FALSE ""
+ "Needed, together with Telepathy-Glib, to build most of the unit tests")
+
+# Find python version >= 2.5
+find_package(PythonLibrary REQUIRED)
+set(REQUIRED_PY 2.5)
+if(${PYTHON_SHORT_VERSION} VERSION_GREATER ${REQUIRED_PY} OR ${PYTHON_SHORT_VERSION} VERSION_EQUAL ${REQUIRED_PY})
+ message(STATUS "Python ${PYTHON_SHORT_VERSION} found")
+else(${PYTHON_SHORT_VERSION} VERSION_GREATER ${REQUIRED_PY} OR ${PYTHON_SHORT_VERSION} VERSION_EQUAL ${REQUIRED_PY})
+ message(SEND_ERROR "Python >= ${REQUIRED_PY} is required")
+endif(${PYTHON_SHORT_VERSION} VERSION_GREATER ${REQUIRED_PY} OR ${PYTHON_SHORT_VERSION} VERSION_EQUAL ${REQUIRED_PY})
+
+# Check for dbus-python
+execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import dbus.mainloop.glib"
+ RESULT_VARIABLE PYTHON_DBUS_RESULT)
+if(PYTHON_DBUS_RESULT EQUAL 0)
+ set(HAVE_TEST_PYTHON 1)
+else(PYTHON_DBUS_RESULT EQUAL 0)
+ set(HAVE_TEST_PYTHON 0)
+endif(PYTHON_DBUS_RESULT EQUAL 0)
+macro_log_feature(HAVE_TEST_PYTHON "dbus-python"
+ "GLib-based Python DBus support"
+ "http://www.python.org/" FALSE ""
+ "Needed to build some additional unit tests")
+
+# Find GLib2, GObject, DBus and LibXml2
+# Those are needed for the insane include dir dependency hell
+find_package(GLIB2)
+find_package(GObject)
+find_package(GIO)
+find_package(GIOUnix)
+find_package(DBus)
+find_package(DBusGLib)
+find_package(LibXml2)
+
+# Find tp-farsight
+set(TELEPATHY_FARSIGHT_MIN_VERSION "0.0.4")
+find_package(TelepathyFarsight)
+macro_log_feature(TELEPATHYFARSIGHT_FOUND "Telepathy-Farsight"
+ "A Framework for dealing with audio/video conferencing protocols"
+ "http://farsight.freedesktop.org/wiki/" FALSE "0.0.4"
+ "Needed, together with GStreamer, to build telepathy-qt4-farsight and some additional examples")
+
+# Find GStreamer
+find_package(GStreamer)
+macro_log_feature(GSTREAMER_FOUND "GStreamer"
+ "An open source multimedia framework"
+ "Needed, together with Tp-Farsight, to build telepathy-qt4-farsight and some additional examples"
+ "http://www.gstreamer.net/" FALSE)
+
+# Build TelepathyQt4-Farsight only if GStreamer, TelepathyFarsight and all of their dependencies were found
+if (TELEPATHYFARSIGHT_FOUND AND GSTREAMER_FOUND AND GLIB2_FOUND AND GOBJECT_FOUND AND DBUS_FOUND AND LIBXML2_FOUND)
+ set (FARSIGHT_COMPONENTS_FOUND 1)
+else (TELEPATHYFARSIGHT_FOUND AND GSTREAMER_FOUND AND GLIB2_FOUND AND GOBJECT_FOUND AND DBUS_FOUND AND LIBXML2_FOUND)
+ set (FARSIGHT_COMPONENTS_FOUND 0)
+endif (TELEPATHYFARSIGHT_FOUND AND GSTREAMER_FOUND AND GLIB2_FOUND AND GOBJECT_FOUND AND DBUS_FOUND AND LIBXML2_FOUND)
+
+# Find telepathy-glib
+set(TELEPATHY_GLIB_MIN_VERSION 0.15.1)
+find_package(TelepathyGlib)
+macro_log_feature(TELEPATHYGLIB_FOUND "Telepathy-glib"
+ "Glib bindings for Telepathy"
+ "http://telepathy.freedesktop.org/" FALSE "0.15.1"
+ "Needed, together with Qt Glib integration, to build most of the unit tests")
+
+find_program(GLIB_GENMARSHAL glib-genmarshal)
+
+# Enable glib-based tests only if Qt4 has GLib support and Telepathy-glib was found
+if(QT4_GLIB_SUPPORT AND TELEPATHYGLIB_FOUND AND GLIB2_FOUND AND DBUS_FOUND)
+ set(ENABLE_TP_GLIB_TESTS 1)
+ if(GIO_FOUND AND GIOUNIX_FOUND)
+ set(ENABLE_TP_GLIB_GIO_TESTS 1)
+ else(GIO_FOUND AND GIOUNIX_FOUND)
+ set(ENABLE_TP_GLIB_GIO_TESTS 0)
+ endif(GIO_FOUND AND GIOUNIX_FOUND)
+else(QT4_GLIB_SUPPORT AND TELEPATHYGLIB_FOUND AND GLIB2_FOUND AND DBUS_FOUND)
+ set(ENABLE_TP_GLIB_TESTS 0)
+ set(ENABLE_TP_GLIB_GIO_TESTS 0)
+endif(QT4_GLIB_SUPPORT AND TELEPATHYGLIB_FOUND AND GLIB2_FOUND AND DBUS_FOUND)
+
+# Add the source subdirectories
+add_subdirectory(TelepathyQt4)
+add_subdirectory(examples)
+add_subdirectory(tests)
+add_subdirectory(tools)
+
+# Generate config.h and config-version.h
+configure_file(${CMAKE_SOURCE_DIR}/config.h.in ${CMAKE_BINARY_DIR}/config.h)
+configure_file(${CMAKE_SOURCE_DIR}/config-version.h.in ${CMAKE_BINARY_DIR}/config-version.h)
+
+# Create the uninstall target
+configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+ IMMEDIATE @ONLY)
+
+add_custom_target(uninstall
+ "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
+
+# Display the feature log
+macro_display_feature_log()
+
+# Always keep it last: CPack definitions file
+include(TelepathyDist)
diff --git a/qt4/COPYING b/qt4/COPYING
new file mode 100644
index 000000000..2d2d780e6
--- /dev/null
+++ b/qt4/COPYING
@@ -0,0 +1,510 @@
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations
+below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it
+becomes a de-facto standard. To achieve this, non-free programs must
+be allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control
+compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply, and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License
+may add an explicit geographical distribution limitation excluding those
+countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms
+of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+It is safest to attach them to the start of each source file to most
+effectively convey the exclusion of warranty; and each file should
+have at least the "copyright" line and a pointer to where the full
+notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the library,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/qt4/ChangeLog b/qt4/ChangeLog
new file mode 100644
index 000000000..a4617b408
--- /dev/null
+++ b/qt4/ChangeLog
@@ -0,0 +1,2 @@
+This is a placeholder ChangeLog - use `git log` instead.
+In distributed tarballs this is replaced by the output of `git log`.
diff --git a/qt4/HACKING b/qt4/HACKING
new file mode 100644
index 000000000..e728a6781
--- /dev/null
+++ b/qt4/HACKING
@@ -0,0 +1,115 @@
+Contents:
+
+• Version control
+• Coding style...
+ › ...for C++ code
+ › ...for C and Python code
+• Submitting patches
+
+
+===============================================================================
+Version control
+===============================================================================
+
+The current development version of telepathy-qt4 is available from the
+'master' branch in the git repository:
+
+ <git://anongit.freedesktop.org/telepathy/telepathy-qt4>
+ <ssh://git.freedesktop.org/git/telepathy/telepathy-qt4> (for committers)
+ <http://cgit.freedesktop.org/telepathy/telepathy-qt4/> (cgit)
+
+Stable branches are available from branches with names like
+'telepathy-qt4-0.2' in the same repository.
+
+
+===============================================================================
+Coding style
+===============================================================================
+
+For C++ code:
+-------------
+
+TelepathyQt4 uses the standard Qt4 coding style for the Qt/C++ code, as also
+followed by eg. KDELibs. The coding style is described in more detail in the
+KDE TechBase at <http://techbase.kde.org/Policies/Kdelibs_Coding_Style>; in
+short, it amounts to:
+
+ * 4 spaces for indentation (no tabs, anywhere)
+ * javaCase for variables and functions (incl. members), FullCamelCase for types
+ * No abbreviations, except for 100% standard ones like regex, refcount, etc.
+ * Type &var and Type *var, not Type& var and Type* var for pointers and refs
+ * Use only one empty line to separate both groups of related statements AND
+ function & class bodies
+ * Use a space after each keyword but none after opening parens (if (true))
+ * No spaces between the function name and the parens for the parameter list
+ * Surround binary operators with spaces, but don't put spaces between a unary
+ operator and their operand
+ * For function bodies, put the opening curly brace on its own line. For
+ everything else, put the opening curly brace in the same line as the
+ statement (if, for, etc.) associated with it.
+ * Use curly braces even for single-line bodies of conditional statements and
+ loops
+ * Prefer static_cast, const_cast etc over C-style casts
+ * Wrap long lines to 100 characters at most
+ * Use foreach, emit instead of Q_FOREACH/Q_EMIT
+ * Don't write long (> ~5 lines) inline methods unless they are needed because of
+ eg. template
+ * Don't use references to QObjects
+ * Destroy transient objects such as method call watchers as soon as possible
+ rather than accumulating them to long-lived parent objects
+ * Documentation should be in the source file instead of the header file
+ * Public xxxInterface methods' definitions should be added to the end of the
+ public methods declaration.
+ * Always add friend struct Private declaration on classes that have Private
+ structure.
+ * Always add Q_DISABLE_COPY(xxx) to classes that cannot be copied.
+ * Q_DISABLE_COPY(xxx) declaration must be placed on the top of the class
+ definition, before the public keyword.
+ * Methods should be ordered as follows:
+ public
+ public Q_SLOTS
+ Q_SIGNALS
+ protected
+ protected Q_SLOTS
+ private Q_SLOTS
+ private
+ * friend class xxx definitions must be placed right below the private keyword.
+ * All exported classes should use TELEPATHY_QT4_EXPORT macro as following:
+ class TELEPATHY_QT4_EXPORT xxx { ... };
+ * Every issue in APIs which can (or will) only be fixed later should be marked
+ with a doxygen \todo and have a corresponding fd.o bug filed and mentioned in
+ the \todo annotation
+
+
+For C and Python code:
+----------------------
+
+Parts of the regression tests — namely, those parts in tests/lib/glib/ — are
+taken from telepathy-glib, and are written in C; the majority of the code
+generation tools — in tools/ — are written in Python. Please follow
+<http://telepathy.freedesktop.org/wiki/Style> for these.
+
+
+===============================================================================
+Submitting patches
+===============================================================================
+
+Patches should be made as (preferably) git branches or (last resort) -uNr diffs
+against upstream git master, as found at:
+
+ git://anongit.freedesktop.org/telepathy/telepathy-qt4
+ ssh://git.freedesktop.org/git/telepathy/telepathy-qt4 (for committers)
+ http://cgit.freedesktop.org/telepathy/telepathy-qt4/ (cgit)
+
+Patches can be announced at the freedesktop.org bugzilla, using the product
+"Telepathy" and component "tp-qt4":
+
+ https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=tp-qt4
+
+If submitting a Git branch, please set the URL field of the bug to point to
+your Git branch. Regardless of whether you are referring to a Git branch or
+attaching patches, please specify the "patch" keyword on the bug.
+
+For details on the code review procedure, see:
+
+ http://telepathy.freedesktop.org/wiki/Review%20Procedure
diff --git a/qt4/NEWS b/qt4/NEWS
new file mode 100644
index 000000000..c3aa3d682
--- /dev/null
+++ b/qt4/NEWS
@@ -0,0 +1,1478 @@
+telepathy-qt 0.9.0 (UNRELEASED)
+================================
+
+The "..." release.
+
+This release begins the new 0.9 development series for feature additions
+following the 0.8 stable release series.
+Starting with this release the project is renamed to telepathy-qt (TelepathyQt) and
+Qt5 support is added alongside Qt4 support and for that 0.9 will be
+API (see changes below) and ABI incompatible with previous versions.
+
+API changes:
+ * ...
+
+Enhancements:
+ * fd.o #35084: The StreamTubeClient and StreamTubeServer classes have been
+ added to allow implementing Telepathy Stream Tube connectivity for
+ applications without having to worry about the channel dispatching details
+ * Contact::refreshInfo() requests are now batched together on D-Bus
+
+Fixes:
+ * Our TODO process being completely out of date. Trying to use doxygen \todo
+ annotations from now on
+ * FeatureRoster is set on Tp::Connection even for roster-less accounts.
+
+telepathy-qt4 0.7.3 (2011-10-07)
+================================
+
+The "a must" release.
+
+Enhancements:
+ * Added HandleTypeRoom StreamTubes to ChannelClassSpec
+ * It is now possible to capture and redirect the library debug output via
+ setting a DebugCallback using the Tp::setDebugCallback function
+ * The ChannelClassSpec implementation has been optimized, which yields
+ ChannelFactory setup and consequently application startup speedups
+ * StreamTubeChannel now ensures connectionClosed() is emitted for all
+ connections on a closing tube
+ * Add Presence/PresenceSpec operator== support
+ * Add Presence/PresenceSpec operator!= support (Rohan Garg)
+ * Add Authentication.TLSCertificate to the set of generated interfaces
+ * Improve file transfer examples to properly use the ChannelDispatcher
+
+Fixes:
+ * Ensure that the proper Channel Dispatcher methods are being called when
+ creating/ensuring channels.
+ * Prevent PendingContacts crashes in corner cases where contacts are still
+ being built when a Connection is destroyed. Note that this affects which
+ object is returned by PendingContacts::object() - we've now made it explicit
+ that object() shouldn't be used externally as we make no guarantees on what
+ object it returns, by deprecating it. It will be made protected: in the next
+ API break
+ * Passing a non-empty service param to
+ ChannelClassSpec::{incoming,outgoing}StreamTube() messing up the shared
+ instance as used by e.g. ChannelFactory
+ * Connections not being removed from StreamTubeChannel::connections() when they
+ are closed, and newConnection() and connectionClosed() events getting
+ reordered
+ * fd.o# 40655 - ChannelDispatchOperation::claim() cannot be used by Approvers
+ to handle channels
+ * Codegen is now able to utilize types with D-Bus signature aay, aas and aav
+ * Account/Connection/Channel now properly check if FeatureCore is ready on
+ accessors
+ * Readded erroneously removed protected API to StreamTubeChannel, which
+ restores full ABI backwards compatibility.
+
+telepathy-qt4 0.7.2 (2011-08-04)
+================================
+
+The "Stage 0 Tune Up" release.
+
+Enhancements:
+ * Lots of additions and corrections to API documentation
+ * Code generator now produces nicer output for member-ref, dbus-ref and
+ rationale elements in docstrings
+ * The stable D-Bus interfaces Account.Interface.Storage, Channel.Type.DBusTube
+ and Channel.Interface.Destroyable have been added to generated bindings
+
+Fixes:
+ * Tp::IncomingStreamTubeChannel always reports an empty socket address to
+ connect to for accepted Tubes - and various other StreamTube fixes thanks to
+ now having unit tests for them
+
+telepathy-qt4 0.7.1 (2011-06-09)
+================================
+
+The "where is the file?" release.
+
+Enhancements:
+ * fd.o #37034: The URI property can now be set when requesting file transfers,
+ and can be read by the Handler (and any Observers) from
+ Tp::FileTransferChannel (Daniele E. Domenichelli)
+ * Improved tests readability by moving common code into helper classes,
+ macros, etc, in preparation for increasing the test coverage
+
+Fixes:
+ * Fixed documentation for Contact::publishStateChanged()
+ (Daniele E. Domenichelli)
+ * Properly crosslink our doxygen docs to Qt documentation on fresh builds
+ * Properly generate docs for auto generated classes
+
+telepathy-qt4 0.7.0 (2011-06-01)
+================================
+
+The "Doctor Love knew this so he made another great invention just for the
+lonely you!" release.
+
+This release begins the new 0.7 development series series for feature additions
+following the 0.6 stable release series. Both 0.6 and 0.7 will continue to be
+backwards compatible API and ABI wise with the earlier 0.5 development series.
+
+Enhancements:
+ * fd.o #35341: Use the new ContactBlocking D-Bus interface for better
+ performance in Tp::ContactManager (George Kiagiadakis)
+ * ReceivedMessage: Add accessors for retrieving delivery report information,
+ sender nickname and superseded message token
+ * Crosslink our doxygen docs to Qt documentation
+
+Fixes:
+ * Do not close channels created using the request and handle API if MC
+ restarts
+ * Pass new value correctly as variant in generated binding setProperty* methods
+ * Properly initialize Connection account balance members
+ * fd.o #36881 - Wrong documentation for Tp::Account::connectionStatusChanged
+ (and a bunch of other parts of the API)
+ * fd.o #31769 - The documentation of TextChannel's features is not very helpful
+ * Skip docs generation for internal OptionalInterfaceCache
+
+telepathy-qt4 0.5.16 (2011-05-01)
+=================================
+
+The "A brown paper bagful of Easter Eggs" release.
+
+Enhancements:
+ * fd.o #36526: Tp::Channel has now gained targetID() and targetContact()
+ accessors
+ * fd.o #35421: Update to spec 0.22.0:
+ - Added auto generated class for Conn.ContactBlocking
+ - Added Connection::ErrorDetails accessors for ConnectionError details
+ server-message, user-requested, expected-hostname and certificate-hostname
+ * Add support for Conn.SimplePresence.MaximumStatusMessageLength
+ * Add Features operator|(Features, Feature)
+ * ContactManager state now only advances to Success when both the roster and
+ the roster groups have been downloaded, if requested
+ * New class SimpleObserver that can be used to observe arbitrary channels on a
+ given Account.
+ * fd.o #33525 - Helper class(es) for observing calls
+ - New class SimpleCallObserver class which makes it easy to follow all
+ StreamedMedia communication on a given Account.
+
+Fixes:
+ * fd.o #35633: Contact features being erased after further contact upgrades,
+ e.g. from ContactFactory used together with Connection::FeatureRosterGroups
+ * Contact::groups() and ContactManager::groupContacts() not populated correctly
+ when using CMs with new-style ContactGroups D-Bus API
+ * Timeout in some CMs when introspecting contact list channels
+ * Crashes with Connection::FeatureSelfContact enabled (Manifested as an assert
+ for inFlightFeatures or pendingFeatures being hit)
+ * Crashes if a connection disconnects while FeatureRosterGroups is being
+ introspected (SIGSEGV in PendingOperation from ContactManager::Roster)
+ * Added correctly named pretty headers for the
+ Conn.ClientTypes/ContactGroups/ContactList/Saving interfaces
+ * Odd build system failures from feature checks which were anyway redundant due
+ to our recently bumped Qt 4.6+ dependency - checks now removed
+
+telepathy-qt4 0.5.15 (2011-04-12)
+=================================
+
+The "How to remove a hole from a set of holes" release.
+
+Enhancements:
+ * Performance improvements for introspection (becomeReady() latency)
+ * Some documentation improvements.
+
+Fixes:
+ * Incorrect scoping for Initial{Audio,Video} properties in Account channel
+ request methods
+ * Possible deadlock on connection status changes when FeatureConnected is
+ requested on a Connection
+ * Crash opportunities when introspecting a Connection which disappears in the
+ process (e.g. due to a network error or a CM crash)
+
+telepathy-qt4 0.5.14 (2011-04-05)
+=================================
+
+The "Now succeeds at failing!" release.
+
+The Qt dependency has been bumped to >= 4.6.0 for all future releases.
+
+Enhancements:
+ * AccountManager will now retry introspection 5 times with a 3 seconds interval
+ (or immediately if error is Timeout) if the introspection failed and
+ becomeReady() will fail if there is still an error after all tries.
+ * AccountManager will no more signal newAccount if the initial
+ introspection failed.
+ * ConnectionCapabilities and ContactCapabilities are now subclassable
+
+Fixes:
+ * Regression in 0.5.13 causing PendingReadys to not fail even if the object
+ being made ready disappears
+ * Discrepancy between the spec and implementation on initially assumed
+ non-locally-requested StreamedMediaChannel stream direction
+ * Reporting capabilities of offline accounts incorrectly because of a .manager
+ file parsing bug
+
+telepathy-qt4 0.5.13 (2011-03-23)
+=================================
+
+The "what is my id?" release.
+
+Enhancements:
+ * Added example to send messages using ContactMessenger.
+
+Fixes:
+ * ContactMessenger: Do not crash if an error occurred normalizing the contact
+ identifier but the ContactManager::contactsForIdentifiers() succeeded.
+ * Multiple parallel invalidated() signal connections created between DBusProxy
+ and ReadinessHelper
+
+telepathy-qt4 0.5.12 (2011-03-18)
+=================================
+
+The "Can I use the IM framework for, like, sending and receiving messages" release.
+
+Enhancements:
+ * fd.o #28753: Added SimpleTextObserver class which makes it easy to follow all
+ text communication on a given Account
+ * fd.o #35321: Added ContactMessenger class for easily sending and receiving
+ text messages with a particular contact
+
+Fixes:
+ * The Request & Handle API spuriously failing with SERVICE_CONFUSED because of
+ a race condition with introspecting the proxies
+
+telepathy-qt4 0.5.11 (2011-03-09)
+=================================
+
+The "more contacts?" release.
+
+Enhancements:
+ * fd.o#33121 - Bind Connection.ContactList.ContactsChangedWithID.
+
+Fixes:
+ * ContactManager will now create contact objects even if everything fails but
+ we have the contact handle/id and the connection has ImmortalHandles.
+ * Ensure FeatureRoster is ready before setting ContactManager state to
+ success even in fallback mode.
+
+telepathy-qt4 0.5.10 (2011-03-09)
+=================================
+
+The "am I connected?" release.
+
+Enhancements:
+ * Added Connection::FeatureConnected to help applications that only care about
+ connected connections. Setting this feature on the ConnectionFactory used
+ will make sure all connections signalled by the library are connected.
+
+Fixes:
+ * Ensure FeatureRoster is ready before setting ContactManager state to
+ success.
+
+telepathy-qt4 0.5.9 (2011-03-07)
+=================================
+
+The "planned engineering works for the last year" release.
+
+Enhancements:
+ * fd.o#28367 - Added High-level API for StreamTube channels
+ * fd.o#34228 - Added API on Account for requesting Channels and handling them
+ yourself, implemented properly using the Channel Dispatcher service
+ * Account::allowedPresenceStatuses() now has fallbacks to include "available"
+ and "offline" when that makes sense
+ * Removed some more private symbols from the shared library, resulting in
+ slightly faster load times
+ * Deprecated ConnectionCapabilities contact search related methods with
+ singular names and added plural versions of them, for API consistency.
+ * Contacts publish/subscription state updates for removed contacts are only
+ signalled after the ContactManager::allKnownContactsChanged signal is
+ emitted.
+
+Fixes:
+ * Redundant Contact::avatarDataChanged() emissions
+ * Connection::becomeReady() never finishing in state Connecting, which for
+ example prevents handling ServerAuthentication channels. NOTE: any code
+ incorrectly relying on the old buggy behavior of becomeReady() only finishing
+ once the connection goes Connected or Disconnected may need adjustment.
+ * Linking errors referencing the QtXml library
+ * Spec-incompliant building of Client names when uniquifying is requested from
+ ClientRegistrar
+ * Sensitive data in Account parameters being included in debug logs
+
+telepathy-qt4 0.5.8 (2011-02-22)
+=================================
+
+The "where are my contacts?" release.
+
+Enhancements:
+ * Account won't try to build a Connection object anymore if the connection
+ object path hasn't changed.
+ * Connection::FeatureRoster will now fail introspection if ContactList
+ interface is not supported, neither fallback roster channels.
+ * ContactManager now has a state() accessor and a corresponding stateChanged()
+ signal to keep track of the progress made in retrieving the contact list.
+ * Made Profile/Account/ContactManager debug output a bit cleaner.
+ * Cleaned up debug classes by removing unused code.
+
+Fixes:
+ * Properly replace "_" with "-" on Account::protocolName().
+
+telepathy-qt4 0.5.7 (2011-02-15)
+=================================
+
+The "fit for galoshes" release.
+
+Enhancements:
+ * Make the debug subsystem as no-op as possible when debugging is disabled at
+ runtime with Tp::enableDebug(false) and Tp::enableWarnings(false).
+ * fd.o#33123 - Bind Protocol.Avatars.
+ * fd.o#33124 - Bind Protocol.Presence.
+ * fd.o#33116 - Added support for passing hints to channel requests, also making
+ channel requests marginally more efficient CPU and memory wise in the
+ process.
+ * fd.o#33117 - Added support for extracting hints from ChannelRequests.
+ * fd.o#33117 - Added support for ChannelRequests reporting the channel created
+ for them on success, for further observation.
+
+Fixes:
+ * fd.o#34131 - Writing avatar cache into $HOME with scratchbox.
+ * Unstable generated future-* headers being installed (although nothing
+ declared in them was ever exported in the library).
+ * Possible crashes in Channel internal updateContacts function when just the
+ self handle changed.
+
+telepathy-qt4 0.5.6 (2011-01-27)
+=================================
+
+The "accept/decline" release.
+
+Enhancements:
+ * Update to spec 0.21.8.
+ - Added auto generated classes for Account.Addressing,
+ Channel.ServerAuthentication, Channel.SASLAuthentication,
+ Channel.Securable and Conn.MainNotification.
+
+Fixes:
+ * Properly ignore protocol with invalid names.
+ * Properly escape protocol name with "-" when constructing protocol object
+ path.
+ * Properly link against QtXml.
+ * Properly emit presencePublicationRequested if the contact current publish
+ state changes to PresenceStateAsk.
+ * Added missing fancy-headers for generated classes.
+
+telepathy-qt4 0.5.5 (2011-01-25)
+=================================
+
+The "I wish I had less contacts" release.
+
+Enhancements:
+ * CapabilitiesBase: Added method to check if file transfer is supported.
+
+Fixes:
+ * Contact list contacts are now guaranteed to contain the features set on
+ ContactFactory.
+ * Contact list groups are now automatically reintrospected when needed.
+ * Another attempt to fix a crash when contacts are removed from contact list
+ while the introspection is still running.
+ * fd.o#33457 - tp-qt4 uses non-atomic file write in avatar cache.
+
+telepathy-qt4 0.5.4 (2011-01-20)
+=================================
+
+The "the shower of golden paper bags" release.
+
+Enhancements:
+ * Presence publication requests are now reported more sensibly by the
+ ContactManager::presencePublicationRequested(Contacts) signal, with the
+ per-contact request message, if any, being in Contact::publishStateMessage()
+
+Fixes:
+ * ContactManager not emitting presencePublicationRequested if the request
+ message is empty.
+ * ContactManager sometimes crashing when contacts are removed using the new
+ ContactList interface.
+
+telepathy-qt4 0.5.3 (2011-01-17)
+=================================
+
+The "contact factorization" release.
+
+Enhancements:
+ * Added Tp::Contact::requestAvatarData()
+ * Added Tp::Contact::isContactInfoKnown()
+ * fd.o#32999 - operator< and qHash are not implemented for
+ Tp::ProtocolParameter.
+ * fd.o#33119 - Bind Connection.HasImmortalHandles.
+ * ContactFactory is no more a stub class. The features set in the
+ ContactFactory will be enabled in all contacts created by ContactManager and
+ the classes using it (Connection, Channel, etc).
+
+Fixes:
+ * Build failures on systems using GNU gold or the
+ --no-add-needed/--no-copy-dt-needed-entries linker flags
+
+telepathy-qt4 0.5.2 (2011-01-03)
+=================================
+
+The "I'm not subscribed now, right? WRONG" release.
+
+Enhancements:
+ * Added Or/NotFilter classes making it more flexible to use the Filter API.
+ * Channel invalidation reasons now more accurately describe what happened,
+ including a new error TP_QT4_ERROR_ORPHANED for the corresponding Connection
+ getting invalidated from whichever reason.
+ * Added support for ContactList and ContactGroups interfaces improving
+ performance of Connection::FeatureRoster/Groups when the CM supports the new
+ interfaces.
+ * ContactManager PendingOperations finish at consistent times wrt actual state
+ changes when used with a CM sporting the new ContactList/ContactGroups
+ interfaces
+ * Deprecated Contact/ContactManager signals carrying a
+ Channel::GroupMemberChangeDetails param for publish/subscription/block state
+ changes and added new signals that should be used in new code.
+ * fd.o #31464 - Added Channel::requestLeave() for leaving channels more
+ gracefully than closing them; StreamedMediaChannel::hangupCall now uses that
+
+Fixes:
+ * Properly install TelepathyQt4/ConnectionManagerLowLevel.
+ * A race condition causing proxies to be needlessly dropped from the factory
+ cache and hence new proxies built for a future request, and eventually
+ hitting an assert in onProxyInvalidated as a result
+ * Memory leaks when using Connection::FeatureRoster/RosterGroups where the
+ connection and roster channels were leaking.
+ * fd.o#29728 - ContactManager::addGroup and removeGroup are confusing/broken.
+ * fd.o#29735 - Roster API semantics are error / race condition prone.
+
+telepathy-qt4 0.5.1 (2010-12-08)
+=================================
+
+The "lazily evaluated birth" release.
+
+Fixes:
+ * fd.o #29731: Memory leaks reported by make check-valgrind
+ * Crash using a dangling (const!) iterator when a channel is removed from a
+ conference
+ * Crash when the an object path for an Account in an account set is quickly
+ reused after it's removed
+ * Emitting redundant {local,remote}SendingStateChanged signals from
+ StreamedMediaChannel
+ * ChannelDispatchOperation::channelLost failing to include Tp:: in the signal
+ arg type names, making it a tad hard to connect to
+
+telepathy-qt4 0.5.0 (2010-11-16)
+=================================
+
+The "new era of breakage" release.
+
+This release IS NOT API/ABI COMPATIBLE WITH EARLIER RELEASES. Further releases
+in the 0.5 development series will however be compatible with this release, as
+will any releases in a 0.6 stable series.
+
+For enhanced compatibility versions from 0.5 and earlier series can be parallel
+installed, with 0.5 bumping the .so major version to .so.1. However, the
+development headers and pkgconfig file can't be parallel installed.
+
+Enhancements:
+ * fd.o #28793 - It is no longer necessary to keep the proxy / other object you
+ get a PendingOperation from manually referenced until the operation is
+ finished
+ * fd.o #28797 - generated client code (AbstractInterface subclasses) now allows
+ setting a non-default timeout for D-Bus method calls
+ * fd.o #29486 - Bare variant maps and string lists are no longer exposed in the
+ API unless absolutely necessary; instead they have wrapper classes with
+ easy access to well-known keys and values. Among other consequences, this
+ means AbstractClient implementations NEED TO CHANGE their method
+ implementation signatures to accept the wrapper classes instead of bare
+ QVariantMaps.
+ * Setting automatic/requested presence in Account now uses Tp::Presence
+ * Cleaned up the API and internal code by removing all deprecated classes,
+ methods and signals.
+ * Added Tp::Object intermediate base class for uniform QObject property change
+ notification. Connection, Channel and Contact will be propertified using it
+ in the future.
+ * Enumeration and flag types generated from the specification can now be used
+ in API without endangering ABI stability due to added padding members,
+ enhancing type safety in numerous instances where bare uints used to be
+ returned / taken as a parameter.
+ * Moved low-level functionality which shouldn't be used when using a full
+ Telepathy setup with a Mission Control service (Account Manager + Channel
+ Dispatcher) available from Connection and ConnectionManager to Lowlevel
+ classes, which can be accessed only if TP_QT4_ENABLE_LOWLEVEL_API is #defined
+ through a lowlevel() accessor on them
+ * Bare pointers are no longer returned from the API, instead either SharedPtr
+ or Qt implicitly shared handle classes are used (applicable to
+ Connection/ContactCapabilities, ProtocolInfo, etc)
+ * Tp::Contact is now a RefCounted and uses Tp::Feature like the rest of the
+ library.
+ * ClientRegistrar no longer guarantees that a singleton instance is returned
+ for create(). In particular, this relaxation allows us to implement an API in
+ the future which constructs a ClientRegistrar behind the scenes to be able to
+ request and handle channels without manually implementing an
+ AbstractClientHandler.
+ * The Filter API is now more future-proof, with the assumption that the filter
+ chain is ANDed together dropped. It will be possible to combine filters using
+ And/Or/Not filter combiners in the future, of which And is included now.
+ * The SharedPtr API has been improved. In particular, it's now safe to use it
+ as a QMap/QHash key and more difficult to use it incorrectly as a boolean or
+ an integer. The redundant WeakPtr class has been removed - use QWeakPointer
+ instead (which works with SharedPtr for all QObject classes).
+ * Tp::Contact friend list state change signals now have a details argument
+ (which will contain e.g. the request message for publishState() == Ask when
+ somebody adds you to their contact list, for example). This means that
+ code connecting to them NEEDS TO BE CHANGED.
+ * Account::haveConnection(Changed) and friends is now the less confusing
+ Account::connection(Changed) pair
+ * AccountManager filtering methods returning an AccountSet now have more
+ descriptive names without the Set suffix
+ * Account/Connection/Channel subclasses now can override the "core" feature
+ implied by isReady() and becomeReady(). In particular, this means that e.g.
+ ContactSearchChannel::isReady() with no arguments will only return true if
+ ContactSearchChannel::FeatureCore is ready in addition to
+ Channel::FeatureCore.
+ * StreamedMediaChannel is once again just for Channel.Type.StreamedMedia
+ channels, with the intermediate Call.DRAFT support and API removed. When Call
+ is undrafted, a new CallChannel will be added and StreamedMediaChannel
+ deprecated.
+ * Similarly, hacky Conference.DRAFT support IS REMOVED. Only the final
+ Conference interface is supported from now on.
+ * Everything that used to be called just Audio/Video/MediaCall etc now has
+ StreamedMedia in the name, to not conflict with future Call API.
+
+Fixes:
+ * fd.o #27204 - Codegen erroneously uses enums' value-prefix as the name for
+ the C++ enum
+ * fd.o #29998 - Connecting to signal Tp::TextChannel::chatStateChanged needs
+ typedef if not done in Tp namespace
+ * fd.o #27795 - Problems using Tp::SharedPtr as a key in QMap
+ * ChannelRequest not becoming ready successfully when there is no Account
+ specified
+ * ContactSearchChannel not initializing its private members correctly
+ * Compile errors with QT_STRICT_ITERATORS
+ * Some MSVC++ compilation issues (though there still are likely some remaining)
+ * ContactManager::allKnownContacts not picking up changes from the "stored"
+ list
+
+telepathy-qt4 0.3.14 (2010-11-05)
+=================================
+
+The "O HAI MY NAME IS CONFERENCE" release.
+
+Enhancements:
+* fd.o #30098 - Added an asynchronous property request API for generated
+ low-level proxies
+* Added high-level class for SimplePresence and changed Contact to use it,
+ deprecating the old methods using SimplePresence directly.
+* Added signals deprecation support to Contact.
+* Deprecated StreamedMediaChannel methods that only make sense when used with
+ Call.DRAFT channels (we're going to drop support for that particular draft in
+ 0.5.0)
+* Deprecated all optional interface convenience methods.
+ The methods inherited from OptionalInterfaceFactory should be used directly
+ instead if access to low-level proxies is needed.
+* Add unnamed (anonymous, TargetHandleType == None) variants for text chats and
+ calls to ChannelClassSpec
+* Register all non-QObject public classes with the Qt meta-object system, so
+ they can e.g. be stored in QVariants.
+
+Fixes:
+* Unnamed text and StreamedMedia calls not included in the ChannelFactory
+ (channel class) -> (subclass, features) mapping
+* Some RequestableChannelClassSpec and ConnectionCapabilities methods having a
+ notion of "text chat with a person WHO is a conference", which doesn't exist
+
+telepathy-qt4 0.3.13 (2010-11-01)
+=================================
+
+The "sickness won't slow us down" release.
+
+Enhancements:
+* Added TP_QT4_ prefixed versions of all generated string constants marked as
+ QLatin1String for less verbose usage with Q_NO_CAST_FROM_ASCII
+* Added signals deprecation mechanism to warn when a deprecated signal is used.
+* fd.o #29451 - Make it possible to specify subclasses to use and features to
+ make ready on them in ChannelFactory
+* fd.o #29484 - RequestableChannelClass should have high-level API
+* fd.o #29486 - Add a ChannelClassSpec class for easily specifying Client
+ channel filters (and building Channel requests and ChannelFactory filters for
+ advanced usage)
+* Added more deprecated methods and remove some methods that should not be
+ deprecated. Now the library together with examples builds itself with
+ deprecation warnings enabled, to make sure no deprecate method is used
+ internally.
+* Deprecated autogenerated synchronous properties accessors/setters.
+
+Fixes:
+* fd.o #30223 - telepathy-qt4's codegen doesn't deal with tp:external-type
+ properly
+* fd.o #30923 - Fix compilation errors in 0.3.12
+ - The tp-glib version requirement in 0.3.12 was too old
+ - Compile error in the tp-glib based test library
+* fd.o #31087 - ChannelRequest immutable property extraction relying on
+ undefined method argument evaluation order -> failing on some toolchains
+* TextChannel never finishing introspection of FeatureMessageQueue with some
+ older services
+* Properly document ContactSearchChannel signals.
+* AccountManager and AccountSet getting confused and hitting asserts with
+ certain sequences of introspecting and adding/removing accounts, most
+ prominently when removing and readding an account
+* Useless PendingReady code having potential for crashes in corner-cases but
+ serving no useful purpose (now removed)
+* Code generator changes not always triggering rebuilds properly
+
+telepathy-qt4 0.3.12 (2010-10-15)
+=================================
+
+The "break is coming" release.
+
+New API:
+* Added ContactSearch high-level class.
+* Added constructors/accessors for using Channel/ContactFactory in Connection.
+* Added high-level class for ContactInfoFieldList.
+
+Enhancements:
+* Updated to spec 0.21.1:
+ - Added auto generated classes for Conn.ClientTypes/ContactGroups/ContactList/
+ PowerSaving and Chan.ServerTLSConnection.
+ - Make use of Observer.ObserverInfo.request-properties map, making
+ ChannelRequest use the immutable properties defined in the map if
+ available, avoiding unneeded introspection.
+ - Added support for Conference interface alongside Conference.DRAFT support.
+ - Added ConnectionCapabilities methods to check conference support.
+* Improved Account::capabilities() to take Profile::unsupportedChannelClasses
+ into account.
+* Improved StreamedMediaChannel test coverage to yellow (> 80%).
+
+Fixes:
+* Properly install TelepathyQt4/Farsight/global.h.
+* fd.o#30386 - Regression: StreamedMediaChannel::streamAdded is not emitted when
+ one requests a new stream using a StreamedMedia channel type.
+* Fixed MediaStream::requestDirection when using a Call channel type.
+* Un-deprecate AccountManager methods to retrieve accounts given the account
+ paths.
+* Some small documentation fixes.
+
+telepathy-qt4 0.3.11 (2010-10-04)
+=================================
+
+The "farewell hated friend" release.
+
+Enhancements:
+* fd.o #24648 - Port Telepathy-Qt4's build system from autotools to cmake,
+ resulting in faster builds. All targets supported by the previous build system
+ are still supported, and some new were added, notably individual targets for
+ unit tests and callgrind support.
+* fd.o #29672 - Support for Profiles - a way to describe different IM/VoIP
+ services by data files (e.g. Google Talk for the XMPP/Jabber protocol)
+* fd.o #29699 - ChannelRequest immutable data is now accessible even before it's
+ fully ready - this is useful for e.g. early initialization of channel context
+ in applications with the AbstractClientHandler requestNotification API
+* Some performance optimizations in ReadyObject, ReadinessHelper and Channel
+
+Fixes:
+ * Race conditions uncovered by Qt 4.7 in TestAccountBasics and TestConnRoster
+
+telepathy-qt4 0.3.10 (2010-09-16)
+=================================
+
+The {bool b; if (b == false || b == true || b == 99) { /*...*/ }} release.
+
+Enhancements:
+* fd.o #29609 - ClientRegistrar and all related classes now use Factories too,
+ which means if you use the factory-enabled create() variants such as
+ create(AccountManagerPtr), you can share account and connection proxies with
+ the rest of your application, and/or make them be ready by the time your
+ addDispatchOperation/handleChannels/observeChannels implementation is called
+* fd.o #29090 - Added support to filter accounts by their capabilities (whether
+ you can do room chats, voice calls, etc, using a given account)
+* Added Account::capabilities(), which automatically gets the capabilities from
+ a connected Connection if there's one, or the protocol object if there isn't
+* Generic Filter framework, currently only used for Account filtering, but in
+ the future also for filtering Contacts
+* ChannelRequest is now also using the same Account/Connection etc objects than
+ the rest of the application
+* PendingChannelRequest now holds a reference to its Account, so you don't have
+ to keep a reference to the Account in your application's scope for the channel
+ request to succeed
+* There are now variants of the ContactManager signals
+ presencePublicationRequested, groupMembersChanged and allKnownContactsChanged
+ which carry the details parameter (contains things like the message and the
+ reason)
+
+Fixes:
+* Memory leak when using PendingComposite
+* Memory leak in QDBus triggered by ClientHandlerRequestsAdaptor code (there's a
+ workaround, and the actual bug is reported as QTBUG-13562)
+* The PendingOperation returned by Connection::requestConnect() finishing even
+ if the connection wasn't connected yet
+* Hitting an assert in cornercases for calling setIntrospectedCalled twice in
+ Account Avatar introspection
+* Account::changingPresence() not being initialized correctly
+
+telepathy-qt4 0.3.9 (2010-09-10)
+================================
+
+The "THE ORIGINAL SWEAT FACTORY^W^WSAUNA MASTER" release.
+
+Enhancements:
+* fd.o #29451 - Add Factory infrastructure, enabling:
+ - sharing accounts/connections between different parts of the library better
+ (this will soon enable us to eliminate duplicate objects between
+ AbstractClient implementations and the AM)
+ - requesting that all accounts/connections/channels/contacts are always ready
+ with given features
+ - automatically constructing application-defined subclasses whenever eg. an
+ Account is constructed
+* Add finished and usable AccountFactory and ConnectionFactory APIs, and also
+ added API/ABI placeholder ChannelFactory and ContactFactory APIs which will be
+ more useful soon
+* fd.o #29606 - Use factories in AccountManager and Account for constructing
+ Accounts and Connections -> if desired features are enabled, one no longer
+ needs to make any Account::becomeReady and Connection::becomeReady calls when
+ using an AccountManager
+* fd.o #29409 - Use QDBusServiceWatcher if available, reducing wakeups
+* fd.o #20034 - Add avatar cache implementation and featureAvatarData on
+ Tp::Contact using it.
+* Made Channel::groupSelfContact() always have some contact for the user as long
+ as the channel is ready (if the group has none, the Connection one is used)
+* (Side-effect from other work) Add fallbacks to channel requests in case the
+ service doesn't provide the InitiatorHandle or the Requested property
+* Made Channel debug output a bit cleaner
+* Added Connection::ErrorDetails to represent additional information about error
+ conditions causing Connection invalidation
+* Made the Connection API guide the applications better to correct error
+ handling practices (all error handling should be done in invalidated() slots)
+* Add SharedPtr::qobjectCast(), an upcasting constructor and pointer to const
+ support
+
+Fixes:
+* A race condition which could result in Channel ignoring a member change (only
+ applicable to services with Group.MembersChangedDetailed)
+* A bug where Channel::groupMembersChanged(only removed members) isn't always
+ emitted if the local user is not a member of the group - probably means
+ signaling contacts being removed from roster groups didn't work either
+* Yet another TestConnRosterGroup race condition (freq: 6 in 20000 runs)
+* fd.o#29930 - Build error with glib 2.25
+* ReadyObjects incorrectly handling feature dependencies
+* Account/Connection/Channel readifying themselves even if not asked to
+* Handle reference management screwing up if there are multiple connections
+ online on a single CM
+* Lots of crash opportunities when a PendingOperation is underway when its
+ parent-ish object is unreferenced in an application
+
+telepathy-qt4 0.3.8 (2010-08-24)
+================================
+
+The "a weekend (and quite a bit of the Monday early hours) well spent" release.
+
+Enhancements:
+* fd.o#29395 - Update to spec 0.19.10
+* fd.o#29461 - Update to spec 0.19.11
+* fd.o#28948 - Added docs/tests/missing Qt properties to AccountSet
+* fd.o#29357 - Account::iconName() and icon() now always return sensible
+ non-empty values
+* fd.o#28819 - {Account,ConnectionManager}::protocolInfo() has been improved
+ - Recent CMs are now introspected with less D-Bus calls
+ - New API capabilities(), vcardField(), englishName(),
+ iconName() on ProtocolInfo and ManagerFile
+* fd.o#25126 - Some redundant debug output has been removed
+* fd.o#27460 - Connection now introspects recent CMs with less D-Bus traffic
+* Connection::contactManager and ::capabilities are now less of a death-trap
+ - now always are non-NULL
+ - operations fail with descriptive errors if the Connection isn't valid
+ - they used to go NULL at an indeterminate time when eg. disconnecting
+* Account::connection() object path parsing has been optimized
+
+Fixes:
+* fd.o#28947 - Account::filterAccounts doc does not properly format the example
+ code
+* fd.o#28651 - Cannot receive files using gabble 0.9
+* fd.o#29145 - AccountSet::accountRemoved is emitted for newly-created
+ non-matching accounts
+* fd.o#29699 - ChannelRequest incorrectly checks immutable properties
+* Broken iteration code in MediaStream which often led to busy-looping forever
+* (Harmless) uninitialized memory use reported by valgrind in Account internals
+
+Test suite improvements:
+* fd.o#29702 - Unit tests now execute reliably, and 10-30x faster
+* Added the script repeat-tests.sh for repeating tests to detect race conditions
+* Added a conservative 10 minute per test watchdog to detect hung-up test logic
+ - Should be plenty even for heavily loaded VM build bots, as the whole test
+ suite now executes in 2.4-2.5 seconds on my laptop
+* The StreamedMedia legacy and Future.Call tests now actually have different
+ names to be able to distinguish between them in the test logs
+* Test coverage reporting now works again; turns out we need to disable building
+ the shared library when it is enabled
+* amd64 memory use errors (pointer size != int size) calling g_object_new() in
+ tests have been fixed
+
+telepathy-qt4 0.3.7 (2010-07-12)
+================================
+
+The “not as bad as Pepsi Max” release.
+
+Enhancements:
+* fd.o#28927 - Generate code for Channel.Interface.{Anonymity,ServicePoint}
+ (wjt)
+* fd.o#28942 - Refresh HACKING and README (wjt)
+* Update to spec version 0.19.9, adding Read and Deleted members to
+ MessageSendingFlagReport, DeliveryReportingSupportFlagReceive, and
+ DeliveryStatus (wjt)
+
+Fixes:
+* fd.o#28945 - AccountManager::accountsByProtocol() returns an empty set
+* fd.o#28946 - AccountSet should indicate whether the filter used is valid
+
+telepathy-qt4 0.3.6 (2010-07-01)
+================================
+
+The "I've been thinkin' a lot today" release.
+
+New API:
+
+* Added Qt properties to Account.
+* Added filter API to AccountManager to filter accounts based on Qt properties.
+ Includes a new class AccountSet that represents a set of accounts that match a
+ certain filter and that updates automatically based on accounts Qt properties
+ changes.
+* fd.o#25035 - Add API to AccountManager to get a list of Account objects, all
+ ready.
+* fd.o#28825 - Bind Account.Service
+* fd.o#28828 - Add Qt properties to ConnectionManager
+* fd.o#28861 - Add auto generated interface and Connection accessor for
+ Connection.Cellular interface
+
+Enhancements:
+* fd.o#28302 - Sync test CM with telepathy-glib
+* fd.o#28827 - Add valgrind support to tests
+* fd.o#28850 - Update to spec 0.19.8
+
+Fixes:
+* fd.o#28489 - manager-file.py missing in tp-qt4 0.3.4 tarball
+* fd.o#28826 - KeyFile should support spaces in key names and string list params
+ that don't terminate with ;
+* fd.o#28829 - Contact should not fail if /capabilities attr is empty
+* fd.o#28830 - Channel does not parse immutable properties correctly
+* fd.o#28831 - Connection FeatureRosterGroups does not work after
+ FeatureAccountBalance support was added
+
+telepathy-qt4 0.3.5 (2010-06-21)
+================================
+
+The "Think I'll get it done yesterday" release.
+
+New API:
+* fd.o#28018: Bind Account.ChangingPresence.
+* fd.o#28552: Bind Account.ConnectionError/Details.
+
+Enhancements:
+* fd.o#28536: update to spec 0.19.7 (ConnectionError, Anonymity, ServicePoint,
+ ChatStates).
+
+Fixes:
+* Update with-session-bus.sh from telepathy-glib, fixing a bashism. (smcv)
+* Fixed coverity issues with call example.
+* Fixed AbstractClient documentation. (albanc)
+* telepathy-qt4-farsight telepathy-glib dependency is now >= 0.8.1. (albanc)
+
+telepathy-qt4 0.3.4 (2010-05-23)
+================================
+
+The "Cause time takes time, ya know" release.
+
+New API:
+* fd.o#28143: Implement Connection.Balance interface support.
+
+Fixes:
+* Fix strict QtDBus demarshalling which was causing crashes when using
+ qdbus_cast from a SocketAddressIP*. (drf)
+
+telepathy-qt4 0.3.3 (2010-05-09)
+================================
+
+The "generic fun" release.
+
+New API:
+* fd.o#27671: ContactInfo high-level API.
+
+Enhancements:
+- Added Call.Content.Remove support to StreamedMediaChannel.
+
+Fixes:
+- Fixed a leak in Connection::gotCapabilities,
+- Correctly remove object path from Account::uniqueIdentifier.
+
+telepathy-qt4 0.3.2 (2010-04-23)
+================================
+
+The "poisoned with anti-coffee" release.
+
+New API:
+* fd.o#27379: Add a new signal, allKnownContactsChanged. (drf)
+* fd.o#27677: Add Observer.Recover support.
+* Added support for retrieving contacts location.
+
+Enhancements:
+* Added example application to list all supported protocols.
+* fd.o#27670: Updated to spec 0.19.5.
+
+Fixes:
+* Fixed compilation (more specific, moc generation). The code was
+ triggering QTBUG #2151. (drf)
+* Correctly handle UTF-8 in code generator. (wjt)
+* Fixed text-chan test race condition. (drf)
+
+telepathy-qt4 0.3.1 (2010-03-30)
+================================
+
+The "it's all about coffee" release.
+
+Enhancements:
+
+* Added/Improved documentation for various classes and some minor documentation
+ style fixes.
+
+Fixes:
+* Fixed bug where StreamedMediaChannel::requestStream returned PendingOperation
+ was never finishing.
+
+telepathy-qt4 0.3.0 (2010-03-18)
+================================
+
+The "With My Own Two Hands" release.
+
+Dependencies:
+
+* Full regression tests now require telepathy-glib >= 0.10.0 (telepathy-glib
+ is still optional)
+
+New API:
+
+* Channel: Added Conference/MergeableConference/Spplitabble interfaces support.
+* StreamedMediaChannel: Added Call interface support.
+
+Enhancements:
+
+* Updated to 0.19.1 spec.
+* Better tests directories organization (complete separation of glib/python
+ specific code).
+
+Fixes:
+
+* fd.o#25422: generate code for Call draft API.
+* fd.o#26117: Add Call interface support to StreamedMediaChannel.
+* fd.o#26881: Remove the usage of QString::fromAscii.
+* fd.o#27124: Missing docs for some classes.
+* fd.o#27125: Add support to QT_NO_CAST_FROM_ASCII.
+* Fixed bug when Channel was never getting ready.
+
+telepathy-qt4 0.2.2 (2010-02-22)
+================================
+
+The "no pain, no gain" release.
+
+New API:
+
+* AbstractClientHandler: Added support to set Handler Capabilities property.
+
+Fixes:
+
+* fd.o #25659: ObserveChannels implementation might actually return immediately.
+
+telepathy-qt4 0.2.1 (2009-12-04)
+================================
+
+The "all you want, only better" release.
+
+Fixes:
+
+* fd.o #25058: reduce the scope of our workaround for Qt 4.5 bug
+ <http://qt.gitorious.org/qt/qt/merge_requests/1657>, fixing compilation
+ against Qt versions >= 4.6 beta, where this bug has been fixed (smcv)
+
+* Avoid the installed AccountManager (if any) being service-activated during
+ distcheck under some circumstances (smcv)
+
+* Compile with symbols hidden by default, explicitly export a few
+ symbols that were mistakenly not exported, and improve the code generation
+ tools to be more correct about their exports (smcv)
+
+* Improve the code-generation tools to cope with UTF-8 in the spec (wjt, smcv)
+
+* Enable Automake 1.11 silent building (./configure --enable-silent-rules
+ to enable this) (wjt)
+
+Code generation release notes:
+
+* qt4-types-gen.py and qt4-client-gen.py previously forced the generated
+ classes to be exported, in a way that's not actually correct for code outside
+ telepathy-qt4 (the TELEPATHY_QT4_EXPORT macro).
+
+ They now use a macro set by --visibility, defaulting to nothing; if you're
+ building a shared library with -fvisibility=hidden, or supporting Windows,
+ you may need to use --visibility=YOUR_LIB_EXPORT when running these scripts.
+
+ See TelepathyQt4/global.h or QtDBus/qdbusmacros.h for an example of setting
+ up such a macro (unfortunately, the only correct way to do this seems to be
+ for each shared library to define its own).
+
+telepathy-qt4 0.2.0 (2009-11-10)
+================================
+
+The "I Shot the Sheriff" release.
+
+API changes:
+
+* Connection: Changed status/statusReason/statusChanged to use enums
+ Tp::Connection::Status and ConnectionStatusReason.
+* Connection: Renamed getContactAttributes method to contactAttributes.
+
+Fixes:
+
+* fd.o#23370: Make better use of telepathy-spec 0.17.27 errors.
+* fd.o#24422: Account removal should be represented by
+ TELEPATHY_QT4_ERROR_OBJECT_REMOVED.
+
+telepathy-qt4 0.1.12 (2009-11-05)
+================================
+
+The "an enzyme that breaks down tigers" release.
+
+New API:
+
+* TextChannel: Added ChatState interface support.
+
+API changes:
+
+* TextChannel: send() methods now receive a flags parameter, so we proper
+ support delivery reports.
+
+Fixes:
+
+* pkgconfig: Added missing QtNetwork dependency.
+
+telepathy-qt4 0.1.11 (2009-10-08)
+================================
+
+The "on more to go" release.
+
+New API:
+
+* FileTransferChannel: Added methods to access FileTransfer interface
+ properties.
+* IncomingFileTransferChannel: Added specialized class for handling incoming
+ file transfers.
+* OutgoingFileTransferChannel: Added specialized class for handling outgoing
+ file transfers.
+* CapabilitiesBase: Added base class to represent contact/connection
+ capabilities.
+* ContactCapabilities: Added specialized class that inherits CapabilitiesBase to
+ represent contact capabilities.
+* ConnectionCapabilities: Added specialized class that inherits CapabilitiesBase
+ to represent connection capabilities.
+* Contact: Added ContactCapabilities interface support.
+* Connection: Added capabilities (RequestableChannelClasses) support.
+* Account: Added ensureAudio/VideoCall methods that make use of
+ InitialAudio/Video properties when creating StreamedMedia channels.
+* Channel: Added streamTubeInterface/tubeInterface methods.
+ (Patch from Abner Silva <abner.silva@collabora.co.uk>).
+* PendingVariant: Added pending operation helper class for D-Bus methods that
+ return a variant as result.
+
+API changes:
+
+* Renamed PendingVoidMethodCall to PendingVoid.
+* Changed PendingVoid/Success/Failure constructor to receive parent as last
+ parameter.
+
+Enhancements:
+
+* Added examples for handling incoming/outgoing file transfers,
+ examples/file-transfer/
+* Added C++ visibility support.
+* Updated to 0.18.0 spec.
+
+Fixes:
+
+* fd.o #24324: Account::create/ensureXXX should receive a const QDateTime & for
+ userActionTime
+* Explicitly use uint for TargetHandleType.
+
+telepathy-qt4 0.1.10 (2009-08-25)
+================================
+
+The "not yet stable" release.
+
+New API:
+
+* StreamedMediaChannel: Added Hold and DTMF interface support.
+
+Enhancements:
+
+* Moved OptionalInterfaceFactory::InterfaceSupportedChecking docs from DBusProxy
+ to OptionalInterfaceFactory.
+* Use struct Private instead of class Private for consistence.
+* Removed cli/Client from header guards.
+
+Fixes:
+
+* fd.o #20269: Channel's Contact objects should initially have no features.
+* fd.o #21335: Implement Group self-handle removal reasons.
+* fd.o #23040: Running connection managers appear twice in
+ ConnectionManager::listNames result.
+* fd.o #23282: Channel should update ReadinessHelper with the supported
+ interfaces.
+
+telepathy-qt4 0.1.9 (2009-07-23)
+================================
+
+The "never too late" release.
+
+New API:
+
+* OptionalInterfaceFactory: Added methods interfaces and optionalInterface
+ and removed duplicated code in all OptionalInterfaceFactory subclasses.
+* Added ContactManager allKnownGroups, addGroup, removeGroup, groupContacts,
+ addContactsToGroup and removeContactsFromGroup methods.
+* Added ContactManager groupAdded, groupRemoved and groupMembersChanged signals.
+* Added Contact groups, addToGroup and removeFromGroup methods.
+* Added Contact addedToGroup and removedFromGroup signals.
+
+API changes:
+
+* Changed ProtocolParameter requiredForRegistration method to
+ isRequiredForRegistration to make it consistent with other bool returning
+ getters.
+
+Enhancements:
+
+* Changed all classes to follow coding-style.
+* Moved documentation to source file for all classes.
+* Standardize class definition in all classes:
+ * Moved public xxxInterface methods definition to the end of the public
+ methods declaration.
+ * Added friend struct Private declaration.
+ * Added Q_DISABLE_COPY(xxx) to all classes that can not be copied.
+ * Moved Q_DISABLE_COPY(xxx) declaration to the top of the class definition,
+ before the public keyword.
+ * Reorder public, protected, SIGNALS declaration as follows:
+ public
+ public Q_SLOTS
+ Q_SIGNALS
+ protected
+ protected Q_SLOTS
+ private Q_SLOTS
+ private
+ * Moved friend class xxx definitions to be placed right below private keyword
+* ChannelDispatchOperation: Emit invalidated with
+ TELEPATHY_QT4_ERROR_OBJECT_REMOVED when ChannelDispatchOperation.Finished is
+ received.
+* Added/Improved some documentation.
+
+Fixes:
+
+* ClientApproverAdaptor: Use the dbus fully qualified name to get the connection
+ property (Patch from George Kiagiadakis <kiagiadakis.george@gmail.com>).
+* Fixed bug 20268: Connection's selfContact object should initially have no
+ features.
+* Fixed bug 20080: KeyFile: ";" as a list may be mis-parsed.
+* Fixed bug 20082: KeyFile: double (and other types?) not correctly tested.
+* Fixed bug 20353: No d-pointer in Channel::GroupMemberChangeDetails.
+* Fixed bug 20033: Contact / ContactList Group integration.
+
+telepathy-qt4 0.1.8 (2009-06-16)
+================================
+
+The "Every Good Boy Deserves Frontalot" release.
+
+New API:
+
+* Added PendingChannelRequest class to be used when requesting channels using
+ ChannelDispatcher through Account.
+* Added Account methods to request/create channels using ChannelDispatcher
+ (ensureTextChat, ensureTextChatroom, ensureMediaCall, createChannel and
+ ensureChannel)
+
+Enhancements:
+
+* Updated to telepathy-spec 0.17.26
+
+Fixes:
+
+* ChannelDispatchOperation: Read Channels property instead of incorrectly
+ reading ChannelDetailsList (Patch from George Kiagiadakis
+ <kiagiadakis.george@gmail.com>).
+
+telepathy-qt4 0.1.7 (2009-06-02)
+================================
+
+The "approver" release.
+
+New API:
+
+* Added Client Approver support.
+* Added ChannelDispatchOperation high-level class.
+
+Bugfixes:
+
+* Fixed bug 21988: Channel does not work properly if the parent
+ connection object is not ready
+* Fixed bug 21993: TextChannel does not become ready until first message is
+ received if FeatureMessageQueue is enabled.
+
+telepathy-qt4 0.1.6 (2009-05-28)
+================================
+
+The "So hot, I have a fever" release.
+
+New API:
+
+* Added Channel::immutableProperties public method.
+
+Enhancements:
+
+* Added ChannelFactory, internal class to create channels based on
+ their types.
+* PendingChannel: Use ChannelFactory to create channels.
+
+Bugfixes:
+
+* Proper define AbstractClientPtr.
+* ClientRegistrar: Fixed Observer adaptor introspection data.
+* ClientRegistrar: Use ChannelFactory to create Channels.
+
+telepathy-qt4 0.1.5 (2009-05-19)
+================================
+
+The "do not look at the conductor" release.
+
+New API:
+
+* Added Client support (Handler, Observer).
+ * Added ClientRegistrar, class responsible for registering clients and proper
+ exporting their D-Bus interfaces.
+ * Added AbstractClientObserver, AbstractClientApprover (skeleton) and
+ AbstractClientHandler. Clients should inherit one or some combination of
+ these, by using multiple inheritance, and register themselves using
+ ClientRegistrar::registerClient() in order to become a Client.
+
+* Added ChannelRequest high-level class.
+
+telepathy-qt4 0.1.4 (2009-05-08)
+================================
+
+The "global military-industrial complex is subsidising your iPod" release.
+
+Dependencies:
+
+* Creating accounts, and possibly updating their parameters, now requires an
+ AccountManager implementing telepathy-spec 0.17.24, such as
+ telepathy-mission-control >= 5.0.beta70 (in particular, beta 69 won't work,
+ and the KWallet-based account manager will also need updating)
+
+API changes:
+
+* Renamed SharedData header file to RefCounted to follow class name.
+
+New API:
+
+* Update to telepathy-spec 0.17.24, breaking some D-Bus APIs:
+ * fd.o #21619: Account::updateParameters() returns a PendingStringList of
+ the parameters that won't be changed until reconnection takes place
+ * Account::reconnect() added (newer MC versions don't violate telepathy-spec
+ by reconnecting automatically when parameters are changed)
+ * AccountManager::supportedAccountProperties() added
+ * AccountManager::createAccount() takes an optional dict of properties
+ (the valid keys are in supportedAccountProperties())
+
+* Enhance PendingStringList to have a constructor from a QDBusPendingCall
+
+Bugfixes:
+
+* Don't try to run Python tests unless we have the gobject module (the tests
+ use it)
+
+telepathy-qt4 0.1.3 (2009-04-22)
+================================
+
+The "what are you scared of?" release.
+
+Dependencies:
+
+* Full regression tests now require telepathy-glib >= 0.7.28 (telepathy-glib
+ is still optional)
+
+API changes:
+
+* Namespace simplification: Telepathy::Client::Channel (etc.) are now
+ Tp::Channel, similar to telepathy-glib's TpChannel. Auto-generated client
+ classes like Telepathy::Client::ChannelInterface are now
+ Tp::Client::ChannelInterface.
+
+* AccountManager, Account, ConnectionManager, Connection, Channel and Channel
+ subclasses now inherit from Tp::RefCounted and are used together with
+ Tp::SharedPtr/Tp::WeakPtr, shared and weak pointer classes using ideas from
+ Qt, glibmm, Boost and WebKit. The constructor is now protected (in order to
+ support custom classes) and a public create method that returns a SharedPtr
+ was added. This is an attempt to avoid memory leaks as much as possible, see
+ http://lists.freedesktop.org/archives/telepathy/2009-March/003218.html for
+ more details.
+
+* Instead of forward-declaring Telepathy::Client::Channel and using
+ a variable of type Telepathy::Client::Channel *, you should now include
+ <TelepathyQt4/Types> and use either Tp::ChannelPtr, which is a
+ reference-counted shared pointer, or Tp::WeakPtr<Tp::Channel>, which is the
+ weak counterpart.
+* Header simplification: the public headers now look like
+ <TelepathyQt4/Channel>, i.e. without the Client subdirectory.
+
+* PendingHandles now finish successfully on non fatal errors (InvalidArgument,
+ InvalidHandle, NotAvailable). PendingHandles::invalidNames/invalidHandles
+ should be used to check if a non-fatal error occurred and some handle could
+ not be acquired.
+
+Enhancements:
+
+* Updated to telepathy-spec 0.17.23
+
+* TelepathyQt4Farsight is a new mini-library with glue code to connect
+ telepathy-farsight to Telepathy-Qt4. Handlers for streamed media channels
+ with media signalling can #include <TelepathyQt4/Farsight/Channel> and pass
+ their Tp::StreamedMediaChannel to Tp::createFarsightChannel, then hook up
+ the resulting TfChannel to a GStreamer pipeline of their choice.
+
+* StreamedMediaChannel has a new handlerStreamingRequired method so you can
+ check whether the handler needs to carry out the streaming
+
+* fd.o #21336: Channels now indicate whether a message is expected when
+ doing various Group operations
+
+* fd.o #21337: Account supports the new HasBeenOnline property
+
+Fixes:
+
+* fd.o #20583: Contact objects don't work without the Contacts interface.
+
+* fd.o #20584: Contact object creation doesn't survive bad IDs or handles.
+
+telepathy-qt4 0.1.2 (2009-03-23)
+================================
+
+The "robotic automatic hoover" release.
+
+Dependencies:
+
+* Full regression tests now require telepathy-glib >= 0.7.27 (telepathy-glib
+ is still optional)
+
+* telepathy-farsight >= 0.0.4 is a new optional dependency
+
+API changes:
+
+* AccountManager, Account, ConnectionManager, Connection, Channel
+ now inherit QSharedData and are used together with
+ QExplicitlySharedDataPointer.
+ This is needed so we can create shared pointers based on the object itself,
+ instead of doing hacks to find the shared pointer related to a given object.
+ See http://lists.freedesktop.org/archives/telepathy/2009-March/003168.html for
+ more details.
+
+* Channel Features are now Feature objects, not integers
+
+* The Feature class is now in its own header, <TelepathyQt4/Feature>
+
+Enhancements:
+
+* The skeletal StreamedMediaChannel class from 0.1.0 has been expanded to
+ cover all the functionality of the Telepathy StreamedMedia interface
+
+* PendingConnection, PendingAccount etc. have busName and objectPath
+ methods where necessary, so that objects of custom classes can be
+ constructed
+
+* Features can now be considered critical, meaning that failure to set them up
+ leads to failure of becomeReady() - this should only be used for features
+ that should never fail unless the service is buggy, like Connection and
+ Channel core functionality
+
+* examples/call/ is an example of how to use StreamedMediaChannel, which can
+ make and receive XMPP Jingle calls using telepathy-gabble
+ (this feature requires telepathy-farsight and GStreamer)
+
+Fixes:
+
+* When introspection of a Feature fails, the D-Bus error is propagated as the
+ failure reason of becomeReady()
+
+* Fix a memory leak in TextChannel
+
+telepathy-qt4 0.1.1 (2009-03-05)
+================================
+
+The "PresencePublicationAuthorizationRequestRejection" release.
+
+API changes:
+
+* PendingReadyAccount, PendingReadyAccountManager, PendingReadyConnection,
+ PendingReadyConnectionManager have all been replaced by the PendingReady
+ class
+
+* Account, AccountManager, Connection and ConnectionManager features are now
+ QSet<uint>, not bitfields
+
+* Plural contacts are generally represented by a QSet<QSharedPointer<Contact> >
+ instead of a QList<QSharedPointer<Contact> > (with a new typedef,
+ Telepathy::Client::Contacts, which must be used in signal/slot connections)
+
+Enhancements:
+
+* Added Connection::FeatureRoster, which, when enabled, adds contact list
+ (a.k.a. roster/buddy list) functionality to the ContactManager and
+ Contact objects
+
+* Improved maintainability of Account, AccountManager, Connection and
+ ConnectionManager becoming ready
+
+* A QSharedPointer<Contact> is now hashable with qHash, meaning contacts can
+ be QSet or QHash members
+
+* Added a trivial contact list user interface, examples/roster/roster
+
+Fixes:
+
+* The client library no longer attempts to enforce group add/remove flags:
+ whatever change the user requests is passed on to the connection manager
+ (which might reject it)
+
+* PendingReady objects returned by Connection::becomeReady() have the
+ Connection as parent, rather than an internal object that isn't useful
+ to library users
+
+telepathy-qt4 0.1.0 (2009-02-26)
+================================
+
+The "pending operation" release.
+
+This first release of telepathy-qt4 features high-level API for the following:
+
+* Manipulating accounts on a Telepathy AccountManager implementation as
+ described by telepathy-spec 0.17.x, such as Mission Control 5 (beta versions
+ currently available)
+
+* Manipulating Telepathy connection managers via the ConnectionManager and
+ Connection core API
+
+* Setting your own presence on a connection manager supporting the
+ SimplePresence interface
+
+* Requesting channels from a connection manager supporting the Requests
+ interface
+
+* Reading contacts' aliases etc. on a connection manager supporting the
+ Contacts interface
+
+* Sending and receiving messages on Text channels, with or without the
+ Messages interface
+
+In addition, lower-level auto-generated accessors are provided for all the
+functionality of telepathy-spec version 0.17.19.
+
+Notable functionality that is currently missing, but will be added soon,
+includes:
+
+* Manipulating a server-stored contact list
+
+* Controlling VoIP calls in StreamedMedia channels
+
+/* ex: set textwidth=80: */
diff --git a/qt4/README b/qt4/README
new file mode 100644
index 000000000..91adc3348
--- /dev/null
+++ b/qt4/README
@@ -0,0 +1,96 @@
+=============
+telepathy-qt4
+=============
+
+This is a library for Qt-based Telepathy clients.
+
+Telepathy is a D-Bus framework for unifying real time communication,
+including instant messaging, voice calls and video calls. It abstracts
+differences between protocols to provide a unified interface for
+applications. See the Telepathy website for more information:
+
+ http://telepathy.freedesktop.org/
+
+Telepathy specification
+=======================
+
+The copy of the Telepathy specification in the spec/ directory indicates
+the specification that this library claims to implement. The HTML documentation for the latest version of the specification can be viewed at:
+
+ http://telepathy.freedesktop.org/spec/
+
+Requirements
+============
+
+Building telepathy-qt4 requires:
+ Qt, including QtDBus <http://www.qtsoftware.com/>
+
+ GNU make <http://www.gnu.org/software/make/>
+ pkg-config <http://ftp.gnome.org/pub/GNOME/sources/pkg-config/>
+ libxslt, xsltproc <http://xmlsoft.org/XSLT/>
+ Python <http://www.python.org/>
+
+For the full set of regression tests to run, you'll also need:
+ telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/>
+
+and to build the example VoIP call UI (examples/call), you'll need:
+ telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/>
+ telepathy-farsight <http://telepathy.freedesktop.org/releases/telepathy-farsight/>
+ GStreamer <http://gstreamer.freedesktop.org/>
+
+See CMakeLists.txt for full details, including versions required.
+Of the packages listed above, only QtCore and QtDBus are required at runtime.
+
+Building also requires the cmake build system.
+
+Bugs, feature requests and to-do list
+=====================================
+
+Report all bugs, feature requests and "to-do" items here:
+ <https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=tp-qt4>
+
+Running "make check" will produce FIXME.out, which lists all the mentions of
+FIXME, TODO or XXX in the source code. Ideally, all of these should be in
+Bugzilla, but sometimes they're not.
+
+API stability policy
+====================
+
+We use an "odd/even" versioning scheme where the minor version (the y in
+x.y.z) determines stability - stable branches have y even, development
+branches have y odd.
+
+In a stable (even) branch, we will not make incompatible API or ABI changes
+between one release tarball and the next.
+
+In a development (odd) branch, if we make incompatible ABI changes
+between one release tarball and the next, we will change the SONAME of the
+library; we will attempt to avoid incompatible API or ABI changes.
+
+Unreleased builds straight from git identify themselves as version
+"x.y.z.1". We DO NOT make any API guarantees about unreleased builds:
+any binary relying on new functionality from an unreleased build is not
+guaranteed to work with any subsequent release or unreleased build, and on
+platforms with versioned symbols (mainly Linux) it definitely won't work with
+subsequent releases (you'll have to at least relink the binary).
+We do not increment SONAMEs on the basis of unreleased changes.
+
+Unreleased builds are compiled with -Werror, so they might stop working
+if your gcc version issues more warnings than ours. If this is a problem
+for you, use a release tarball.
+
+Contact info
+============
+
+This library is maintained by the Telepathy project:
+ <http://telepathy.freedesktop.org/>
+ <mailto:telepathy@lists.freedesktop.org>
+ <irc://irc.freenode.net/telepathy>
+
+Telepathy development is supported by Collabora Ltd.
+ <http://www.collabora.co.uk/>.
+
+Hacking
+=======
+
+See HACKING for version control, coding style and patch submission information.
diff --git a/qt4/TelepathyQt4/AbstractClient b/qt4/TelepathyQt4/AbstractClient
new file mode 100644
index 000000000..20eff9be2
--- /dev/null
+++ b/qt4/TelepathyQt4/AbstractClient
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AbstractClient_HEADER_GUARD_
+#define _TelepathyQt4_AbstractClient_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/abstract-client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AbstractClientApprover b/qt4/TelepathyQt4/AbstractClientApprover
new file mode 100644
index 000000000..cb08beb37
--- /dev/null
+++ b/qt4/TelepathyQt4/AbstractClientApprover
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AbstractClientApprover_HEADER_GUARD_
+#define _TelepathyQt4_AbstractClientApprover_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/abstract-client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AbstractClientHandler b/qt4/TelepathyQt4/AbstractClientHandler
new file mode 100644
index 000000000..9df1d6b25
--- /dev/null
+++ b/qt4/TelepathyQt4/AbstractClientHandler
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AbstractClientHandler_HEADER_GUARD_
+#define _TelepathyQt4_AbstractClientHandler_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/abstract-client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AbstractClientObserver b/qt4/TelepathyQt4/AbstractClientObserver
new file mode 100644
index 000000000..5ecdfde75
--- /dev/null
+++ b/qt4/TelepathyQt4/AbstractClientObserver
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AbstractClientObserver_HEADER_GUARD_
+#define _TelepathyQt4_AbstractClientObserver_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/abstract-client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AbstractInterface b/qt4/TelepathyQt4/AbstractInterface
new file mode 100644
index 000000000..0050367cf
--- /dev/null
+++ b/qt4/TelepathyQt4/AbstractInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AbstractInterface_HEADER_GUARD_
+#define _TelepathyQt4_AbstractInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/abstract-interface.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Account b/qt4/TelepathyQt4/Account
new file mode 100644
index 000000000..ed84b84bb
--- /dev/null
+++ b/qt4/TelepathyQt4/Account
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Account_HEADER_GUARD_
+#define _TelepathyQt4_Account_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountCapabilityFilter b/qt4/TelepathyQt4/AccountCapabilityFilter
new file mode 100644
index 000000000..f53bb2df2
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountCapabilityFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountCapabilityFilter_HEADER_GUARD_
+#define _TelepathyQt4_AccountCapabilityFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-capability-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountFactory b/qt4/TelepathyQt4/AccountFactory
new file mode 100644
index 000000000..63ab1cb4b
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountFactory_HEADER_GUARD_
+#define _TelepathyQt4_AccountFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountFilter b/qt4/TelepathyQt4/AccountFilter
new file mode 100644
index 000000000..485d7f505
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountFilter_HEADER_GUARD_
+#define _TelepathyQt4_AccountFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountInterface b/qt4/TelepathyQt4/AccountInterface
new file mode 100644
index 000000000..98c0810ae
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountInterface_HEADER_GUARD_
+#define _TelepathyQt4_AccountInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountInterfaceAddressingInterface b/qt4/TelepathyQt4/AccountInterfaceAddressingInterface
new file mode 100644
index 000000000..ad499d992
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountInterfaceAddressingInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountInterfaceAddressingInterface_HEADER_GUARD_
+#define _TelepathyQt4_AccountInterfaceAddressingInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountInterfaceAvatarInterface b/qt4/TelepathyQt4/AccountInterfaceAvatarInterface
new file mode 100644
index 000000000..5b30df45d
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountInterfaceAvatarInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountInterfaceAvatarInterface_HEADER_GUARD_
+#define _TelepathyQt4_AccountInterfaceAvatarInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountManager b/qt4/TelepathyQt4/AccountManager
new file mode 100644
index 000000000..e52538c10
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountManager
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountManager_HEADER_GUARD_
+#define _TelepathyQt4_AccountManager_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-manager.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountManagerInterface b/qt4/TelepathyQt4/AccountManagerInterface
new file mode 100644
index 000000000..b8101e573
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountManagerInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountManagerInterface_HEADER_GUARD_
+#define _TelepathyQt4_AccountManagerInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-manager.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountPropertyFilter b/qt4/TelepathyQt4/AccountPropertyFilter
new file mode 100644
index 000000000..647e4c0da
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountPropertyFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountPropertyFilter_HEADER_GUARD_
+#define _TelepathyQt4_AccountPropertyFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-property-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AccountSet b/qt4/TelepathyQt4/AccountSet
new file mode 100644
index 000000000..a6cf10f64
--- /dev/null
+++ b/qt4/TelepathyQt4/AccountSet
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AccountSet_HEADER_GUARD_
+#define _TelepathyQt4_AccountSet_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/account-set.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AndFilter b/qt4/TelepathyQt4/AndFilter
new file mode 100644
index 000000000..a9591e6ab
--- /dev/null
+++ b/qt4/TelepathyQt4/AndFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AndFilter_HEADER_GUARD_
+#define _TelepathyQt4_AndFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/and-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AuthenticationTLSCertificateInterface b/qt4/TelepathyQt4/AuthenticationTLSCertificateInterface
new file mode 100644
index 000000000..d167259d3
--- /dev/null
+++ b/qt4/TelepathyQt4/AuthenticationTLSCertificateInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AuthenticationTLSCertificateInterface_HEADER_GUARD_
+#define _TelepathyQt4_AuthenticationTLSCertificateInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/tls-certificate.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AvatarData b/qt4/TelepathyQt4/AvatarData
new file mode 100644
index 000000000..17dba1127
--- /dev/null
+++ b/qt4/TelepathyQt4/AvatarData
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AvatarData_HEADER_GUARD_
+#define _TelepathyQt4_AvatarData_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/avatar.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/AvatarSpec b/qt4/TelepathyQt4/AvatarSpec
new file mode 100644
index 000000000..72335550e
--- /dev/null
+++ b/qt4/TelepathyQt4/AvatarSpec
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_AvatarSpec_HEADER_GUARD_
+#define _TelepathyQt4_AvatarSpec_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/avatar.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/CMakeLists.txt b/qt4/TelepathyQt4/CMakeLists.txt
new file mode 100644
index 000000000..372df95c0
--- /dev/null
+++ b/qt4/TelepathyQt4/CMakeLists.txt
@@ -0,0 +1,694 @@
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen")
+
+# Set the required flags found in TelepathyDefaults
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${VISIBILITY_HIDDEN_FLAGS} ${COMPILER_COVERAGE_FLAGS} ${DEPRECATED_DECLARATIONS_FLAGS}")
+set(LD_FLAGS "${LD_FLAGS} ${VISIBILITY_HIDDEN_FLAGS} ${COMPILER_COVERAGE_FLAGS} ${DEPRECATED_DECLARATIONS_FLAGS}")
+
+# We are building Telepathy-Qt4
+add_definitions(-DBUILDING_TELEPATHY_QT4)
+
+# Sources for Tp-Qt4
+set(telepathy_qt4_SRCS
+ abstract-client.cpp
+ abstract-interface.cpp
+ account.cpp
+ account-factory.cpp
+ account-manager.cpp
+ account-property-filter.cpp
+ account-set.cpp
+ account-set-internal.h
+ avatar.cpp
+ capabilities-base.cpp
+ channel.cpp
+ channel-class-spec.cpp
+ channel-dispatcher.cpp
+ channel-dispatch-operation.cpp
+ channel-factory.cpp
+ channel-internal.h
+ channel-request.cpp
+ client.cpp
+ client-registrar.cpp
+ client-registrar-internal.h
+ connection.cpp
+ connection-capabilities.cpp
+ connection-factory.cpp
+ connection-internal.h
+ connection-manager.cpp
+ connection-manager-internal.h
+ contact.cpp
+ contact-capabilities.cpp
+ contact-factory.cpp
+ contact-manager.cpp
+ contact-manager-roster.cpp
+ contact-messenger.cpp
+ contact-search-channel.cpp
+ dbus.cpp
+ dbus-proxy.cpp
+ dbus-proxy-factory.cpp
+ dbus-proxy-factory-internal.h
+ debug.cpp
+ debug-internal.h
+ fake-handler-manager-internal.cpp
+ fake-handler-manager-internal.h
+ feature.cpp
+ file-transfer-channel.cpp
+ file-transfer-channel-creation-properties.cpp
+ fixed-feature-factory.cpp
+ future.cpp
+ future-internal.h
+ handled-channel-notifier.cpp
+ incoming-file-transfer-channel.cpp
+ incoming-stream-tube-channel.cpp
+ key-file.cpp
+ location-info.cpp
+ manager-file.cpp
+ media-session-handler.cpp
+ media-stream-handler.cpp
+ message.cpp
+ message-content-part.cpp
+ object.cpp
+ optional-interface-factory.cpp
+ outgoing-file-transfer-channel.cpp
+ outgoing-stream-tube-channel.cpp
+ pending-account.cpp
+ pending-channel.cpp
+ pending-channel-request.cpp
+ pending-channel-request-internal.h
+ pending-connection.cpp
+ pending-contact-attributes.cpp
+ pending-contact-info.cpp
+ pending-contacts.cpp
+ pending-handles.cpp
+ pending-operation.cpp
+ pending-ready.cpp
+ pending-send-message.cpp
+ pending-string-list.cpp
+ pending-stream-tube-connection.cpp
+ pending-variant.cpp
+ presence.cpp
+ pending-variant-map.cpp
+ profile.cpp
+ profile-manager.cpp
+ properties.cpp
+ protocol-info.cpp
+ protocol-parameter.cpp
+ readiness-helper.cpp
+ requestable-channel-class-spec.cpp
+ ready-object.cpp
+ referenced-handles.cpp
+ request-temporary-handler-internal.cpp
+ request-temporary-handler-internal.h
+ room-list-channel.cpp
+ simple-call-observer.cpp
+ simple-observer.cpp
+ simple-observer-internal.h
+ simple-stream-tube-handler.cpp
+ simple-text-observer.cpp
+ simple-text-observer-internal.h
+ stream-tube-channel.cpp
+ stream-tube-client.cpp
+ stream-tube-client-internal.h
+ stream-tube-server.cpp
+ stream-tube-server-internal.h
+ streamed-media-channel.cpp
+ test-backdoors.cpp
+ test-backdoors.h
+ text-channel.cpp
+ tls-certificate.cpp
+ tube-channel.cpp
+ types.cpp
+ types-internal.h
+ utils.cpp)
+
+# Exported headers for Tp-Qt4
+set(telepathy_qt4_HEADERS
+ AbstractClient
+ AbstractClientApprover
+ abstract-client.h
+ AbstractClientHandler
+ AbstractClientObserver
+ AbstractInterface
+ abstract-interface.h
+ Account
+ account.h
+ AccountCapabilityFilter
+ account-capability-filter.h
+ AccountFactory
+ account-factory.h
+ AccountFilter
+ account-filter.h
+ AccountInterface
+ AccountInterfaceAddressingInterface
+ AccountInterfaceAvatarInterface
+ AccountManager
+ account-manager.h
+ AccountManagerInterface
+ account-property-filter.h
+ AccountPropertyFilter
+ AccountSet
+ account-set.h
+ AndFilter
+ and-filter.h
+ AuthenticationTLSCertificateInterface
+ AvatarData
+ AvatarSpec
+ avatar.h
+ CapabilitiesBase
+ capabilities-base.h
+ Channel
+ channel.h
+ ChannelClassFeatures
+ channel-class-features.h
+ ChannelClassSpec
+ ChannelClassSpecList
+ channel-class-spec.h
+ ChannelDispatcher
+ ChannelDispatcherInterface
+ channel-dispatcher.h
+ ChannelDispatchOperation
+ channel-dispatch-operation.h
+ ChannelDispatchOperationInterface
+ ChannelFactory
+ channel-factory.h
+ ChannelInterface
+ ChannelInterfaceAnonymityInterface
+ ChannelInterfaceCallStateInterface
+ ChannelInterfaceChatStateInterface
+ ChannelInterfaceConferenceInterface
+ ChannelInterfaceDTMFInterface
+ ChannelInterfaceGroupInterface
+ ChannelInterfaceHoldInterface
+ ChannelInterfaceMediaSignallingInterface
+ ChannelInterfaceMessagesInterface
+ ChannelInterfacePasswordInterface
+ ChannelInterfaceSASLAuthenticationInterface
+ ChannelInterfaceSecurableInterface
+ ChannelInterfaceServicePointInterface
+ ChannelInterfaceTubeInterface
+ ChannelRequest
+ ChannelRequestHints
+ channel-request.h
+ ChannelRequestInterface
+ ChannelTypeContactListInterface
+ ChannelTypeContactSearchInterface
+ ChannelTypeFileTransferInterface
+ ChannelTypeRoomListInterface
+ ChannelTypeServerAuthenticationInterface
+ ChannelTypeServerTLSConnectionInterface
+ ChannelTypeStreamedMediaInterface
+ ChannelTypeStreamTubeInterface
+ ChannelTypeTextInterface
+ ChannelTypeTubeInterface
+ ChannelTypeTubesInterface
+ Client
+ ClientApproverInterface
+ client.h
+ ClientHandlerInterface
+ ClientInterface
+ ClientInterfaceRequestsInterface
+ ClientObserverInterface
+ ClientRegistrar
+ client-registrar.h
+ Connection
+ ConnectionCapabilities
+ connection-capabilities.h
+ connection.h
+ ConnectionFactory
+ connection-factory.h
+ connection-lowlevel.h
+ ConnectionInterface
+ ConnectionInterfaceAliasingInterface
+ ConnectionInterfaceAnonymityInterface
+ ConnectionInterfaceAvatarsInterface
+ ConnectionInterfaceBalanceInterface
+ ConnectionInterfaceCapabilitiesInterface
+ ConnectionInterfaceCellularInterface
+ ConnectionInterfaceClientTypes
+ ConnectionInterfaceContactBlockingInterface
+ ConnectionInterfaceClientTypesInterface
+ ConnectionInterfaceContactCapabilitiesInterface
+ ConnectionInterfaceContactGroups
+ ConnectionInterfaceContactGroupsInterface
+ ConnectionInterfaceContactInfoInterface
+ ConnectionInterfaceContactList
+ ConnectionInterfaceContactListInterface
+ ConnectionInterfaceContactsInterface
+ ConnectionInterfaceLocationInterface
+ ConnectionInterfaceMailNotificationInterface
+ ConnectionInterfacePowerSaving
+ ConnectionInterfacePowerSavingInterface
+ ConnectionInterfacePresenceInterface
+ ConnectionInterfaceRequestsInterface
+ ConnectionInterfaceServicePointInterface
+ ConnectionInterfaceSimplePresenceInterface
+ ConnectionLowlevel
+ ConnectionManager
+ connection-manager.h
+ connection-manager-lowlevel.h
+ ConnectionManagerInterface
+ ConnectionManagerLowlevel
+ Constants
+ constants.h
+ Contact
+ contact.h
+ ContactCapabilities
+ contact-capabilities.h
+ ContactFactory
+ contact-factory.h
+ ContactManager
+ contact-manager.h
+ ContactMessenger
+ contact-messenger.h
+ ContactSearchChannel
+ contact-search-channel.h
+ DBus
+ DBusDaemonInterface
+ dbus.h
+ DBusProxy
+ dbus-proxy.h
+ DBusProxyFactory
+ dbus-proxy-factory.h
+ Debug
+ debug.h
+ Feature
+ Features
+ feature.h
+ FileTransferChannel
+ FileTransferChannelCreationProperties
+ file-transfer-channel-creation-properties.h
+ file-transfer-channel.h
+ Filter
+ filter.h
+ FixedFeatureFactory
+ fixed-feature-factory.h
+ GenericCapabilityFilter
+ generic-capability-filter.h
+ GenericPropertyFilter
+ generic-property-filter.h
+ Global
+ global.h
+ HandledChannelNotifier
+ handled-channel-notifier.h
+ IncomingFileTransferChannel
+ incoming-file-transfer-channel.h
+ IncomingStreamTubeChannel
+ incoming-stream-tube-channel.h
+ IntrospectableInterface
+ KeyFile
+ key-file.h
+ LocationInfo
+ location-info.h
+ ManagerFile
+ manager-file.h
+ MediaSessionHandler
+ media-session-handler.h
+ MediaSessionHandlerInterface
+ MediaStreamHandler
+ media-stream-handler.h
+ MediaStreamHandlerInterface
+ Message
+ message.h
+ MessageContentPart
+ MessageContentPartList
+ message-content-part.h
+ MethodInvocationContext
+ method-invocation-context.h
+ NotFilter
+ not-filter.h
+ Object
+ object.h
+ OptionalInterfaceFactory
+ optional-interface-factory.h
+ OrFilter
+ or-filter.h
+ OutgoingFileTransferChannel
+ outgoing-file-transfer-channel.h
+ OutgoingStreamTubeChannel
+ outgoing-stream-tube-channel.h
+ PeerInterface
+ PendingAccount
+ pending-account.h
+ PendingChannel
+ pending-channel.h
+ PendingChannelRequest
+ pending-channel-request.h
+ PendingComposite
+ PendingConnection
+ pending-connection.h
+ PendingContactAttributes
+ pending-contact-attributes.h
+ PendingContactInfo
+ pending-contact-info.h
+ PendingContacts
+ pending-contacts.h
+ PendingFailure
+ PendingHandles
+ pending-handles.h
+ PendingOperation
+ pending-operation.h
+ PendingReady
+ pending-ready.h
+ PendingSendMessage
+ pending-send-message.h
+ PendingStreamedMediaStreams
+ PendingStreamTubeConnection
+ pending-stream-tube-connection.h
+ PendingStringList
+ pending-string-list.h
+ PendingSuccess
+ PendingVariant
+ pending-variant.h
+ PendingVariantMap
+ pending-variant-map.h
+ PendingVoid
+ Presence
+ presence.h
+ PresenceSpec
+ PresenceSpecList
+ Profile
+ profile.h
+ ProfileManager
+ profile-manager.h
+ Properties
+ properties.h
+ PropertiesInterface
+ PropertiesInterfaceInterface
+ ProtocolInfo
+ protocol-info.h
+ ProtocolParameter
+ protocol-parameter.h
+ ReadinessHelper
+ readiness-helper.h
+ ReadyObject
+ ready-object.h
+ ReceivedMessage
+ RefCounted
+ ReferencedHandles
+ referenced-handles.h
+ ReferencedHandlesIterator
+ requestable-channel-class-spec.h
+ RequestableChannelClassSpec
+ RequestableChannelClassSpecList
+ RoomListChannel
+ room-list-channel.h
+ SharedPtr
+ shared-ptr.h
+ SimpleCallObserver
+ simple-call-observer.h
+ SimpleObserver
+ simple-observer.h
+ simple-pending-operations.h
+ SimpleTextObserver
+ simple-text-observer.h
+ StatefulDBusProxy
+ StatelessDBusProxy
+ StreamTubeChannel
+ StreamTubeClient
+ StreamTubeServer
+ stream-tube-channel.h
+ stream-tube-client.h
+ stream-tube-server.h
+ StreamedMediaChannel
+ streamed-media-channel.h
+ StreamedMediaStream
+ TextChannel
+ text-channel.h
+ tls-certificate.h
+ TubeChannel
+ tube-channel.h
+ Types
+ types.h
+ Utils
+ utils.h)
+
+# Generated headers which will be installed and exported
+set(telepathy_qt4_gen_HEADERS
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-account.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-account-manager.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel-dispatcher.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel-dispatch-operation.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel-request.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-client.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection-manager.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-dbus.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-media-session-handler.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-media-stream-handler.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-properties.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-tls-certificate.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/constants.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h)
+
+# Headers file moc will be run on
+set(telepathy_qt4_MOC_SRCS
+ abstract-interface.h
+ account.h
+ account-factory.h
+ account-manager.h
+ account-set.h
+ account-set-internal.h
+ channel.h
+ channel-dispatch-operation.h
+ channel-dispatch-operation-internal.h
+ channel-factory.h
+ channel-internal.h
+ channel-request.h
+ client-registrar.h
+ client-registrar-internal.h
+ connection.h
+ connection-internal.h
+ connection-lowlevel.h
+ connection-manager.h
+ connection-manager-internal.h
+ connection-manager-lowlevel.h
+ contact.h
+ contact-manager.h
+ contact-manager-internal.h
+ contact-messenger.h
+ contact-search-channel.h
+ contact-search-channel-internal.h
+ dbus-proxy.h
+ dbus-proxy-factory.h
+ dbus-proxy-factory-internal.h
+ fake-handler-manager-internal.h
+ file-transfer-channel.h
+ fixed-feature-factory.h
+ handled-channel-notifier.h
+ incoming-file-transfer-channel.h
+ incoming-stream-tube-channel.h
+ object.h
+ outgoing-file-transfer-channel.h
+ outgoing-stream-tube-channel.h
+ outgoing-stream-tube-channel-internal.h
+ pending-account.h
+ pending-channel.h
+ pending-channel-request.h
+ pending-channel-request-internal.h
+ pending-connection.h
+ pending-contact-attributes.h
+ pending-contact-info.h
+ pending-contacts.h
+ pending-handles.h
+ pending-operation.h
+ pending-ready.h
+ pending-send-message.h
+ pending-stream-tube-connection.h
+ pending-string-list.h
+ pending-variant.h
+ pending-variant-map.h
+ profile-manager.h
+ readiness-helper.h
+ request-temporary-handler-internal.h
+ room-list-channel.h
+ simple-call-observer.h
+ simple-pending-operations.h
+ simple-observer.h
+ simple-observer-internal.h
+ simple-stream-tube-handler.h
+ simple-text-observer.h
+ simple-text-observer-internal.h
+ stream-tube-channel.h
+ stream-tube-client.h
+ stream-tube-client-internal.h
+ stream-tube-server.h
+ stream-tube-server-internal.h
+ streamed-media-channel.h
+ text-channel.h
+ tube-channel.h)
+
+# Generate the spec files for both stable and future spec
+set(gen_stable_spec_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/stable-spec.xml)
+set(gen_future_spec_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-spec.xml)
+
+tpqt4_xincludator(stable-ifaces-includator ${CMAKE_CURRENT_SOURCE_DIR}/stable-interfaces.xml ${gen_stable_spec_xml})
+tpqt4_xincludator(future-ifaces-includator ${CMAKE_CURRENT_SOURCE_DIR}/future-interfaces.xml ${gen_future_spec_xml})
+
+add_custom_target(all-generated-sources)
+
+tpqt4_constants_gen(stable-constants ${gen_stable_spec_xml} ${CMAKE_CURRENT_BINARY_DIR}/_gen/constants.h
+ --namespace=Tp
+ --str-constant-prefix=TELEPATHY_
+ --define-prefix=TP_QT4_
+ --must-define=IN_TELEPATHY_QT4_HEADER
+ DEPENDS stable-ifaces-includator)
+tpqt4_constants_gen(future-constants ${gen_future_spec_xml} ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-constants.h
+ --namespace=TpFuture
+ --str-constant-prefix=TP_FUTURE_
+ --define-prefix=TP_QT4_FUTURE_
+ DEPENDS future-ifaces-includator)
+
+tpqt4_types_gen(stable-typesgen ${gen_stable_spec_xml}
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/types-body.hpp
+ Tp TelepathyQt4/types.h TelepathyQt4/Types
+ --must-define=IN_TELEPATHY_QT4_HEADER
+ --visibility=TELEPATHY_QT4_EXPORT
+ DEPENDS stable-constants)
+tpqt4_types_gen(future-typesgen ${gen_future_spec_xml}
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types-body.hpp
+ TpFuture TelepathyQt4/future-internal.h TelepathyQt4/future-internal.h
+ DEPENDS future-constants)
+
+# Add the generated types to the library's sources
+list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h)
+list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/types-body.hpp)
+list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-constants.h)
+list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types.h)
+list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types-body.hpp)
+
+# For each spec, both stable and future, generate a cli file and add it to the sources (including mocs).
+set(SPECS
+ account
+ account-manager
+ channel
+ channel-dispatcher
+ channel-dispatch-operation
+ channel-request
+ client
+ connection
+ connection-manager
+ dbus
+ media-session-handler
+ media-stream-handler
+ properties
+ tls-certificate)
+foreach(spec ${SPECS})
+ tpqt4_xincludator(${spec}-spec-xincludator ${CMAKE_CURRENT_SOURCE_DIR}/${spec}.xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/spec-${spec}.xml
+ DEPENDS stable-typesgen)
+ set(NEW_FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}-body.hpp
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.moc.hpp)
+ list(APPEND telepathy_qt4_SRCS ${NEW_FILES})
+ list(APPEND telepathy_qt4_generated_specs_mocs "moc-cli-${spec}.moc.hpp")
+ set_source_files_properties(${NEW_FILES} PROPERTIES GENERATED true)
+endforeach(spec ${SPECS})
+
+set(FUTURE_SPECS
+ channel
+ channel-dispatcher
+ misc)
+foreach(spec ${FUTURE_SPECS})
+ tpqt4_xincludator(${spec}-future-xincludator ${CMAKE_CURRENT_SOURCE_DIR}/future-${spec}.xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.xml
+ DEPENDS stable-typesgen future-typesgen)
+ set(NEW_FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}-body.hpp
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.moc.hpp)
+ list(APPEND telepathy_qt4_SRCS ${NEW_FILES})
+ list(APPEND telepathy_qt4_generated_specs_mocs "moc-future-${spec}.moc.hpp")
+ set_source_files_properties(${NEW_FILES} PROPERTIES GENERATED true)
+endforeach(spec ${FUTURE_SPECS})
+
+# The escape character in MSVC is ^
+if(MSVC)
+ set(TYPES_INCLUDE ^<TelepathyQt4/Types^> )
+else(MSVC)
+ set(TYPES_INCLUDE '<TelepathyQt4/Types>' )
+endif(MSVC)
+
+# Use the client generator for generating headers out of specs
+tpqt4_client_generator(account clientaccount AccountManager Tp::Client --mainiface=Tp::Client::AccountInterface DEPENDS account-spec-xincludator)
+tpqt4_client_generator(account-manager clientam AccountManager Tp::Client --mainiface=Tp::Client::AccountManagerInterface DEPENDS account-manager-spec-xincludator)
+tpqt4_client_generator(channel clientchannel Channel Tp::Client --mainiface=Tp::Client::ChannelInterface DEPENDS channel-spec-xincludator)
+tpqt4_client_generator(channel-dispatcher clientchanneldispatcher ChannelDispatcher Tp::Client --mainiface=Tp::Client::ChannelDispatcherInterface DEPENDS channel-dispatcher-spec-xincludator)
+tpqt4_client_generator(channel-dispatch-operation clientchanneldispatchoperation ChannelDispatchOperation Tp::Client --mainiface=Tp::Client::ChannelDispatchOperationInterface DEPENDS channel-dispatch-operation-spec-xincludator)
+tpqt4_client_generator(channel-request clientchannelrequest ChannelRequest Tp::Client --mainiface=Tp::Client::ChannelRequestInterface DEPENDS channel-request-spec-xincludator)
+tpqt4_client_generator(client clientclient Client Tp::Client --mainiface=Tp::Client::ClientInterface DEPENDS client-spec-xincludator)
+tpqt4_client_generator(connection clientconn Connection Tp::Client --mainiface=Tp::Client::ConnectionInterface DEPENDS connection-spec-xincludator)
+tpqt4_client_generator(connection-manager clientcm ConnectionManager Tp::Client --mainiface=Tp::Client::ConnectionManagerInterface DEPENDS connection-manager-spec-xincludator)
+tpqt4_client_generator(dbus clientdbus DBus Tp::Client::DBus DEPENDS dbus-spec-xincludator)
+tpqt4_client_generator(media-session-handler clientmsesh MediaSessionHandler Tp::Client --mainiface=Tp::Client::MediaSessionHandlerInterface DEPENDS media-session-handler-spec-xincludator)
+tpqt4_client_generator(media-stream-handler clientmstrh MediaStreamHandler Tp::Client --mainiface=Tp::Client::MediaStreamHandlerInterface DEPENDS media-stream-handler-spec-xincludator)
+tpqt4_client_generator(properties clientprops Properties Tp::Client DEPENDS properties-spec-xincludator)
+tpqt4_client_generator(tls-certificate clienttls TLSCertificate Tp::Client DEPENDS tls-certificate-spec-xincludator)
+
+tpqt4_future_client_generator(channel TpFuture::Client --mainiface=Tp::Client::ChannelInterface DEPENDS channel-future-xincludator)
+tpqt4_future_client_generator(channel-dispatcher TpFuture::Client --mainiface=Tp::Client::ChannelDispatcherInterface DEPENDS channel-dispatcher-future-xincludator)
+tpqt4_future_client_generator(misc TpFuture::Client DEPENDS misc-future-xincludator)
+
+if (TARGET doxygen-doc)
+ add_dependencies(doxygen-doc all-generated-sources)
+endif (TARGET doxygen-doc)
+
+# Create the library
+if (ENABLE_COMPILER_COVERAGE)
+ add_library(telepathy-qt4 STATIC ${telepathy_qt4_SRCS})
+else (ENABLE_COMPILER_COVERAGE)
+ add_library(telepathy-qt4 SHARED ${telepathy_qt4_SRCS})
+endif (ENABLE_COMPILER_COVERAGE)
+
+# generate client moc files
+foreach(moc_src ${telepathy_qt4_MOC_SRCS})
+ set(generated_file _gen/${moc_src})
+ string(REPLACE ".h" ".moc.hpp" generated_file ${generated_file})
+ tpqt4_generate_moc_i_target_deps(${CMAKE_CURRENT_SOURCE_DIR}/${moc_src} ${CMAKE_CURRENT_BINARY_DIR}/${generated_file}
+ ${telepathy_qt4_generated_specs_mocs})
+ list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/${generated_file})
+ string(REPLACE ".h" ".moc.hpp" moc_src ${moc_src})
+ add_dependencies(telepathy-qt4 "moc-${moc_src}")
+endforeach(moc_src ${telepathy_qt4_MOC_SRCS})
+
+# Link
+target_link_libraries(telepathy-qt4
+ ${QT_QTCORE_LIBRARY}
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTNETWORK_LIBRARIES}
+ ${QT_QTXML_LIBRARIES})
+
+if (ENABLE_COMPILER_COVERAGE)
+ target_link_libraries(telepathy-qt4 gcov)
+endif (ENABLE_COMPILER_COVERAGE)
+
+# Set the correct version number
+set_target_properties(telepathy-qt4 PROPERTIES
+ SOVERSION ${TP_QT4_ABI_VERSION}
+ VERSION ${TP_QT4_LIBRARY_VERSION})
+
+
+# Install header files
+install(FILES ${telepathy_qt4_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/telepathy-1.0/TelepathyQt4 COMPONENT headers)
+install(FILES ${telepathy_qt4_gen_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/telepathy-1.0/TelepathyQt4/_gen COMPONENT headers)
+
+# Install the library - watch out for the correct components
+if (WIN32)
+ install(TARGETS telepathy-qt4 RUNTIME DESTINATION ${LIB_INSTALL_DIR} COMPONENT mainlibrary
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT headers)
+else (WIN32)
+ install(TARGETS telepathy-qt4 LIBRARY DESTINATION ${LIB_INSTALL_DIR} COMPONENT mainlibrary
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT headers)
+endif (WIN32)
+
+# pkg-config file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQt4.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt4.pc)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQt4-uninstalled.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt4-uninstalled.pc)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt4.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig COMPONENT headers)
+
+# If Farsight was found, install its pkg-config files as well, and go into the subdirectory
+if(FARSIGHT_COMPONENTS_FOUND)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQt4Farsight.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt4Farsight.pc)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQt4Farsight-uninstalled.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt4Farsight-uninstalled.pc)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt4Farsight.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig COMPONENT farsight_headers)
+endif(FARSIGHT_COMPONENTS_FOUND)
+
+add_subdirectory(Farsight)
+
diff --git a/qt4/TelepathyQt4/CapabilitiesBase b/qt4/TelepathyQt4/CapabilitiesBase
new file mode 100644
index 000000000..22cd25852
--- /dev/null
+++ b/qt4/TelepathyQt4/CapabilitiesBase
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_CapabilitiesBase_HEADER_GUARD_
+#define _TelepathyQt4_CapabilitiesBase_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/capabilities-base.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Channel b/qt4/TelepathyQt4/Channel
new file mode 100644
index 000000000..21b13c0b6
--- /dev/null
+++ b/qt4/TelepathyQt4/Channel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Channel_HEADER_GUARD_
+#define _TelepathyQt4_Channel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelClassFeatures b/qt4/TelepathyQt4/ChannelClassFeatures
new file mode 100644
index 000000000..714e4d7b6
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelClassFeatures
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelClassFeatures_HEADER_GUARD_
+#define _TelepathyQt4_ChannelClassFeatures_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-class-features.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelClassSpec b/qt4/TelepathyQt4/ChannelClassSpec
new file mode 100644
index 000000000..1e27a148a
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelClassSpec
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelClassSpec_HEADER_GUARD_
+#define _TelepathyQt4_ChannelClassSpec_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-class-spec.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelClassSpecList b/qt4/TelepathyQt4/ChannelClassSpecList
new file mode 100644
index 000000000..4f3b2db5a
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelClassSpecList
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelClassSpecList_HEADER_GUARD_
+#define _TelepathyQt4_ChannelClassSpecList_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-class-spec.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelDispatchOperation b/qt4/TelepathyQt4/ChannelDispatchOperation
new file mode 100644
index 000000000..e897f2078
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelDispatchOperation
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelDispatchOperation_HEADER_GUARD_
+#define _TelepathyQt4_ChannelDispatchOperation_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-dispatch-operation.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelDispatchOperationInterface b/qt4/TelepathyQt4/ChannelDispatchOperationInterface
new file mode 100644
index 000000000..1c1ca52d6
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelDispatchOperationInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelDispatchOperationInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelDispatchOperationInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-dispatch-operation.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelDispatcher b/qt4/TelepathyQt4/ChannelDispatcher
new file mode 100644
index 000000000..8ae6f01c4
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelDispatcher
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelDispatcher_HEADER_GUARD_
+#define _TelepathyQt4_ChannelDispatcher_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-dispatcher.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelDispatcherInterface b/qt4/TelepathyQt4/ChannelDispatcherInterface
new file mode 100644
index 000000000..0a795de08
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelDispatcherInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelDispatcherInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelDispatcherInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-dispatcher.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelFactory b/qt4/TelepathyQt4/ChannelFactory
new file mode 100644
index 000000000..ee1b5d241
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelFactory_HEADER_GUARD_
+#define _TelepathyQt4_ChannelFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterface b/qt4/TelepathyQt4/ChannelInterface
new file mode 100644
index 000000000..142536658
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceAnonymityInterface b/qt4/TelepathyQt4/ChannelInterfaceAnonymityInterface
new file mode 100644
index 000000000..27fa98088
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceAnonymityInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceAnonymityInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceAnonymityInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceCallStateInterface b/qt4/TelepathyQt4/ChannelInterfaceCallStateInterface
new file mode 100644
index 000000000..8bdcbf2fc
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceCallStateInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceCallStateInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceCallStateInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceChatStateInterface b/qt4/TelepathyQt4/ChannelInterfaceChatStateInterface
new file mode 100644
index 000000000..25435ac57
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceChatStateInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceChatStateInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceChatStateInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceConferenceInterface b/qt4/TelepathyQt4/ChannelInterfaceConferenceInterface
new file mode 100644
index 000000000..e3d700587
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceConferenceInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceConferenceInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceConferenceInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceDTMFInterface b/qt4/TelepathyQt4/ChannelInterfaceDTMFInterface
new file mode 100644
index 000000000..2e2f1b52d
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceDTMFInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceDTMFInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceDTMFInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceGroupInterface b/qt4/TelepathyQt4/ChannelInterfaceGroupInterface
new file mode 100644
index 000000000..0f01d7750
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceGroupInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceGroupInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceGroupInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceHoldInterface b/qt4/TelepathyQt4/ChannelInterfaceHoldInterface
new file mode 100644
index 000000000..b75296e2c
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceHoldInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceHoldInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceHoldInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceMediaSignallingInterface b/qt4/TelepathyQt4/ChannelInterfaceMediaSignallingInterface
new file mode 100644
index 000000000..f42fe5ef9
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceMediaSignallingInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceMediaSignallingInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceMediaSignallingInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceMessagesInterface b/qt4/TelepathyQt4/ChannelInterfaceMessagesInterface
new file mode 100644
index 000000000..187737eee
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceMessagesInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceMessagesInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceMessagesInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfacePasswordInterface b/qt4/TelepathyQt4/ChannelInterfacePasswordInterface
new file mode 100644
index 000000000..e54034a2f
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfacePasswordInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfacePasswordInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfacePasswordInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceSASLAuthenticationInterface b/qt4/TelepathyQt4/ChannelInterfaceSASLAuthenticationInterface
new file mode 100644
index 000000000..7f5fde3b3
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceSASLAuthenticationInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceSASLConnectionInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceSASLConnectionInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceSecurableInterface b/qt4/TelepathyQt4/ChannelInterfaceSecurableInterface
new file mode 100644
index 000000000..00af1f3bd
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceSecurableInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceSecurableInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceSecurableInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceServicePointInterface b/qt4/TelepathyQt4/ChannelInterfaceServicePointInterface
new file mode 100644
index 000000000..3a4e15cdb
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceServicePointInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceServicePointInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceServicePointInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelInterfaceTubeInterface b/qt4/TelepathyQt4/ChannelInterfaceTubeInterface
new file mode 100644
index 000000000..6a2499b0b
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelInterfaceTubeInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelInterfaceTubeInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelInterfaceTubeInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelRequest b/qt4/TelepathyQt4/ChannelRequest
new file mode 100644
index 000000000..7878fec27
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelRequest
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelRequest_HEADER_GUARD_
+#define _TelepathyQt4_ChannelRequest_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-request.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelRequestHints b/qt4/TelepathyQt4/ChannelRequestHints
new file mode 100644
index 000000000..8292b9b06
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelRequestHints
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelRequestHints_HEADER_GUARD_
+#define _TelepathyQt4_ChannelRequestHints_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-request.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelRequestInterface b/qt4/TelepathyQt4/ChannelRequestInterface
new file mode 100644
index 000000000..67c059eff
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelRequestInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelRequestInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelRequestInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel-request.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeContactListInterface b/qt4/TelepathyQt4/ChannelTypeContactListInterface
new file mode 100644
index 000000000..794da7732
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeContactListInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeContactListInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeContactListInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeContactSearchInterface b/qt4/TelepathyQt4/ChannelTypeContactSearchInterface
new file mode 100644
index 000000000..89989f24a
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeContactSearchInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeContactSearchInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeContactSearchInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeFileTransferInterface b/qt4/TelepathyQt4/ChannelTypeFileTransferInterface
new file mode 100644
index 000000000..23227e03a
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeFileTransferInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeFileTransferInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeFileTransferInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeRoomListInterface b/qt4/TelepathyQt4/ChannelTypeRoomListInterface
new file mode 100644
index 000000000..45dbf0b6c
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeRoomListInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeRoomListInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeRoomListInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeServerAuthenticationInterface b/qt4/TelepathyQt4/ChannelTypeServerAuthenticationInterface
new file mode 100644
index 000000000..47ea8e68a
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeServerAuthenticationInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeServerAuthenticationInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeServerAuthenticationInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeServerTLSConnectionInterface b/qt4/TelepathyQt4/ChannelTypeServerTLSConnectionInterface
new file mode 100644
index 000000000..d4b7de93c
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeServerTLSConnectionInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeServerTLSConnectionInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeServerTLSConnectionInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeStreamTubeInterface b/qt4/TelepathyQt4/ChannelTypeStreamTubeInterface
new file mode 100644
index 000000000..f11605f90
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeStreamTubeInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Client_ChannelTypeStreamTubeInterface_HEADER_GUARD_
+#define _TelepathyQt4_Client_ChannelTypeStreamTubeInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeStreamedMediaInterface b/qt4/TelepathyQt4/ChannelTypeStreamedMediaInterface
new file mode 100644
index 000000000..be065b9b2
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeStreamedMediaInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeStreamedMediaInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeStreamedMediaInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeTextInterface b/qt4/TelepathyQt4/ChannelTypeTextInterface
new file mode 100644
index 000000000..bf35946fd
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeTextInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeTextInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeTextInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeTubeInterface b/qt4/TelepathyQt4/ChannelTypeTubeInterface
new file mode 100644
index 000000000..163a11332
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeTubeInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Client_ChannelTypeTubeInterface_HEADER_GUARD_
+#define _TelepathyQt4_Client_ChannelTypeTubeInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ChannelTypeTubesInterface b/qt4/TelepathyQt4/ChannelTypeTubesInterface
new file mode 100644
index 000000000..982f9e32e
--- /dev/null
+++ b/qt4/TelepathyQt4/ChannelTypeTubesInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ChannelTypeTubesInterface_HEADER_GUARD_
+#define _TelepathyQt4_ChannelTypeTubesInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Client b/qt4/TelepathyQt4/Client
new file mode 100644
index 000000000..a819fef8e
--- /dev/null
+++ b/qt4/TelepathyQt4/Client
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Client_HEADER_GUARD_
+#define _TelepathyQt4_Client_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ClientApproverInterface b/qt4/TelepathyQt4/ClientApproverInterface
new file mode 100644
index 000000000..2b89b410f
--- /dev/null
+++ b/qt4/TelepathyQt4/ClientApproverInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ClientApproverInterface_HEADER_GUARD_
+#define _TelepathyQt4_ClientApproverInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ClientHandlerInterface b/qt4/TelepathyQt4/ClientHandlerInterface
new file mode 100644
index 000000000..e6f8b5099
--- /dev/null
+++ b/qt4/TelepathyQt4/ClientHandlerInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ClientHandlerInterface_HEADER_GUARD_
+#define _TelepathyQt4_ClientHandlerInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ClientInterface b/qt4/TelepathyQt4/ClientInterface
new file mode 100644
index 000000000..edc41a22a
--- /dev/null
+++ b/qt4/TelepathyQt4/ClientInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ClientInterface_HEADER_GUARD_
+#define _TelepathyQt4_ClientInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ClientInterfaceRequestsInterface b/qt4/TelepathyQt4/ClientInterfaceRequestsInterface
new file mode 100644
index 000000000..7f77444de
--- /dev/null
+++ b/qt4/TelepathyQt4/ClientInterfaceRequestsInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ClientInterfaceRequestsInterface_HEADER_GUARD_
+#define _TelepathyQt4_ClientInterfaceRequestsInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ClientObserverInterface b/qt4/TelepathyQt4/ClientObserverInterface
new file mode 100644
index 000000000..737a61b14
--- /dev/null
+++ b/qt4/TelepathyQt4/ClientObserverInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ClientObserverInterface_HEADER_GUARD_
+#define _TelepathyQt4_ClientObserverInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ClientRegistrar b/qt4/TelepathyQt4/ClientRegistrar
new file mode 100644
index 000000000..bde96724d
--- /dev/null
+++ b/qt4/TelepathyQt4/ClientRegistrar
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ClientRegistrar_HEADER_GUARD_
+#define _TelepathyQt4_ClientRegistrar_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/client-registrar.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Connection b/qt4/TelepathyQt4/Connection
new file mode 100644
index 000000000..1d2d6ba64
--- /dev/null
+++ b/qt4/TelepathyQt4/Connection
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Connection_HEADER_GUARD_
+#define _TelepathyQt4_Connection_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionCapabilities b/qt4/TelepathyQt4/ConnectionCapabilities
new file mode 100644
index 000000000..b8044d553
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionCapabilities
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionCapabilities_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionCapabilities_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection-capabilities.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionFactory b/qt4/TelepathyQt4/ConnectionFactory
new file mode 100644
index 000000000..218578217
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionFactory_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterface b/qt4/TelepathyQt4/ConnectionInterface
new file mode 100644
index 000000000..8db3896e4
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceAliasingInterface b/qt4/TelepathyQt4/ConnectionInterfaceAliasingInterface
new file mode 100644
index 000000000..d02ef3708
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceAliasingInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceAliasingInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceAliasingInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceAnonymityInterface b/qt4/TelepathyQt4/ConnectionInterfaceAnonymityInterface
new file mode 100644
index 000000000..9ed6a66cb
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceAnonymityInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceAnonymityInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceAnonymityInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceAvatarsInterface b/qt4/TelepathyQt4/ConnectionInterfaceAvatarsInterface
new file mode 100644
index 000000000..7aad818dd
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceAvatarsInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceAvatarsInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceAvatarsInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceBalanceInterface b/qt4/TelepathyQt4/ConnectionInterfaceBalanceInterface
new file mode 100644
index 000000000..927a04eed
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceBalanceInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceBalanceInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceBalanceInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceCapabilitiesInterface b/qt4/TelepathyQt4/ConnectionInterfaceCapabilitiesInterface
new file mode 100644
index 000000000..09c69ac90
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceCapabilitiesInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceCapabilitiesInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceCapabilitiesInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceCellularInterface b/qt4/TelepathyQt4/ConnectionInterfaceCellularInterface
new file mode 100644
index 000000000..7d769c8db
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceCellularInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceCellularInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceCellularInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceClientTypes b/qt4/TelepathyQt4/ConnectionInterfaceClientTypes
new file mode 100644
index 000000000..c48232712
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceClientTypes
@@ -0,0 +1,17 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceClientTyes_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceClientTyes_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#ifdef TELEPATHY_QT4_DEPRECATED_WARNINGS
+#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt4/ConnectionInterfaceClientTypesInterface> instead"
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceClientTypesInterface b/qt4/TelepathyQt4/ConnectionInterfaceClientTypesInterface
new file mode 100644
index 000000000..657c2887a
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceClientTypesInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceClientTypesInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceClientTypesInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactBlockingInterface b/qt4/TelepathyQt4/ConnectionInterfaceContactBlockingInterface
new file mode 100644
index 000000000..e6e5b60fb
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactBlockingInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactBlockingInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactBlockingInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactCapabilitiesInterface b/qt4/TelepathyQt4/ConnectionInterfaceContactCapabilitiesInterface
new file mode 100644
index 000000000..96d94a71e
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactCapabilitiesInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactCapabilitiesInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactCapabilitiesInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactGroups b/qt4/TelepathyQt4/ConnectionInterfaceContactGroups
new file mode 100644
index 000000000..a09fa221c
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactGroups
@@ -0,0 +1,17 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactGroups_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactGroups_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#ifdef TELEPATHY_QT4_DEPRECATED_WARNINGS
+#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt4/ConnectionInterfaceContactGroupsInterface> instead"
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactGroupsInterface b/qt4/TelepathyQt4/ConnectionInterfaceContactGroupsInterface
new file mode 100644
index 000000000..ac3eb759a
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactGroupsInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactGroupsInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactGroupsInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactInfoInterface b/qt4/TelepathyQt4/ConnectionInterfaceContactInfoInterface
new file mode 100644
index 000000000..e4a6c0a8e
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactInfoInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactInfoInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactInfoInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactList b/qt4/TelepathyQt4/ConnectionInterfaceContactList
new file mode 100644
index 000000000..9f0aaf4f5
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactList
@@ -0,0 +1,17 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactList_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactList_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#ifdef TELEPATHY_QT4_DEPRECATED_WARNINGS
+#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt4/ConnectionInterfaceContactListInterface> instead"
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactListInterface b/qt4/TelepathyQt4/ConnectionInterfaceContactListInterface
new file mode 100644
index 000000000..43f67562e
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactListInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactListInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactListInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceContactsInterface b/qt4/TelepathyQt4/ConnectionInterfaceContactsInterface
new file mode 100644
index 000000000..1aaf814f8
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceContactsInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceContactsInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceContactsInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceLocationInterface b/qt4/TelepathyQt4/ConnectionInterfaceLocationInterface
new file mode 100644
index 000000000..20c948f4f
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceLocationInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceLocationInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceLocationInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceMailNotificationInterface b/qt4/TelepathyQt4/ConnectionInterfaceMailNotificationInterface
new file mode 100644
index 000000000..b34b3f488
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceMailNotificationInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceMailNotificationInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceMailNotificationInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfacePowerSaving b/qt4/TelepathyQt4/ConnectionInterfacePowerSaving
new file mode 100644
index 000000000..2bfaa186d
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfacePowerSaving
@@ -0,0 +1,17 @@
+#ifndef _TelepathyQt4_ConnectionInterfacePowerSaving_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfacePowerSaving_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#ifdef TELEPATHY_QT4_DEPRECATED_WARNINGS
+#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt4/ConnectionInterfacePowerSavingInterface> instead"
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfacePowerSavingInterface b/qt4/TelepathyQt4/ConnectionInterfacePowerSavingInterface
new file mode 100644
index 000000000..179ce896e
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfacePowerSavingInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfacePowerSavingInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfacePowerSavingInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfacePresenceInterface b/qt4/TelepathyQt4/ConnectionInterfacePresenceInterface
new file mode 100644
index 000000000..7f3c7d37d
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfacePresenceInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfacePresenceInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfacePresenceInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceRequestsInterface b/qt4/TelepathyQt4/ConnectionInterfaceRequestsInterface
new file mode 100644
index 000000000..b2e91056d
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceRequestsInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceRequestsInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceRequestsInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceServicePointInterface b/qt4/TelepathyQt4/ConnectionInterfaceServicePointInterface
new file mode 100644
index 000000000..1d82d8783
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceServicePointInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceServicePointInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceServicePointInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionInterfaceSimplePresenceInterface b/qt4/TelepathyQt4/ConnectionInterfaceSimplePresenceInterface
new file mode 100644
index 000000000..5a8ecf89e
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionInterfaceSimplePresenceInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionInterfaceSimplePresenceInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionInterfaceSimplePresenceInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionLowlevel b/qt4/TelepathyQt4/ConnectionLowlevel
new file mode 100644
index 000000000..3e771e068
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionLowlevel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionLowlevel_HEADER_GUARD
+#define _TelepathyQt4_ConnectionLowlevel_HEADER_GUARD
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection-lowlevel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionManager b/qt4/TelepathyQt4/ConnectionManager
new file mode 100644
index 000000000..40a3cca77
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionManager
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionManager_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionManager_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection-manager.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionManagerInterface b/qt4/TelepathyQt4/ConnectionManagerInterface
new file mode 100644
index 000000000..ac3eb34ca
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionManagerInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionManagerInterface_HEADER_GUARD_
+#define _TelepathyQt4_ConnectionManagerInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection-manager.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ConnectionManagerLowlevel b/qt4/TelepathyQt4/ConnectionManagerLowlevel
new file mode 100644
index 000000000..60a82b87d
--- /dev/null
+++ b/qt4/TelepathyQt4/ConnectionManagerLowlevel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ConnectionManagerLowlevel_HEADER_GUARD
+#define _TelepathyQt4_ConnectionManagerLowlevel_HEADER_GUARD
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/connection-manager-lowlevel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Constants b/qt4/TelepathyQt4/Constants
new file mode 100644
index 000000000..11be2eeb0
--- /dev/null
+++ b/qt4/TelepathyQt4/Constants
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Constants_HEADER_GUARD_
+#define _TelepathyQt4_Constants_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/constants.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Contact b/qt4/TelepathyQt4/Contact
new file mode 100644
index 000000000..78b2d4b2c
--- /dev/null
+++ b/qt4/TelepathyQt4/Contact
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Contact_HEADER_GUARD_
+#define _TelepathyQt4_Contact_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/contact.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ContactCapabilities b/qt4/TelepathyQt4/ContactCapabilities
new file mode 100644
index 000000000..758bdbb7f
--- /dev/null
+++ b/qt4/TelepathyQt4/ContactCapabilities
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ContactCapabilities_HEADER_GUARD_
+#define _TelepathyQt4_ContactCapabilities_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/contact-capabilities.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ContactFactory b/qt4/TelepathyQt4/ContactFactory
new file mode 100644
index 000000000..4df4372bd
--- /dev/null
+++ b/qt4/TelepathyQt4/ContactFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ContactFactory_HEADER_GUARD_
+#define _TelepathyQt4_ContactFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/contact-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ContactManager b/qt4/TelepathyQt4/ContactManager
new file mode 100644
index 000000000..dfd1723ab
--- /dev/null
+++ b/qt4/TelepathyQt4/ContactManager
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ContactManager_HEADER_GUARD_
+#define _TelepathyQt4_ContactManager_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/contact-manager.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ContactMessenger b/qt4/TelepathyQt4/ContactMessenger
new file mode 100644
index 000000000..d57668bdc
--- /dev/null
+++ b/qt4/TelepathyQt4/ContactMessenger
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ContactMessenger_HEADER_GUARD_
+#define _TelepathyQt4_ContactMessenger_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/contact-messenger.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ContactSearchChannel b/qt4/TelepathyQt4/ContactSearchChannel
new file mode 100644
index 000000000..a5e6fa223
--- /dev/null
+++ b/qt4/TelepathyQt4/ContactSearchChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ContactSearchChannel_HEADER_GUARD_
+#define _TelepathyQt4_ContactSearchChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/contact-search-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/DBus b/qt4/TelepathyQt4/DBus
new file mode 100644
index 000000000..f449e60e0
--- /dev/null
+++ b/qt4/TelepathyQt4/DBus
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_DBus_HEADER_GUARD_
+#define _TelepathyQt4_DBus_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/DBusDaemonInterface b/qt4/TelepathyQt4/DBusDaemonInterface
new file mode 100644
index 000000000..7164d1dd5
--- /dev/null
+++ b/qt4/TelepathyQt4/DBusDaemonInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_DBusDaemonInterface_HEADER_GUARD_
+#define _TelepathyQt4_DBusDaemonInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/DBusProxy b/qt4/TelepathyQt4/DBusProxy
new file mode 100644
index 000000000..ab4f5aa26
--- /dev/null
+++ b/qt4/TelepathyQt4/DBusProxy
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_DBusProxy_HEADER_GUARD_
+#define _TelepathyQt4_DBusProxy_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus-proxy.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/DBusProxyFactory b/qt4/TelepathyQt4/DBusProxyFactory
new file mode 100644
index 000000000..4af3cdf61
--- /dev/null
+++ b/qt4/TelepathyQt4/DBusProxyFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_DBusProxyFactory_HEADER_GUARD_
+#define _TelepathyQt4_DBusProxyFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus-proxy-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Debug b/qt4/TelepathyQt4/Debug
new file mode 100644
index 000000000..fe5aab662
--- /dev/null
+++ b/qt4/TelepathyQt4/Debug
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Debug_HEADER_GUARD_
+#define _TelepathyQt4_Debug_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/debug.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Farsight/CMakeLists.txt b/qt4/TelepathyQt4/Farsight/CMakeLists.txt
new file mode 100644
index 000000000..f32831e42
--- /dev/null
+++ b/qt4/TelepathyQt4/Farsight/CMakeLists.txt
@@ -0,0 +1,52 @@
+if(FARSIGHT_COMPONENTS_FOUND)
+ include_directories(${TELEPATHY_FARSIGHT_INCLUDE_DIR}
+ ${GSTREAMER_INCLUDE_DIR}
+ ${GLIB2_INCLUDE_DIR}
+ ${LIBXML2_INCLUDE_DIR}
+ ${DBUS_INCLUDE_DIR})
+
+ # It gets inherited from the previous directory, hence it has to be removed explicitely
+ remove_definitions(-DBUILDING_TELEPATHY_QT4)
+ # We are building Telepathy-Qt4-Farsight
+ add_definitions(-DBUILDING_TELEPATHY_QT4_FARSIGHT -DQT_NO_KEYWORDS)
+
+ set(telepathy_qt4_farsight_SRCS
+ channel.cpp)
+
+ set(telepathy_qt4_farsight_HEADERS
+ Channel
+ channel.h
+ global.h)
+
+ # Create the library
+ if (ENABLE_COMPILER_COVERAGE)
+ add_library(telepathy-qt4-farsight STATIC ${telepathy_qt4_farsight_SRCS})
+ else (ENABLE_COMPILER_COVERAGE)
+ add_library(telepathy-qt4-farsight SHARED ${telepathy_qt4_farsight_SRCS})
+ endif (ENABLE_COMPILER_COVERAGE)
+ # Link
+ target_link_libraries(telepathy-qt4-farsight
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ ${TELEPATHY_FARSIGHT_LIBRARIES}
+ ${GSTREAMER_INTERFACE_LIBRARY}
+ telepathy-qt4)
+
+ # Set the correct version number
+ set_target_properties(telepathy-qt4-farsight PROPERTIES
+ SOVERSION ${TP_QT4_ABI_VERSION}
+ VERSION ${TP_QT4_LIBRARY_VERSION})
+
+ # Install the library - watch out for the correct components
+ if (WIN32)
+ install(TARGETS telepathy-qt4-farsight RUNTIME DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight_headers)
+ else (WIN32)
+ install(TARGETS telepathy-qt4-farsight LIBRARY DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight_headers)
+ endif (WIN32)
+
+ # Install headers
+ install(FILES ${telepathy_qt4_farsight_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/telepathy-1.0/TelepathyQt4/Farsight
+ COMPONENT farsight_headers)
+endif(FARSIGHT_COMPONENTS_FOUND)
diff --git a/qt4/TelepathyQt4/Farsight/Channel b/qt4/TelepathyQt4/Farsight/Channel
new file mode 100644
index 000000000..aa4c93ef5
--- /dev/null
+++ b/qt4/TelepathyQt4/Farsight/Channel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Farsight_Channel_HEADER_GUARD_
+#define _TelepathyQt4_Farsight_Channel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Farsight/channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Farsight/channel.cpp b/qt4/TelepathyQt4/Farsight/channel.cpp
new file mode 100644
index 000000000..069fb8048
--- /dev/null
+++ b/qt4/TelepathyQt4/Farsight/channel.cpp
@@ -0,0 +1,87 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Farsight/Channel>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/StreamedMediaChannel>
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/connection.h>
+#include <telepathy-glib/dbus.h>
+
+namespace Tp
+{
+
+TfChannel *createFarsightChannel(const StreamedMediaChannelPtr &channel)
+{
+ if (!channel->handlerStreamingRequired()) {
+ warning() << "Handler streaming not required";
+ return 0;
+ }
+
+ TpDBusDaemon *dbus = tp_dbus_daemon_dup(0);
+
+ if (!dbus) {
+ warning() << "Unable to connect to D-Bus";
+ return 0;
+ }
+
+ ConnectionPtr connection = channel->connection();
+
+ TpConnection *gconnection = tp_connection_new(dbus,
+ connection->busName().toAscii(),
+ connection->objectPath().toAscii(),
+ 0);
+ g_object_unref(dbus);
+ dbus = 0;
+
+ if (!gconnection) {
+ warning() << "Unable to construct TpConnection";
+ return 0;
+ }
+
+ TpChannel *gchannel = tp_channel_new(gconnection,
+ channel->objectPath().toAscii(),
+ TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ (TpHandleType) channel->targetHandleType(),
+ channel->targetHandle(),
+ 0);
+ g_object_unref(gconnection);
+ gconnection = 0;
+
+ if (!gchannel) {
+ warning() << "Unable to construct TpChannel";
+ return 0;
+ }
+
+ TfChannel *ret = tf_channel_new(gchannel);
+ g_object_unref(gchannel);
+ gchannel = 0;
+
+ return ret;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/Farsight/channel.h b/qt4/TelepathyQt4/Farsight/channel.h
new file mode 100644
index 000000000..8476d0d75
--- /dev/null
+++ b/qt4/TelepathyQt4/Farsight/channel.h
@@ -0,0 +1,43 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_Farsight_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_Farsight_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Farsight/global.h>
+
+#include <TelepathyQt4/Types>
+
+#include <telepathy-farsight/channel.h>
+
+namespace Tp
+{
+
+TELEPATHY_QT4_FS_EXPORT TfChannel *createFarsightChannel(const StreamedMediaChannelPtr &channel);
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/Farsight/global.h b/qt4/TelepathyQt4/Farsight/global.h
new file mode 100644
index 000000000..f3754ae31
--- /dev/null
+++ b/qt4/TelepathyQt4/Farsight/global.h
@@ -0,0 +1,46 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_Farsight_global_h_HEADER_GUARD_
+#define _TelepathyQt4_Farsight_global_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <QtGlobal>
+
+#ifdef BUILDING_TELEPATHY_QT4_FARSIGHT
+# define TELEPATHY_QT4_FS_EXPORT Q_DECL_EXPORT
+#else
+# define TELEPATHY_QT4_FS_EXPORT Q_DECL_IMPORT
+#endif
+
+#if !defined(Q_OS_WIN) && defined(QT_VISIBILITY_AVAILABLE)
+# define TELEPATHY_QT4_FS_NO_EXPORT __attribute__((visibility("hidden")))
+#endif
+
+#ifndef TELEPATHY_QT4_FS_NO_EXPORT
+# define TELEPATHY_QT4_FS_NO_EXPORT
+#endif
+
+#endif
diff --git a/qt4/TelepathyQt4/Feature b/qt4/TelepathyQt4/Feature
new file mode 100644
index 000000000..fa80e07f9
--- /dev/null
+++ b/qt4/TelepathyQt4/Feature
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Feature_HEADER_GUARD_
+#define _TelepathyQt4_Feature_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/feature.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Features b/qt4/TelepathyQt4/Features
new file mode 100644
index 000000000..0e864c798
--- /dev/null
+++ b/qt4/TelepathyQt4/Features
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Features_HEADER_GUARD_
+#define _TelepathyQt4_Features_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/feature.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/FileTransferChannel b/qt4/TelepathyQt4/FileTransferChannel
new file mode 100644
index 000000000..57f6b944f
--- /dev/null
+++ b/qt4/TelepathyQt4/FileTransferChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_FileTransferChannel_HEADER_GUARD_
+#define _TelepathyQt4_FileTransferChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/file-transfer-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/FileTransferChannelCreationProperties b/qt4/TelepathyQt4/FileTransferChannelCreationProperties
new file mode 100644
index 000000000..c6aaad71f
--- /dev/null
+++ b/qt4/TelepathyQt4/FileTransferChannelCreationProperties
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_FileTransferChannelCreationProperties_HEADER_GUARD_
+#define _TelepathyQt4_FileTransferChannelCreationProperties_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/file-transfer-channel-creation-properties.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Filter b/qt4/TelepathyQt4/Filter
new file mode 100644
index 000000000..291514644
--- /dev/null
+++ b/qt4/TelepathyQt4/Filter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Filter_HEADER_GUARD_
+#define _TelepathyQt4_Filter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/FixedFeatureFactory b/qt4/TelepathyQt4/FixedFeatureFactory
new file mode 100644
index 000000000..555301c37
--- /dev/null
+++ b/qt4/TelepathyQt4/FixedFeatureFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_FixedFeatureFactory_HEADER_GUARD_
+#define _TelepathyQt4_FixedFeatureFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/fixed-feature-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/GenericCapabilityFilter b/qt4/TelepathyQt4/GenericCapabilityFilter
new file mode 100644
index 000000000..074f59535
--- /dev/null
+++ b/qt4/TelepathyQt4/GenericCapabilityFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_GenericCapabilityFilter_HEADER_GUARD_
+#define _TelepathyQt4_GenericCapabilityFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/generic-capability-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/GenericPropertyFilter b/qt4/TelepathyQt4/GenericPropertyFilter
new file mode 100644
index 000000000..57f3235d0
--- /dev/null
+++ b/qt4/TelepathyQt4/GenericPropertyFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_GenericPropertyFilter_HEADER_GUARD_
+#define _TelepathyQt4_GenericPropertyFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/generic-property-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Global b/qt4/TelepathyQt4/Global
new file mode 100644
index 000000000..3456c70bf
--- /dev/null
+++ b/qt4/TelepathyQt4/Global
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Global_HEADER_GUARD_
+#define _TelepathyQt4_Global_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/global.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/HandledChannelNotifier b/qt4/TelepathyQt4/HandledChannelNotifier
new file mode 100644
index 000000000..1f99c8cd1
--- /dev/null
+++ b/qt4/TelepathyQt4/HandledChannelNotifier
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_HandledChannelNotifier_HEADER_GUARD_
+#define _TelepathyQt4_HandledChannelNotifier_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/handled-channel-notifier.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/IncomingFileTransferChannel b/qt4/TelepathyQt4/IncomingFileTransferChannel
new file mode 100644
index 000000000..4392d3e3b
--- /dev/null
+++ b/qt4/TelepathyQt4/IncomingFileTransferChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_IncomingFileTransferChannel_HEADER_GUARD_
+#define _TelepathyQt4_IncomingFileTransferChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/incoming-file-transfer-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/IncomingStreamTubeChannel b/qt4/TelepathyQt4/IncomingStreamTubeChannel
new file mode 100644
index 000000000..eb573bbc2
--- /dev/null
+++ b/qt4/TelepathyQt4/IncomingStreamTubeChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_IncomingStreamTubeChannel_HEADER_GUARD_
+#define _TelepathyQt4_IncomingStreamTubeChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/incoming-stream-tube-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/IntrospectableInterface b/qt4/TelepathyQt4/IntrospectableInterface
new file mode 100644
index 000000000..f5cb393f0
--- /dev/null
+++ b/qt4/TelepathyQt4/IntrospectableInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_IntrospectableInterface_HEADER_GUARD_
+#define _TelepathyQt4_IntrospectableInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/KeyFile b/qt4/TelepathyQt4/KeyFile
new file mode 100644
index 000000000..0839310ef
--- /dev/null
+++ b/qt4/TelepathyQt4/KeyFile
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_KeyFile_HEADER_GUARD_
+#define _TelepathyQt4_KeyFile_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/key-file.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/LocationInfo b/qt4/TelepathyQt4/LocationInfo
new file mode 100644
index 000000000..0e3977c78
--- /dev/null
+++ b/qt4/TelepathyQt4/LocationInfo
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_LocationInfo_HEADER_GUARD_
+#define _TelepathyQt4_LocationInfo_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/location-info.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ManagerFile b/qt4/TelepathyQt4/ManagerFile
new file mode 100644
index 000000000..11b4601f4
--- /dev/null
+++ b/qt4/TelepathyQt4/ManagerFile
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ManagerFile_HEADER_GUARD_
+#define _TelepathyQt4_ManagerFile_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/manager-file.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MediaSessionHandler b/qt4/TelepathyQt4/MediaSessionHandler
new file mode 100644
index 000000000..9c7a91960
--- /dev/null
+++ b/qt4/TelepathyQt4/MediaSessionHandler
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MediaSessionHandler_HEADER_GUARD_
+#define _TelepathyQt4_MediaSessionHandler_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/media-session-handler.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MediaSessionHandlerInterface b/qt4/TelepathyQt4/MediaSessionHandlerInterface
new file mode 100644
index 000000000..21f7fd052
--- /dev/null
+++ b/qt4/TelepathyQt4/MediaSessionHandlerInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MediaSessionHandlerInterface_HEADER_GUARD_
+#define _TelepathyQt4_MediaSessionHandlerInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/media-session-handler.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MediaStreamHandler b/qt4/TelepathyQt4/MediaStreamHandler
new file mode 100644
index 000000000..472d6d285
--- /dev/null
+++ b/qt4/TelepathyQt4/MediaStreamHandler
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MediaStreamHandler_HEADER_GUARD_
+#define _TelepathyQt4_MediaStreamHandler_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/media-stream-handler.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MediaStreamHandlerInterface b/qt4/TelepathyQt4/MediaStreamHandlerInterface
new file mode 100644
index 000000000..e1ef8c545
--- /dev/null
+++ b/qt4/TelepathyQt4/MediaStreamHandlerInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MediaStreamHandlerInterface_HEADER_GUARD_
+#define _TelepathyQt4_MediaStreamHandlerInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/media-stream-handler.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Message b/qt4/TelepathyQt4/Message
new file mode 100644
index 000000000..80d1519ce
--- /dev/null
+++ b/qt4/TelepathyQt4/Message
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Message_HEADER_GUARD_
+#define _TelepathyQt4_Message_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/message.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MessageContentPart b/qt4/TelepathyQt4/MessageContentPart
new file mode 100644
index 000000000..bcaa64a06
--- /dev/null
+++ b/qt4/TelepathyQt4/MessageContentPart
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MessageContentPart_HEADER_GUARD_
+#define _TelepathyQt4_MessageContentPart_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/message-content-part.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MessageContentPartList b/qt4/TelepathyQt4/MessageContentPartList
new file mode 100644
index 000000000..a05fe647c
--- /dev/null
+++ b/qt4/TelepathyQt4/MessageContentPartList
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MessageContentPartList_HEADER_GUARD_
+#define _TelepathyQt4_MessageContentPartList_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/message-content-part.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/MethodInvocationContext b/qt4/TelepathyQt4/MethodInvocationContext
new file mode 100644
index 000000000..aa4ae9bb4
--- /dev/null
+++ b/qt4/TelepathyQt4/MethodInvocationContext
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_MethodInvocationContext_HEADER_GUARD_
+#define _TelepathyQt4_MethodInvocationContext_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/method-invocation-context.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/NotFilter b/qt4/TelepathyQt4/NotFilter
new file mode 100644
index 000000000..2019f1793
--- /dev/null
+++ b/qt4/TelepathyQt4/NotFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_NotFilter_HEADER_GUARD_
+#define _TelepathyQt4_NotFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/not-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Object b/qt4/TelepathyQt4/Object
new file mode 100644
index 000000000..65a86c69a
--- /dev/null
+++ b/qt4/TelepathyQt4/Object
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Object_HEADER_GUARD_
+#define _TelepathyQt4_Object_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/object.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/OptionalInterfaceFactory b/qt4/TelepathyQt4/OptionalInterfaceFactory
new file mode 100644
index 000000000..d376c1c0a
--- /dev/null
+++ b/qt4/TelepathyQt4/OptionalInterfaceFactory
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_OptionalInterfaceFactory_HEADER_GUARD_
+#define _TelepathyQt4_OptionalInterfaceFactory_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/optional-interface-factory.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/OrFilter b/qt4/TelepathyQt4/OrFilter
new file mode 100644
index 000000000..3ef0a2f54
--- /dev/null
+++ b/qt4/TelepathyQt4/OrFilter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_OrFilter_HEADER_GUARD_
+#define _TelepathyQt4_OrFilter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/or-filter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/OutgoingFileTransferChannel b/qt4/TelepathyQt4/OutgoingFileTransferChannel
new file mode 100644
index 000000000..792225b05
--- /dev/null
+++ b/qt4/TelepathyQt4/OutgoingFileTransferChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_OutgoingFileTransferChannel_HEADER_GUARD_
+#define _TelepathyQt4_OutgoingFileTransferChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/outgoing-file-transfer-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/OutgoingStreamTubeChannel b/qt4/TelepathyQt4/OutgoingStreamTubeChannel
new file mode 100644
index 000000000..5fd21af55
--- /dev/null
+++ b/qt4/TelepathyQt4/OutgoingStreamTubeChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_OutgoingStreamTubeChannel_HEADER_GUARD_
+#define _TelepathyQt4_OutgoingStreamTubeChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/outgoing-stream-tube-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PeerInterface b/qt4/TelepathyQt4/PeerInterface
new file mode 100644
index 000000000..17165aaa4
--- /dev/null
+++ b/qt4/TelepathyQt4/PeerInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PeerInterface_HEADER_GUARD_
+#define _TelepathyQt4_PeerInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingAccount b/qt4/TelepathyQt4/PendingAccount
new file mode 100644
index 000000000..d1569ebc8
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingAccount
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingAccount_HEADER_GUARD_
+#define _TelepathyQt4_PendingAccount_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-account.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingChannel b/qt4/TelepathyQt4/PendingChannel
new file mode 100644
index 000000000..6f1bc9040
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingChannel_HEADER_GUARD_
+#define _TelepathyQt4_PendingChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingChannelRequest b/qt4/TelepathyQt4/PendingChannelRequest
new file mode 100644
index 000000000..567129ace
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingChannelRequest
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingChannelRequest_HEADER_GUARD_
+#define _TelepathyQt4_PendingChannelRequest_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-channel-request.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingComposite b/qt4/TelepathyQt4/PendingComposite
new file mode 100644
index 000000000..ea06ff640
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingComposite
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingComposite_HEADER_GUARD_
+#define _TelepathyQt4_PendingComposite_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-pending-operations.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingConnection b/qt4/TelepathyQt4/PendingConnection
new file mode 100644
index 000000000..db1f046ce
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingConnection
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingConnection_HEADER_GUARD_
+#define _TelepathyQt4_PendingConnection_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingContactAttributes b/qt4/TelepathyQt4/PendingContactAttributes
new file mode 100644
index 000000000..be05ad771
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingContactAttributes
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingContactAttributes_HEADER_GUARD_
+#define _TelepathyQt4_PendingContactAttributes_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-contact-attributes.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingContactInfo b/qt4/TelepathyQt4/PendingContactInfo
new file mode 100644
index 000000000..e2ace1d40
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingContactInfo
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingContactInfo_HEADER_GUARD_
+#define _TelepathyQt4_PendingContactInfo_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-contact-info.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingContacts b/qt4/TelepathyQt4/PendingContacts
new file mode 100644
index 000000000..a6b1fb696
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingContacts
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingContacts_HEADER_GUARD_
+#define _TelepathyQt4_PendingContacts_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-contacts.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingFailure b/qt4/TelepathyQt4/PendingFailure
new file mode 100644
index 000000000..6be765376
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingFailure
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingFailure_HEADER_GUARD_
+#define _TelepathyQt4_PendingFailure_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-pending-operations.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingHandles b/qt4/TelepathyQt4/PendingHandles
new file mode 100644
index 000000000..4697f604a
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingHandles
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingHandles_HEADER_GUARD_
+#define _TelepathyQt4_PendingHandles_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-handles.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingOperation b/qt4/TelepathyQt4/PendingOperation
new file mode 100644
index 000000000..cbcd995cf
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingOperation
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingOperation_HEADER_GUARD_
+#define _TelepathyQt4_PendingOperation_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-operation.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingReady b/qt4/TelepathyQt4/PendingReady
new file mode 100644
index 000000000..befae0181
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingReady
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingReady_HEADER_GUARD_
+#define _TelepathyQt4_PendingReady_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-ready.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingSendMessage b/qt4/TelepathyQt4/PendingSendMessage
new file mode 100644
index 000000000..5a4c2caf3
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingSendMessage
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingSendMessage_HEADER_GUARD_
+#define _TelepathyQt4_PendingSendMessage_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-send-message.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingStreamTubeConnection b/qt4/TelepathyQt4/PendingStreamTubeConnection
new file mode 100644
index 000000000..dc87287f0
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingStreamTubeConnection
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingStreamTubeConnection_HEADER_GUARD_
+#define _TelepathyQt4_PendingStreamTubeConnection_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-stream-tube-connection.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingStreamedMediaStreams b/qt4/TelepathyQt4/PendingStreamedMediaStreams
new file mode 100644
index 000000000..4db0fbac3
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingStreamedMediaStreams
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingStreamedMediaStreams_HEADER_GUARD_
+#define _TelepathyQt4_PendingStreamedMediaStreams_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/streamed-media-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingStringList b/qt4/TelepathyQt4/PendingStringList
new file mode 100644
index 000000000..529835a65
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingStringList
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingStringList_HEADER_GUARD_
+#define _TelepathyQt4_PendingStringList_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-string-list.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingSuccess b/qt4/TelepathyQt4/PendingSuccess
new file mode 100644
index 000000000..473f1767d
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingSuccess
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingSuccess_HEADER_GUARD_
+#define _TelepathyQt4_PendingSuccess_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-pending-operations.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingVariant b/qt4/TelepathyQt4/PendingVariant
new file mode 100644
index 000000000..d2473d35c
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingVariant
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingVariant_HEADER_GUARD_
+#define _TelepathyQt4_PendingVariant_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-variant.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingVariantMap b/qt4/TelepathyQt4/PendingVariantMap
new file mode 100644
index 000000000..da08bd944
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingVariantMap
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingVariantMap_HEADER_GUARD_
+#define _TelepathyQt4_PendingVariantMap_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/pending-variant-map.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PendingVoid b/qt4/TelepathyQt4/PendingVoid
new file mode 100644
index 000000000..624e4776b
--- /dev/null
+++ b/qt4/TelepathyQt4/PendingVoid
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PendingVoid_HEADER_GUARD_
+#define _TelepathyQt4_PendingVoid_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-pending-operations.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Presence b/qt4/TelepathyQt4/Presence
new file mode 100644
index 000000000..928954a7f
--- /dev/null
+++ b/qt4/TelepathyQt4/Presence
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Presence_HEADER_GUARD_
+#define _TelepathyQt4_Presence_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/presence.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PresenceSpec b/qt4/TelepathyQt4/PresenceSpec
new file mode 100644
index 000000000..b50fc2f48
--- /dev/null
+++ b/qt4/TelepathyQt4/PresenceSpec
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PresenceSpec_HEADER_GUARD_
+#define _TelepathyQt4_PresenceSpec_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/presence.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PresenceSpecList b/qt4/TelepathyQt4/PresenceSpecList
new file mode 100644
index 000000000..d9cd9d502
--- /dev/null
+++ b/qt4/TelepathyQt4/PresenceSpecList
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PresenceSpecList_HEADER_GUARD_
+#define _TelepathyQt4_PresenceSpecList_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/presence.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Profile b/qt4/TelepathyQt4/Profile
new file mode 100644
index 000000000..b4fb51aac
--- /dev/null
+++ b/qt4/TelepathyQt4/Profile
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Profile_HEADER_GUARD_
+#define _TelepathyQt4_Profile_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/profile.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ProfileManager b/qt4/TelepathyQt4/ProfileManager
new file mode 100644
index 000000000..d244b96d7
--- /dev/null
+++ b/qt4/TelepathyQt4/ProfileManager
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ProfileManager_HEADER_GUARD_
+#define _TelepathyQt4_ProfileManager_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/profile-manager.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Properties b/qt4/TelepathyQt4/Properties
new file mode 100644
index 000000000..e9f1581ca
--- /dev/null
+++ b/qt4/TelepathyQt4/Properties
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Properties_HEADER_GUARD_
+#define _TelepathyQt4_Properties_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/properties.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PropertiesInterface b/qt4/TelepathyQt4/PropertiesInterface
new file mode 100644
index 000000000..bebc2299c
--- /dev/null
+++ b/qt4/TelepathyQt4/PropertiesInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PropertiesInterface_HEADER_GUARD_
+#define _TelepathyQt4_PropertiesInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/PropertiesInterfaceInterface b/qt4/TelepathyQt4/PropertiesInterfaceInterface
new file mode 100644
index 000000000..eb47f947b
--- /dev/null
+++ b/qt4/TelepathyQt4/PropertiesInterfaceInterface
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_PropertiesInterfaceInterface_HEADER_GUARD_
+#define _TelepathyQt4_PropertiesInterfaceInterface_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/properties.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ProtocolInfo b/qt4/TelepathyQt4/ProtocolInfo
new file mode 100644
index 000000000..93c98a4bf
--- /dev/null
+++ b/qt4/TelepathyQt4/ProtocolInfo
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ProtocolInfo_HEADER_GUARD_
+#define _TelepathyQt4_ProtocolInfo_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/protocol-info.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ProtocolParameter b/qt4/TelepathyQt4/ProtocolParameter
new file mode 100644
index 000000000..f63cf4a17
--- /dev/null
+++ b/qt4/TelepathyQt4/ProtocolParameter
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ProtocolParameter_HEADER_GUARD_
+#define _TelepathyQt4_ProtocolParameter_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/protocol-parameter.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ReadinessHelper b/qt4/TelepathyQt4/ReadinessHelper
new file mode 100644
index 000000000..368a8a523
--- /dev/null
+++ b/qt4/TelepathyQt4/ReadinessHelper
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ReadinessHelper_HEADER_GUARD_
+#define _TelepathyQt4_ReadinessHelper_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/readiness-helper.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ReadyObject b/qt4/TelepathyQt4/ReadyObject
new file mode 100644
index 000000000..ad6b261a1
--- /dev/null
+++ b/qt4/TelepathyQt4/ReadyObject
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ReadyObject_HEADER_GUARD_
+#define _TelepathyQt4_ReadyObject_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/ready-object.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ReceivedMessage b/qt4/TelepathyQt4/ReceivedMessage
new file mode 100644
index 000000000..c8119c194
--- /dev/null
+++ b/qt4/TelepathyQt4/ReceivedMessage
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_ReceivedMessage_HEADER_GUARD_
+#define _TelepathyQt4_ReceivedMessage_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/message.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/RefCounted b/qt4/TelepathyQt4/RefCounted
new file mode 100644
index 000000000..7a6d6e4b8
--- /dev/null
+++ b/qt4/TelepathyQt4/RefCounted
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_RefCounted_HEADER_GUARD_
+#define _TelepathyQt4_RefCounted_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/shared-ptr.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/ReferencedHandles b/qt4/TelepathyQt4/ReferencedHandles
new file mode 100644
index 000000000..53285afef
--- /dev/null
+++ b/qt4/TelepathyQt4/ReferencedHandles
@@ -0,0 +1,12 @@
+#ifndef _TelepathyQt4_ReferencedHandles_HEADER_GUARD_
+#define _TelepathyQt4_ReferencedHandles_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/referenced-handles.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
diff --git a/qt4/TelepathyQt4/ReferencedHandlesIterator b/qt4/TelepathyQt4/ReferencedHandlesIterator
new file mode 100644
index 000000000..2e49aabaf
--- /dev/null
+++ b/qt4/TelepathyQt4/ReferencedHandlesIterator
@@ -0,0 +1,6 @@
+#ifndef _TelepathyQt4_ReferencedHandlesIterator_HEADER_GUARD_
+#define _TelepathyQt4_ReferencedHandlesIterator_HEADER_GUARD_
+
+#include <TelepathyQt4/referenced-handles.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/RequestableChannelClassSpec b/qt4/TelepathyQt4/RequestableChannelClassSpec
new file mode 100644
index 000000000..692ead68e
--- /dev/null
+++ b/qt4/TelepathyQt4/RequestableChannelClassSpec
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_RequestableChannelClassSpec_HEADER_GUARD_
+#define _TelepathyQt4_RequestableChannelClassSpec_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/requestable-channel-class-spec.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/RequestableChannelClassSpecList b/qt4/TelepathyQt4/RequestableChannelClassSpecList
new file mode 100644
index 000000000..e86ac7c7f
--- /dev/null
+++ b/qt4/TelepathyQt4/RequestableChannelClassSpecList
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_RequestableChannelClassSpecList_HEADER_GUARD_
+#define _TelepathyQt4_RequestableChannelClassSpecList_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/requestable-channel-class-spec.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/RoomListChannel b/qt4/TelepathyQt4/RoomListChannel
new file mode 100644
index 000000000..4b953b693
--- /dev/null
+++ b/qt4/TelepathyQt4/RoomListChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_RoomListChannel_HEADER_GUARD_
+#define _TelepathyQt4_RoomListChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/room-list-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/SharedPtr b/qt4/TelepathyQt4/SharedPtr
new file mode 100644
index 000000000..0ff96a43b
--- /dev/null
+++ b/qt4/TelepathyQt4/SharedPtr
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_SharedPtr_HEADER_GUARD_
+#define _TelepathyQt4_SharedPtr_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/shared-ptr.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/SimpleCallObserver b/qt4/TelepathyQt4/SimpleCallObserver
new file mode 100644
index 000000000..e12dbea35
--- /dev/null
+++ b/qt4/TelepathyQt4/SimpleCallObserver
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_SimpleCallObserver_HEADER_GUARD_
+#define _TelepathyQt4_SimpleCallObserver_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-call-observer.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/SimpleObserver b/qt4/TelepathyQt4/SimpleObserver
new file mode 100644
index 000000000..7b42ac28f
--- /dev/null
+++ b/qt4/TelepathyQt4/SimpleObserver
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_SimpleObserver_HEADER_GUARD_
+#define _TelepathyQt4_SimpleObserver_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-observer.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/SimpleTextObserver b/qt4/TelepathyQt4/SimpleTextObserver
new file mode 100644
index 000000000..2817bc4c1
--- /dev/null
+++ b/qt4/TelepathyQt4/SimpleTextObserver
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_SimpleTextObserver_HEADER_GUARD_
+#define _TelepathyQt4_SimpleTextObserver_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/simple-text-observer.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StatefulDBusProxy b/qt4/TelepathyQt4/StatefulDBusProxy
new file mode 100644
index 000000000..cafcb85d5
--- /dev/null
+++ b/qt4/TelepathyQt4/StatefulDBusProxy
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StatefulDBusProxy_HEADER_GUARD_
+#define _TelepathyQt4_StatefulDBusProxy_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus-proxy.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StatelessDBusProxy b/qt4/TelepathyQt4/StatelessDBusProxy
new file mode 100644
index 000000000..be9465f8d
--- /dev/null
+++ b/qt4/TelepathyQt4/StatelessDBusProxy
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StatelessDBusProxy_HEADER_GUARD_
+#define _TelepathyQt4_StatelessDBusProxy_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/dbus-proxy.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StreamTubeChannel b/qt4/TelepathyQt4/StreamTubeChannel
new file mode 100644
index 000000000..0e538bd1e
--- /dev/null
+++ b/qt4/TelepathyQt4/StreamTubeChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StreamTubeChannel_HEADER_GUARD_
+#define _TelepathyQt4_StreamTubeChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/stream-tube-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StreamTubeClient b/qt4/TelepathyQt4/StreamTubeClient
new file mode 100644
index 000000000..51f74c430
--- /dev/null
+++ b/qt4/TelepathyQt4/StreamTubeClient
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StreamTubeClient_HEADER_GUARD_
+#define _TelepathyQt4_StreamTubeClient_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/stream-tube-client.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StreamTubeServer b/qt4/TelepathyQt4/StreamTubeServer
new file mode 100644
index 000000000..533611101
--- /dev/null
+++ b/qt4/TelepathyQt4/StreamTubeServer
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StreamTubeServer_HEADER_GUARD_
+#define _TelepathyQt4_StreamTubeServer_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/stream-tube-server.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StreamedMediaChannel b/qt4/TelepathyQt4/StreamedMediaChannel
new file mode 100644
index 000000000..f64c4038f
--- /dev/null
+++ b/qt4/TelepathyQt4/StreamedMediaChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StreamedMediaChannel_HEADER_GUARD_
+#define _TelepathyQt4_StreamedMediaChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/streamed-media-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/StreamedMediaStream b/qt4/TelepathyQt4/StreamedMediaStream
new file mode 100644
index 000000000..704afc237
--- /dev/null
+++ b/qt4/TelepathyQt4/StreamedMediaStream
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_StreamedMediaStream_HEADER_GUARD_
+#define _TelepathyQt4_StreamedMediaStream_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/streamed-media-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/TelepathyQt4-uninstalled.pc.in b/qt4/TelepathyQt4/TelepathyQt4-uninstalled.pc.in
new file mode 100644
index 000000000..cc8336a09
--- /dev/null
+++ b/qt4/TelepathyQt4/TelepathyQt4-uninstalled.pc.in
@@ -0,0 +1,11 @@
+prefix=/nonexistent
+exec_prefix=/nonexistent
+abs_top_builddir=${CMAKE_BINARY_DIR}
+abs_top_srcdir=${CMAKE_SOURCE_DIR}
+
+Name: Telepathy-Qt4 (uninstalled copy)
+Description: Qt4 utility library for the Telepathy framework
+Version: ${PACKAGE_VERSION}
+Requires.private: QtCore >= 4.5, QtDBus >= 4.5, QtNetwork >= 4.5
+Libs: ${CMAKE_BINARY_DIR}/TelepathyQt4/libtelepathy-qt4.so
+Cflags: -I${CMAKE_SOURCE_DIR} -I${CMAKE_BINARY_DIR}
diff --git a/qt4/TelepathyQt4/TelepathyQt4.pc.in b/qt4/TelepathyQt4/TelepathyQt4.pc.in
new file mode 100644
index 000000000..d3fb65186
--- /dev/null
+++ b/qt4/TelepathyQt4/TelepathyQt4.pc.in
@@ -0,0 +1,11 @@
+prefix=${CMAKE_INSTALL_PREFIX}
+exec_prefix=${CMAKE_INSTALL_PREFIX}
+libdir=${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}
+includedir=${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}
+
+Name: Telepathy-Qt4
+Description: Qt4 utility library for the Telepathy framework
+Version: ${PACKAGE_VERSION}
+Requires.private: QtCore >= 4.5, QtDBus >= 4.5, QtNetwork >= 4.5
+Libs: -L${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} -ltelepathy-qt4
+Cflags: -I${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}/telepathy-1.0
diff --git a/qt4/TelepathyQt4/TelepathyQt4Farsight-uninstalled.pc.in b/qt4/TelepathyQt4/TelepathyQt4Farsight-uninstalled.pc.in
new file mode 100644
index 000000000..38cb8379c
--- /dev/null
+++ b/qt4/TelepathyQt4/TelepathyQt4Farsight-uninstalled.pc.in
@@ -0,0 +1,11 @@
+prefix=/nonexistent
+exec_prefix=/nonexistent
+abs_top_builddir=${CMAKE_BINARY_DIR}
+abs_top_srcdir=${CMAKE_SOURCE_DIR}
+
+Name: Telepathy-Qt4-Farsight (uninstalled copy)
+Description: Qt4 Telepathy Farsight utility library for the Telepathy framework
+Version: ${PACKAGE_VERSION}
+Requires.private: QtCore >= 4.5, QtDBus >= 4.5, telepathy-glib >= 0.7.28, telepathy-farsight >= 0.0.4, TelepathyQt4 = ${PACKAGE_VERSION}
+Libs: ${CMAKE_BINARY_DIR}/TelepathyQt4/Farsight/libtelepathy-qt4-farsight.so
+Cflags: -I${CMAKE_SOURCE_DIR} -I${CMAKE_BINARY_DIR}
diff --git a/qt4/TelepathyQt4/TelepathyQt4Farsight.pc.in b/qt4/TelepathyQt4/TelepathyQt4Farsight.pc.in
new file mode 100644
index 000000000..48ef67804
--- /dev/null
+++ b/qt4/TelepathyQt4/TelepathyQt4Farsight.pc.in
@@ -0,0 +1,11 @@
+prefix=${CMAKE_INSTALL_PREFIX}
+exec_prefix=${CMAKE_INSTALL_PREFIX}
+libdir=${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}
+includedir=${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}
+
+Name: Telepathy-Qt4-Farsight
+Description: Qt4 Telepathy Farsight utility library for the Telepathy framework
+Version: ${PACKAGE_VERSION}
+Requires.private: QtCore >= 4.5, QtDBus >= 4.5, telepathy-glib >= 0.7.28, telepathy-farsight >= 0.0.4, TelepathyQt4 = ${PACKAGE_VERSION}
+Libs: -L${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} -ltelepathy-qt4-farsight
+Cflags: -I${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}/telepathy-1.0
diff --git a/qt4/TelepathyQt4/TextChannel b/qt4/TelepathyQt4/TextChannel
new file mode 100644
index 000000000..3f76400d6
--- /dev/null
+++ b/qt4/TelepathyQt4/TextChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_TextChannel_HEADER_GUARD_
+#define _TelepathyQt4_TextChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/text-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/TubeChannel b/qt4/TelepathyQt4/TubeChannel
new file mode 100644
index 000000000..c4c6c3630
--- /dev/null
+++ b/qt4/TelepathyQt4/TubeChannel
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_TubeChannel_HEADER_GUARD_
+#define _TelepathyQt4_TubeChannel_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/tube-channel.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp: \ No newline at end of file
diff --git a/qt4/TelepathyQt4/Types b/qt4/TelepathyQt4/Types
new file mode 100644
index 000000000..0bd48cc45
--- /dev/null
+++ b/qt4/TelepathyQt4/Types
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Types_HEADER_GUARD_
+#define _TelepathyQt4_Types_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/types.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/Utils b/qt4/TelepathyQt4/Utils
new file mode 100644
index 000000000..4b84a6fb3
--- /dev/null
+++ b/qt4/TelepathyQt4/Utils
@@ -0,0 +1,13 @@
+#ifndef _TelepathyQt4_Utils_HEADER_GUARD_
+#define _TelepathyQt4_Utils_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#define IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/utils.h>
+
+#undef IN_TELEPATHY_QT4_HEADER
+
+#endif
+// vim:set ft=cpp:
diff --git a/qt4/TelepathyQt4/abstract-client.cpp b/qt4/TelepathyQt4/abstract-client.cpp
new file mode 100644
index 000000000..8153faa13
--- /dev/null
+++ b/qt4/TelepathyQt4/abstract-client.cpp
@@ -0,0 +1,988 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AbstractClient>
+
+#include <QSharedData>
+#include <QString>
+
+#include <TelepathyQt4/ChannelClassSpecList>
+
+namespace Tp
+{
+
+/**
+ * \class AbstractClient
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClient>
+ *
+ * \brief The AbstractClient class represents a Telepathy client.
+ *
+ * Clients are programs used to process channels, approving, handling or
+ * observing them. User interface processes are the obvious example of clients,
+ * but they can provide other functionality, such as address-book
+ * synchronization, message logging, etc.
+ *
+ * Each client is either an observer, an approver, a handler, or some
+ * combination of these.
+ *
+ * Clients can be activatable services (those with a D-Bus .service file)
+ * so that they can run in response to channel creation, or non-activatable
+ * services (those that do not register a D-Bus .service file
+ * for their well-known name, but do request it at runtime) so
+ * that they can process channels, but only if they are already
+ * running - for instance, a full-screen media center application might do this.
+ *
+ * As an optimization, service-activatable clients should install a file
+ * $XDG_DATA_DIRS/telepathy/clients/clientname.client containing a cached version
+ * of their immutable properties. The syntax of these files is documented in the <a
+ * href="http://telepathy.freedesktop.org/spec/org.freedesktop.Telepathy.Client.html">
+ * Telepathy specification</a>.
+ *
+ * Non-activatable clients may install a .client file, but there's not much
+ * point in them doing so.
+ *
+ * This is a base class and should not be used directly, use the
+ * specialized classes AbstractClientObserver, AbstractClientApprover and
+ * AbstractClientHandler instead.
+ *
+ * If the same process wants to be either a mix of observer, approver and
+ * handler, or a combination of those it can multiple inherit the specialized
+ * abstract classes.
+ *
+ * \sa AbstractClientObserver, AbstractClientApprover, AbstractClientHandler
+ */
+
+/**
+ * Construct a new AbstractClient object.
+ *
+ * Note that this is a base class and should not be used directly, use the
+ * specialized classes AbstractClientObserver, AbstractClientApprover and
+ * AbstractClientHandler instead.
+ */
+AbstractClient::AbstractClient()
+{
+}
+
+/**
+ * Class destructor.
+ */
+AbstractClient::~AbstractClient()
+{
+}
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractClientObserver::Private
+{
+ Private(const ChannelClassList &channelFilter, bool shouldRecover)
+ : channelFilter(channelFilter),
+ shouldRecover(shouldRecover)
+ {
+ }
+
+ ChannelClassList channelFilter;
+ bool shouldRecover;
+};
+
+/**
+ * \class AbstractClientObserver
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClientObserver>
+ *
+ * \brief The AbstractClientObserver class represents a Telepathy observer.
+ *
+ * Observers are clients that monitor the creation of new channels.
+ * This functionality can be used for things like message logging.
+ *
+ * Observers should not modify the state of a channel except via user
+ * interaction.
+ *
+ * Observers must not carry out actions that exactly one process must take
+ * responsibility for (e.g. acknowledging text messages, or carrying out
+ * the actual file transfer), since arbitrarily many observers can be
+ * activated for each channel. The handler is responsible for such tasks.
+ *
+ * Handlers may, of course, delegate responsibility for these tasks to other
+ * clients (including those run as observers), but this must be done
+ * explicitly via a request from the handler to the observer.
+ *
+ * Whenever a collection of new channels is signalled, the channel dispatcher
+ * will notify all running or activatable observers whose filter indicates that
+ * they are interested in some of the channels.
+ *
+ * Observers are activated for all channels in which they have registered an
+ * interest - incoming, outgoing or automatically created - although of course
+ * the filter property can be set to filter specific channels.
+ *
+ * To become an observer one should inherit AbstractClientObserver and
+ * implement the pure virtual observeChannels() method. After that the object
+ * representing the observer must be registered using
+ * ClientRegistrar::registerClient().
+ *
+ * When new channels in which the observer has registered an interest are
+ * announced, the method observeChannels() is invoked. All observers are
+ * notified simultaneously.
+ *
+ * \section observer_usage_sec Usage
+ *
+ * \subsection observer_create_sec Implementing an observer
+ *
+ * \code
+ *
+ * class MyObserver : public AbstractClientObserver
+ * {
+ * public:
+ * MyObserver(const ChannelClassSpecList &channelFilter);
+ * ~MyObserver() { }
+ *
+ * void observeChannels(const MethodInvocationContextPtr<> &context,
+ * const AccountPtr &account,
+ * const ConnectionPtr &connection,
+ * const QList<ChannelPtr> &channels,
+ * const ChannelDispatchOperationPtr &dispatchOperation,
+ * const QList<ChannelRequestPtr> &requestsSatisfied,
+ * const AbstractClientObserver::ObserverInfo &observerInfo);
+ * };
+ *
+ * MyObserver::MyObserver(const ChannelClassSpecList &channelFilter)
+ * : AbstractClientObserver(channelFilter)
+ * {
+ * }
+ *
+ * void MyObserver::observeChannels(const MethodInvocationContextPtr<> &context,
+ * const AccountPtr &account,
+ * const ConnectionPtr &connection,
+ * const QList<ChannelPtr> &channels,
+ * const ChannelDispatchOperationPtr &dispatchOperation,
+ * const QList<ChannelRequestPtr> &requestsSatisfied,
+ * const AbstractClientObserver::ObserverInfo &observerInfo)
+ * {
+ * // do something, log messages, ...
+ *
+ * context->setFinished();
+ * }
+ *
+ * \endcode
+ *
+ * \subsection observer_register_sec Registering an observer
+ *
+ * \code
+ *
+ * ClientRegistrar registrar = ClientRegistrar::create();
+ * AbstractClientPtr observer = AbstractClientPtr::dynamicCast(
+ * SharedPtr<MyObserver>(new MyObserver(
+ * ChannelClassSpecList() << ChannelClassSpec::textChat())));
+ * registrar->registerClient(observer, "myobserver");
+ *
+ * \endcode
+ *
+ * \sa AbstractClient
+ */
+
+/**
+ * \class AbstractClientObserver::ObserverInfo
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClientObserver>
+ *
+ * \brief The AbstractClientObserver::ObserverInfo class provides a wrapper
+ * around the additional info about the channels passed to observeChannels().
+ *
+ * \sa AbstractClientObserver
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractClientObserver::ObserverInfo::Private : public QSharedData
+{
+ Private(const QVariantMap &info)
+ : info(info) {}
+
+ QVariantMap info;
+};
+
+AbstractClientObserver::ObserverInfo::ObserverInfo(const QVariantMap &info)
+ : mPriv(new Private(info))
+{
+}
+
+AbstractClientObserver::ObserverInfo::ObserverInfo(const ObserverInfo &other)
+ : mPriv(other.mPriv)
+{
+}
+
+AbstractClientObserver::ObserverInfo::~ObserverInfo()
+{
+}
+
+AbstractClientObserver::ObserverInfo &AbstractClientObserver::ObserverInfo::operator=(
+ const ObserverInfo &other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ mPriv = other.mPriv;
+ return *this;
+}
+
+QVariantMap AbstractClientObserver::ObserverInfo::allInfo() const
+{
+ return mPriv->info;
+}
+
+/**
+ * Construct a new AbstractClientObserver object.
+ *
+ * \param channelFilter A specification of the channels in which this observer
+ * is interested.
+ * \param shouldRecover Whether upon the startup of this observer,
+ * observeChannels() will be called for every already
+ * existing channel matching its observerChannelFilter().
+ */
+AbstractClientObserver::AbstractClientObserver(
+ const ChannelClassSpecList &channelFilter,
+ bool shouldRecover)
+ : mPriv(new Private(channelFilter.bareClasses(), shouldRecover))
+ // The channel filter is converted here to the low-level class so that any warnings are
+ // emitted immediately rather than only when the CD introspects this Client
+{
+}
+
+/**
+ * Class destructor.
+ */
+AbstractClientObserver::~AbstractClientObserver()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the property containing a specification of the channels that this
+ * channel observer is interested. The observeChannels() method should be called
+ * by the channel dispatcher whenever any of the newly created channels match
+ * this description.
+ *
+ * See <a
+ * href="http://telepathy.freedesktop.org/spec/org.freedesktop.Telepathy.Client.Observer.html#org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter">
+ * the Telepathy specification</a> for documentation about the allowed
+ * types and how to define filters.
+ *
+ * This property never changes while the observer process owns its client bus
+ * name. If an observer wants to add extra channels to its list of interests at
+ * runtime, it can register an additional client bus name using
+ * ClientRegistrar::registerClient().
+ * To remove those filters, it can release the bus name using
+ * ClientRegistrar::unregisterClient().
+ *
+ * The same principle is applied to approvers and handlers.
+ *
+ * \return A specification of the channels that this channel observer is
+ * interested as a list of ChannelClassSpec objects.
+ * \sa observeChannels()
+ */
+ChannelClassSpecList AbstractClientObserver::observerFilter() const
+{
+ return ChannelClassSpecList(mPriv->channelFilter);
+}
+
+/**
+ * Return whether upon the startup of this observer, observeChannels()
+ * will be called for every already existing channel matching its
+ * observerChannelFilter().
+ *
+ * \param \c true if this observer observerChannels() will be called for every
+ * already existing channel matching its observerChannelFilter(),
+ * \c false otherwise.
+ */
+bool AbstractClientObserver::shouldRecover() const
+{
+ return mPriv->shouldRecover;
+}
+
+/**
+ * \fn void AbstractClientObserver::observeChannels(
+ * const MethodInvocationContextPtr<> &context,
+ * const AccountPtr &account,
+ * const ConnectionPtr &connection,
+ * const QList<ChannelPtr> &channels,
+ * const ChannelDispatchOperationPtr &dispatchOperation,
+ * const QList<ChannelRequestPtr> &requestsSatisfied,
+ * const ObserverInfo &observerInfo);
+ *
+ * Called by the channel dispatcher when channels in which the observer has
+ * registered an interest are announced.
+ *
+ * If the announced channels contains channels that match the
+ * observerChannelFilter(), and some that do not, then only a subset of the
+ * channels (those that do match the filter) are passed to this method.
+ *
+ * If the channel dispatcher will split up the channels from a single
+ * announcement and dispatch them separately (for instance because no
+ * installed handler can handle all of them), it will call this method
+ * several times.
+ *
+ * The observer must not call MethodInvocationContext::setFinished() until it
+ * is ready for a handler for the channel to run (which may change the
+ * channel's state). For instance the received \a context object should be
+ * stored until this method is finished processing and then
+ * MethodInvocationContext::setFinished() or
+ * MethodInvocationContext::setFinishedWithError() should be called on the
+ * received \a context object.
+ *
+ * Specialized observers must reimplement this method.
+ *
+ * \param context A MethodInvocationContextPtr object that must be used to
+ * indicate whether this method finished processing.
+ * \param account The account with which the channels are associated.
+ * \param connection The connection with which the channels are associated.
+ * \param channels The channels to be observed.
+ * \param dispatchOperation The dispatch operation for these channels.
+ * The object will be invalid (DBusProxy::isValid()
+ * will be false) if there is no dispatch
+ * operation in place (because the channels were
+ * requested, not incoming).
+ * If the Observer calls
+ * ChannelDispatchOperation::claim() or
+ * ChannelDispatchOperation::handleWith() on this
+ * object, it must be careful to avoid deadlock, since
+ * these methods cannot return until the observer has
+ * returned from observeChannels().
+ * \param requestsSatisfied The requests satisfied by these channels.
+ * \param observerInfo Additional information about these channels.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractClientApprover::Private
+{
+ Private(const ChannelClassList &channelFilter)
+ : channelFilter(channelFilter)
+ {
+ }
+
+ ChannelClassList channelFilter;
+};
+
+/**
+ * \class AbstractClientApprover
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClientApprover>
+ *
+ * \brief The AbstractClientApprover class represents a Telepathy approver.
+ *
+ * Approvers are clients that notify the user that new channels have been
+ * created, and allow the user to accept or reject those channels.
+ *
+ * Approvers can also select which channel handler will be used for the channel,
+ * for instance by offering the user a list of possible handlers rather than
+ * just an accept/reject choice. However, the channel dispatcher must be able to
+ * prioritize possible handlers on its own using some reasonable heuristic,
+ * probably based on user configuration.
+ *
+ * It is possible (and useful) to have an approver and a channel handler in the
+ * same process; this is particularly useful if a channel handler wants to claim
+ * responsibility for particular channels itself.
+ *
+ * All approvers are notified simultaneously. For instance, in a desktop system,
+ * there might be one approver that displays a notification-area icon, one that
+ * is part of a contact list window and highlights contacts there, and one that
+ * is part of a full-screen media player.
+ *
+ * Any approver can approve the handling of a channel dispatch operation with a
+ * particular channel handler by calling the
+ * ChannelDispatchOperation::handleWith() method. Approvers can also attempt to
+ * claim channels by calling ChannelDispatchOperation::claim(). If this
+ * succeeds, the approver may handle the channels itself (if it is also a
+ * handler), or close the channels in order to reject them.
+ *
+ * Approvers wishing to reject channels should call the
+ * ChannelDispatchOperation::claim() method, then (if it succeeds) close the
+ * channels in any way they see fit.
+ *
+ * The first approver to reply gets its decision acted on; any other approvers
+ * that reply at approximately the same time will get an error, indicating that
+ * the channel has already been dealt with.
+ *
+ * Approvers should usually prompt the user and ask for confirmation, rather
+ * than dispatching the channel to a handler straight away.
+ *
+ * To become an approver one should inherit AbstractClientApprover and
+ * implement the pure virtual addDispatchOperation() method. After that the
+ * object representing the approver must be registered using
+ * ClientRegistrar::registerClient().
+ *
+ * When new channels in which the approver has registered an interest are
+ * ready to be dispatched, the method addDispatchOperation() is invoked.
+ * The new channels are represented by a ChannelDispatchOperation object, which
+ * is passed to the addDispatchOperation() method.
+ * All approvers are notified simultaneously.
+ *
+ * \section approver_usage_sec Usage
+ *
+ * \subsection approver_create_sec Implementing an approver
+ *
+ * \code
+ *
+ * class MyApprover : public AbstractClientApprover
+ * {
+ * public:
+ * MyApprover(const ChannelClassSpecSpecList &channelFilter);
+ * ~MyApprover() { }
+ *
+ * void addDispatchOperation(const MethodInvocationContextPtr<> &context,
+ * const ChannelDispatchOperationPtr &dispatchOperation);
+ * };
+ *
+ * MyApprover::MyApprover(const ChannelClassSpecList &channelFilter)
+ * : AbstractClientApprover(channelFilter)
+ * {
+ * }
+ *
+ * void MyApprover::addDispatchOperation(
+ * const MethodInvocationContextPtr<> &context,
+ * const ChannelDispatchOperationPtr &dispatchOperation)
+ * {
+ * // do something with dispatchOperation
+ *
+ * context->setFinished();
+ * }
+ *
+ * \endcode
+ *
+ * \subsection approver_register_sec Registering an approver
+ *
+ * \code
+ *
+ * ClientRegistrar registrar = ClientRegistrar::create();
+ * AbstractClientPtr approver = AbstractClientPtr::dynamicCast(
+ * SharedPtr<MyApprover>(new MyApprover(
+ * ChannelClassSpecList() << ChannelClassSpec::textChat())));
+ * registrar->registerClient(approver, "myapprover");
+ *
+ * \endcode
+ *
+ * \sa AbstractClient
+ */
+
+/**
+ * Construct a new AbstractClientApprover object.
+ *
+ * \param channelFilter A specification of the channels in which this approver
+ * is interested.
+ */
+AbstractClientApprover::AbstractClientApprover(
+ const ChannelClassSpecList &channelFilter)
+ : mPriv(new Private(channelFilter.bareClasses()))
+{
+}
+
+/**
+ * Class destructor.
+ */
+AbstractClientApprover::~AbstractClientApprover()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the property containing a specification of the channels that this
+ * channel approver is interested. The addDispatchOperation() method should be
+ * called by the channel dispatcher whenever at least one of the channels in
+ * a channel dispatch operation matches this description.
+ *
+ * This method works in exactly the same way as the
+ * AbstractClientObserver::observerChannelFilter() method. In particular, the
+ * returned value cannot change while the handler process continues to own the
+ * corresponding client bus name.
+ *
+ * In the .client file, represented in the same way as observer channel
+ * filter, the group is #TP_QT4_IFACE_CLIENT_APPROVER followed by
+ * ApproverChannelFilter instead.
+ *
+ * \return A specification of the channels that this channel approver is
+ * interested as a list of ChannelClassSpec objects.
+ * \sa addDispatchOperation()
+ */
+ChannelClassSpecList AbstractClientApprover::approverFilter() const
+{
+ return ChannelClassSpecList(mPriv->channelFilter);
+}
+
+/**
+ * \fn void AbstractClientApprover::addDispatchOperation(
+ * const MethodInvocationContextPtr<> &context,
+ * const ChannelDispatchOperationPtr &dispatchOperation);
+ *
+ * Called by the channel dispatcher when a dispatch operation in which the
+ * approver has registered an interest is created, or when the approver starts
+ * up while such channel dispatch operations already exist.
+ *
+ * The received \a context object should be stored until this
+ * method is finished processing and then MethodInvocationContext::setFinished()
+ * or MethodInvocationContext::setFinishedWithError() should be called on the
+ * received \a context object.
+ *
+ * Specialized approvers must reimplement this method.
+ *
+ * \param context A MethodInvocationContextPtr object that must be used to
+ * indicate whether this method finished processing.
+ * \param dispatchOperation The dispatch operation to be processed.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractClientHandler::Private
+{
+ Private(const ChannelClassList &channelFilter,
+ const Capabilities &capabilities,
+ bool wantsRequestNotification)
+ : channelFilter(channelFilter),
+ capabilities(capabilities),
+ wantsRequestNotification(wantsRequestNotification),
+ registered(false)
+ {
+ }
+
+ ChannelClassList channelFilter;
+ Capabilities capabilities;
+ bool wantsRequestNotification;
+ bool registered;
+};
+
+/**
+ * \class AbstractClientHandler
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClientHandler>
+ *
+ * \brief The AbstractClientHandler class represents a Telepathy handler.
+ *
+ * Handlers are the user interface for a channel. They turn an abstract
+ * channel into something the user wants to see, like a text message
+ * stream or an audio and/or video call.
+ *
+ * For its entire lifetime, each channel on a connection known to the channel
+ * dispatcher is either being processed by the channel dispatcher, or being
+ * handled by precisely one handler.
+ *
+ * Because each channel is only handled by one handler, handlers may perform
+ * actions that only make sense to do once, such as acknowledging text messages,
+ * transferring the file, etc.
+ *
+ * When a new incoming channel is offered to approvers by the channel
+ * dispatcher, it also offers the approvers a list of all the running or
+ * activatable handlers whose filter indicates that they are able to handle
+ * the channel. The approvers can choose one of those channel handlers to
+ * handle the channel.
+ *
+ * When a new outgoing channel appears, the channel dispatcher passes it to
+ * an appropriate channel handler automatically.
+ *
+ * To become an handler one should inherit AbstractClientHandler and
+ * implement the pure virtual bypassApproval() and handleChannels() methods.
+ * After that the object representing the handler must be registered using
+ * ClientRegistrar::registerClient().
+ *
+ * When new channels in which the approver has registered an interest are
+ * ready to be handled, the method handleChannels() is invoked.
+ *
+ * \section handler_usage_sec Usage
+ *
+ * \subsection handler_create_sec Implementing a handler
+ *
+ * \code
+ *
+ * class MyHandler : public AbstractClientHandler
+ * {
+ * public:
+ * MyHandler(const ChannelClassSpecList &channelFilter);
+ * ~MyHandler() { }
+ *
+ * void bypassApproval() const;
+ *
+ * void handleChannels(const MethodInvocationContextPtr<> &context,
+ * const AccountPtr &account,
+ * const ConnectionPtr &connection,
+ * const QList<ChannelPtr> &channels,
+ * const QList<ChannelRequestPtr> &requestsSatisfied,
+ * const QDateTime &userActionTime,
+ * const AbstractClientHandler::HandlerInfo &handlerInfo);
+ * };
+ *
+ * MyHandler::MyHandler(const ChannelClassSpecList &channelFilter)
+ * : AbstractClientHandler(channelFilter)
+ * {
+ * }
+ *
+ * void MyHandler::bypassApproval() const
+ * {
+ * return false;
+ * }
+ *
+ * void MyHandler::handleChannels(const MethodInvocationContextPtr<> &context,
+ * const AccountPtr &account,
+ * const ConnectionPtr &connection,
+ * const QList<ChannelPtr> &channels,
+ * const QList<ChannelRequestPtr> &requestsSatisfied,
+ * const QDateTime &userActionTime,
+ * const AbstractClientHandler::HandlerInfo &handlerInfo)
+ * {
+ * // do something
+ *
+ * context->setFinished();
+ * }
+ *
+ * \endcode
+ *
+ * \subsection handler_register_sec Registering a handler
+ *
+ * \code
+ *
+ * ClientRegistrar registrar = ClientRegistrar::create();
+ * AbstractClientPtr handler = AbstractClientPtr::dynamicCast(
+ * SharedPtr<MyHandler>(new MyHandler(
+ * ChannelClassSpecList() << ChannelClassSpec::textChat())));
+ * registrar->registerClient(handler, "myhandler");
+ *
+ * \endcode
+ *
+ * \sa AbstractClient
+ */
+
+/**
+ * \class AbstractClientHandler::Capabilities
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClientHandler>
+ *
+ * \brief The AbstractClientHandler::Capabilities class provides a wrapper
+ * around the capabilities of a handler.
+ *
+ * \sa AbstractClientHandler
+ */
+
+/**
+ * \class AbstractClientHandler::HandlerInfo
+ * \ingroup clientclient
+ * \headerfile TelepathyQt4/abstract-client.h <TelepathyQt4/AbstractClientHandler>
+ *
+ * \brief The AbstractClientHandler::HandlerInfo class provides a wrapper
+ * around the additional info about the channels passed to handleChannels().
+ *
+ * \sa AbstractClientHandler
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractClientHandler::Capabilities::Private : public QSharedData
+{
+ Private(const QStringList &tokens)
+ : tokens(QSet<QString>::fromList(tokens)) {}
+
+ QSet<QString> tokens;
+};
+
+AbstractClientHandler::Capabilities::Capabilities(const QStringList &tokens)
+ : mPriv(new Private(tokens))
+{
+}
+
+AbstractClientHandler::Capabilities::Capabilities(const Capabilities &other)
+ : mPriv(other.mPriv)
+{
+}
+
+AbstractClientHandler::Capabilities::~Capabilities()
+{
+}
+
+AbstractClientHandler::Capabilities &AbstractClientHandler::Capabilities::operator=(
+ const Capabilities &other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ mPriv = other.mPriv;
+ return *this;
+}
+
+bool AbstractClientHandler::Capabilities::hasToken(const QString &token) const
+{
+ return mPriv->tokens.contains(token);
+}
+
+void AbstractClientHandler::Capabilities::setToken(const QString &token)
+{
+ mPriv->tokens.insert(token);
+}
+
+void AbstractClientHandler::Capabilities::unsetToken(const QString &token)
+{
+ mPriv->tokens.remove(token);
+}
+
+QStringList AbstractClientHandler::Capabilities::allTokens() const
+{
+ return mPriv->tokens.toList();
+}
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractClientHandler::HandlerInfo::Private : public QSharedData
+{
+ Private(const QVariantMap &info)
+ : info(info) {}
+
+ QVariantMap info;
+};
+
+AbstractClientHandler::HandlerInfo::HandlerInfo(const QVariantMap &info)
+ : mPriv(new Private(info))
+{
+}
+
+AbstractClientHandler::HandlerInfo::HandlerInfo(const HandlerInfo &other)
+ : mPriv(other.mPriv)
+{
+}
+
+AbstractClientHandler::HandlerInfo::~HandlerInfo()
+{
+}
+
+AbstractClientHandler::HandlerInfo &AbstractClientHandler::HandlerInfo::operator=(
+ const HandlerInfo &other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ mPriv = other.mPriv;
+ return *this;
+}
+
+QVariantMap AbstractClientHandler::HandlerInfo::allInfo() const
+{
+ return mPriv->info;
+}
+
+/**
+ * Construct a new AbstractClientHandler object.
+ *
+ * \param channelFilter A specification of the channels in which this observer
+ * is interested.
+ * \param wantsRequestNotification Whether this handler wants to receive channel
+ * requests notification via addRequest() and
+ * removeRequest().
+ * \param capabilities The set of additional capabilities supported by this
+ * handler.
+ */
+AbstractClientHandler::AbstractClientHandler(const ChannelClassSpecList &channelFilter,
+ const Capabilities &capabilities,
+ bool wantsRequestNotification)
+ : mPriv(new Private(channelFilter.bareClasses(), capabilities, wantsRequestNotification))
+{
+}
+
+/**
+ * Class destructor.
+ */
+AbstractClientHandler::~AbstractClientHandler()
+{
+ delete mPriv;
+}
+
+/**
+ * Return whether this handler is registered.
+ *
+ * \return \c true if registered, \c false otherwise.
+ */
+bool AbstractClientHandler::isRegistered() const
+{
+ return mPriv->registered;
+}
+
+/**
+ * Return the property containing a specification of the channels that this
+ * channel handler can deal with. It will be offered to approvers as a potential
+ * channel handler for bundles that contain only suitable channels, or for
+ * suitable channels that must be handled separately.
+ *
+ * This method works in exactly the same way as the
+ * AbstractClientObserver::observerChannelFilter() method. In particular, the
+ * returned value cannot change while the handler process continues to own the
+ * corresponding client bus name.
+ *
+ * In the .client file, represented in the same way as observer channel
+ * filter, the group is #TP_QT4_IFACE_CLIENT_HANDLER suffixed
+ * by HandlerChannelFilter instead.
+ *
+ * \return A specification of the channels that this channel handler can deal
+ * with as a list of ChannelClassSpec objects.
+ */
+ChannelClassSpecList AbstractClientHandler::handlerFilter() const
+{
+ return ChannelClassSpecList(mPriv->channelFilter);
+}
+
+/**
+ * Return the set of additional capabilities supported by this handler.
+ *
+ * \return The capabilities as an AbstractClientHandler::Capabilities object.
+ */
+AbstractClientHandler::Capabilities AbstractClientHandler::handlerCapabilities() const
+{
+ return mPriv->capabilities;
+}
+
+/**
+ * \fn bool AbstractClientHandler::bypassApproval() const;
+ *
+ * Return whether channels destined for this handler are automatically
+ * handled, without invoking approvers.
+ *
+ * \return \c true if automatically handled, \c false otherwise.
+ */
+
+/**
+ * \fn void AbstractClientHandler::handleChannels(
+ * const MethodInvocationContextPtr<> &context,
+ * const AccountPtr &account,
+ * const ConnectionPtr &connection,
+ * const QList<ChannelPtr> &channels,
+ * const QList<ChannelRequestPtr> &requestsSatisfied,
+ * const QDateTime &userActionTime,
+ * const HandlerInfo &handlerInfo);
+ *
+ * Called by the channel dispatcher when this handler should handle these
+ * channels, or when this handler should present channels that it is already
+ * handling to the user (e.g. bring them into the foreground).
+ *
+ * Clients are expected to know what channels they're already handling, and
+ * which channel object corresponds to which window or tab.
+ *
+ * After handleChannels() replies successfully by calling
+ * MethodInvocationContext::setFinished(), the client process is considered
+ * to be responsible for the channel until it its unique name disappears from
+ * the bus.
+ *
+ * If a process has multiple client bus names - some temporary and some
+ * long-lived - and drops one of the temporary bus names in order to reduce the
+ * set of channels that it will handle, any channels that it is already handling
+ * will remain unaffected.
+ *
+ * The received \a context object should be stored until this
+ * method is finished processing and then MethodInvocationContext::setFinished()
+ * or MethodInvocationContext::setFinishedWithError() should be called on the
+ * received \a context object.
+ *
+ * Specialized handlers must reimplement this method.
+ *
+ * \param context A MethodInvocationContextPtr object that must be used to
+ * indicate whether this method finished processing.
+ * \param account The account with which the channels are associated.
+ * \param connection The connection with which the channels are associated.
+ * \param channels The channels to be handled.
+ * \param dispatchOperation The dispatch operation for these channels.
+ * The object will be invalid (DBusProxy::isValid()
+ * will be false) if there is no dispatch
+ * operation in place (because the channels were
+ * requested, not incoming).
+ * \param requestsSatisfied The requests satisfied by these channels.
+ * \param userActionTime The time at which user action occurred, or 0 if this
+ * channel is to be handled for some reason not involving
+ * user action. Handlers should use this for
+ * focus-stealing prevention, if applicable.
+ * \param handlerInfo Additional information about these channels.
+ */
+
+/**
+ * Return whether this handler wants to receive notification of channel requests
+ * via addRequest() and removeRequest().
+ *
+ * This property is set by the constructor and cannot be changed after that.
+ *
+ * \return \c true if receiving channel requests notification is desired,
+ * \c false otherwise.
+ */
+bool AbstractClientHandler::wantsRequestNotification() const
+{
+ return mPriv->wantsRequestNotification;
+}
+
+/**
+ * Called by the channel dispatcher to indicate that channels have been
+ * requested, and that if the request is successful, they will probably be
+ * handled by this handler.
+ *
+ * This allows the UI to start preparing to handle the channels in advance
+ * (e.g. render a window with an "in progress" message), improving perceived
+ * responsiveness.
+ *
+ * If the request succeeds and is given to the expected handler, the
+ * requestsSatisfied parameter to handleChannels() can be used to match the
+ * channel to a previous addRequest() call.
+ *
+ * This lets the UI direct the channels to the window that it already opened.
+ *
+ * If the request fails, the expected handler is notified by the channel
+ * dispatcher calling its removeRequest() method.
+ *
+ * This lets the UI close the window or display the error.
+ *
+ * The channel dispatcher will attempt to ensure that handleChannels() is called
+ * on the same handler that received addRequest(). If that isn't possible,
+ * removeRequest() will be called on the handler that previously received
+ * addRequest(), with the special error #TP_QT4_ERROR_NOT_YOURS, which
+ * indicates that some other handler received the channel instead.
+ *
+ * Expected handling is for the UI to close the window it previously opened.
+ *
+ * Specialized handlers that want to be notified of newly requested channel
+ * should reimplement this method.
+ *
+ * \param channelRequest The newly created channel request.
+ * \sa removeRequest()
+ */
+void AbstractClientHandler::addRequest(
+ const ChannelRequestPtr &channelRequest)
+{
+ // do nothing, subclasses that want to listen requests should reimplement
+ // this method
+}
+
+/**
+ * Called by the ChannelDispatcher to indicate that a request previously passed
+ * to addRequest() has failed and should be disregarded.
+ *
+ * Specialized handlers that want to be notified of removed channel requests
+ * should reimplement this method.
+ *
+ * \param channelRequest The channel request that failed.
+ * \param errorName The name of the D-Bus error with which the request failed.
+ * If this is #TP_QT4_ERROR_NOT_YOURS, this indicates that
+ * the request succeeded, but all the resulting channels were
+ * given to some other handler.
+ * \param errorMessage Any message supplied with the D-Bus error.
+ */
+void AbstractClientHandler::removeRequest(
+ const ChannelRequestPtr &channelRequest,
+ const QString &errorName, const QString &errorMessage)
+{
+ // do nothing, subclasses that want to listen requests should reimplement
+ // this method
+}
+
+void AbstractClientHandler::setRegistered(bool registered)
+{
+ mPriv->registered = registered;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/abstract-client.h b/qt4/TelepathyQt4/abstract-client.h
new file mode 100644
index 000000000..00eb1ef49
--- /dev/null
+++ b/qt4/TelepathyQt4/abstract-client.h
@@ -0,0 +1,323 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_abstract_client_h_HEADER_GUARD_
+#define _TelepathyQt4_abstract_client_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+#include <QList>
+#include <QObject>
+#include <QSharedDataPointer>
+#include <QString>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class ClientRegistrar;
+class ChannelClassSpecList;
+
+class TELEPATHY_QT4_EXPORT AbstractClient : public RefCounted
+{
+ Q_DISABLE_COPY(AbstractClient)
+
+public:
+ AbstractClient();
+ virtual ~AbstractClient();
+};
+
+class TELEPATHY_QT4_EXPORT AbstractClientObserver : public virtual AbstractClient
+{
+ Q_DISABLE_COPY(AbstractClientObserver)
+
+public:
+ class ObserverInfo
+ {
+ public:
+ ObserverInfo(const QVariantMap &info = QVariantMap());
+ ObserverInfo(const ObserverInfo &other);
+ ~ObserverInfo();
+
+ ObserverInfo &operator=(const ObserverInfo &other);
+
+ bool isRecovering() const { return qdbus_cast<bool>(allInfo().value(QLatin1String("recovering"))); }
+
+ QVariantMap allInfo() const;
+
+ private:
+ struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ virtual ~AbstractClientObserver();
+
+ ChannelClassSpecList observerFilter() const;
+
+ bool shouldRecover() const;
+
+ virtual void observeChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const ChannelDispatchOperationPtr &dispatchOperation,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const ObserverInfo &observerInfo) = 0;
+
+protected:
+ AbstractClientObserver(const ChannelClassSpecList &channelFilter, bool shouldRecover = false);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT AbstractClientApprover : public virtual AbstractClient
+{
+ Q_DISABLE_COPY(AbstractClientApprover)
+
+public:
+ virtual ~AbstractClientApprover();
+
+ ChannelClassSpecList approverFilter() const;
+
+ virtual void addDispatchOperation(const MethodInvocationContextPtr<> &context,
+ const ChannelDispatchOperationPtr &dispatchOperation) = 0;
+
+protected:
+ AbstractClientApprover(const ChannelClassSpecList &channelFilter);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+/*
+ * TODO: use case specific subclasses:
+ * - StreamTubeHandler(QString(List) protocol(s))
+ * - handleTube(DBusTubeChannelPtr, userActionTime)
+ * - DBusTubeHandler(QString(List) serviceName(s))
+ * - handleTube(DBusTubeChannelPtr, userActionTime)
+ */
+class TELEPATHY_QT4_EXPORT AbstractClientHandler : public virtual AbstractClient
+{
+ Q_DISABLE_COPY(AbstractClientHandler)
+
+public:
+ class Capabilities
+ {
+ public:
+ Capabilities(const QStringList &tokens = QStringList());
+ Capabilities(const Capabilities &other);
+ ~Capabilities();
+
+ Capabilities &operator=(const Capabilities &other);
+
+ bool hasGTalkP2PNATTraversalToken() const
+ {
+ return hasToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/gtalk-p2p"));
+ }
+
+ void setGTalkP2PNATTraversalToken()
+ {
+ setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/gtalk-p2p"));
+ }
+
+ void unsetGTalkP2PNATTraversalToken()
+ {
+ unsetToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/gtalk-p2p"));
+ }
+
+ bool hasICEUDPNATTraversalToken() const
+ {
+ return hasToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/ice-udp"));
+ }
+
+ void setICEUDPNATTraversalToken()
+ {
+ setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/ice-udp"));
+ }
+
+ void unsetICEUDPNATTraversalToken()
+ {
+ unsetToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/ice-udp"));
+ }
+
+ bool hasWLM85NATTraversalToken() const
+ {
+ return hasToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/wlm-8.5"));
+ }
+
+ void setWLM85NATTraversalToken()
+ {
+ setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/wlm-8.5"));
+ }
+
+ void unsetWLM85NATTraversalToken()
+ {
+ unsetToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/wlm-8.5"));
+ }
+
+ bool hasWLM2009NATTraversalToken() const
+ {
+ return hasToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/wlm-2009"));
+ }
+
+ void setWLM2009NATTraversalToken()
+ {
+ setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/wlm-2009"));
+ }
+
+ void unsetWLM2009NATTraversalToken()
+ {
+ unsetToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/wlm-2009"));
+ }
+
+ bool hasAudioCodecToken(const QString &mimeSubType) const
+ {
+ return hasToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/audio/") + mimeSubType.toLower());
+ }
+
+ void setAudioCodecToken(const QString &mimeSubType)
+ {
+ setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/audio/") + mimeSubType.toLower());
+ }
+
+ void unsetAudioCodecToken(const QString &mimeSubType)
+ {
+ unsetToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/audio/") + mimeSubType.toLower());
+ }
+
+ bool hasVideoCodecToken(const QString &mimeSubType) const
+ {
+ return hasToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/video/") + mimeSubType.toLower());
+ }
+
+ void setVideoCodecToken(const QString &mimeSubType)
+ {
+ setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/video/") + mimeSubType.toLower());
+ }
+
+ void unsetVideoCodecToken(const QString &mimeSubType)
+ {
+ unsetToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/video/") + mimeSubType.toLower());
+ }
+
+ bool hasToken(const QString &token) const;
+ void setToken(const QString &token);
+ void unsetToken(const QString &token);
+
+ QStringList allTokens() const;
+
+ private:
+ struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ class HandlerInfo
+ {
+ public:
+ HandlerInfo(const QVariantMap &info = QVariantMap());
+ HandlerInfo(const HandlerInfo &other);
+ ~HandlerInfo();
+
+ HandlerInfo &operator=(const HandlerInfo &other);
+
+ QVariantMap allInfo() const;
+
+ private:
+ struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ virtual ~AbstractClientHandler();
+
+ // FIXME (API/ABI break) Move isRegistered/setRegistered to AbstractClient
+ bool isRegistered() const;
+
+ ChannelClassSpecList handlerFilter() const;
+
+ Capabilities handlerCapabilities() const;
+
+ virtual bool bypassApproval() const = 0;
+
+ virtual void handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo) = 0;
+
+ bool wantsRequestNotification() const;
+ virtual void addRequest(const ChannelRequestPtr &request);
+ virtual void removeRequest(const ChannelRequestPtr &request,
+ const QString &errorName, const QString &errorMessage);
+
+protected:
+ AbstractClientHandler(const ChannelClassSpecList &channelFilter,
+ const Capabilities &capabilities = Capabilities(),
+ bool wantsRequestNotification = false);
+
+private:
+ friend class ClientRegistrar;
+
+ void setRegistered(bool registered);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::AbstractClientObserver::ObserverInfo);
+Q_DECLARE_METATYPE(Tp::AbstractClientHandler::Capabilities);
+Q_DECLARE_METATYPE(Tp::AbstractClientHandler::HandlerInfo);
+
+#endif
diff --git a/qt4/TelepathyQt4/abstract-interface.cpp b/qt4/TelepathyQt4/abstract-interface.cpp
new file mode 100644
index 000000000..2e1c5ca7d
--- /dev/null
+++ b/qt4/TelepathyQt4/abstract-interface.cpp
@@ -0,0 +1,136 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AbstractInterface>
+
+#include "TelepathyQt4/_gen/abstract-interface.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/Types>
+
+#include <QDBusPendingCall>
+#include <QDBusVariant>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT AbstractInterface::Private
+{
+ QString mError;
+ QString mMessage;
+};
+
+/**
+ * \class AbstractInterface
+ * \ingroup clientsideproxies
+ * \headerfile TelepathyQt4/abstract-interface.h <TelepathyQt4/AbstractInterface>
+ *
+ * \brief The AbstractInterface class is the base class for all client side
+ * D-Bus interfaces, allowing access to remote methods/properties/signals.
+ */
+
+AbstractInterface::AbstractInterface(const QString &busName,
+ const QString &path, const QLatin1String &interface,
+ const QDBusConnection &dbusConnection, QObject *parent)
+ : QDBusAbstractInterface(busName, path, interface.latin1(), dbusConnection, parent),
+ mPriv(new Private)
+{
+}
+
+AbstractInterface::AbstractInterface(DBusProxy *parent, const QLatin1String &interface)
+ : QDBusAbstractInterface(parent->busName(), parent->objectPath(),
+ interface.latin1(), parent->dbusConnection(), parent),
+ mPriv(new Private)
+{
+ connect(parent, SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ this, SLOT(invalidate(Tp::DBusProxy*,QString,QString)));
+}
+
+AbstractInterface::~AbstractInterface()
+{
+ delete mPriv;
+}
+
+bool AbstractInterface::isValid() const
+{
+ return QDBusAbstractInterface::isValid() && mPriv->mError.isEmpty();
+}
+
+QString AbstractInterface::invalidationReason() const
+{
+ return mPriv->mError;
+}
+
+QString AbstractInterface::invalidationMessage() const
+{
+ return mPriv->mMessage;
+}
+
+void AbstractInterface::invalidate(DBusProxy *proxy,
+ const QString &error, const QString &message)
+{
+ Q_ASSERT(!error.isEmpty());
+
+ if (mPriv->mError.isEmpty()) {
+ mPriv->mError = error;
+ mPriv->mMessage = message;
+ }
+}
+
+PendingVariant *AbstractInterface::internalRequestProperty(const QString &name) const
+{
+ QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(),
+ TP_QT4_IFACE_PROPERTIES, QLatin1String("Get"));
+ msg << interface() << name;
+ QDBusPendingCall pendingCall = connection().asyncCall(msg);
+ DBusProxy *proxy = qobject_cast<DBusProxy*>(parent());
+ return new PendingVariant(pendingCall, DBusProxyPtr(proxy));
+}
+
+PendingOperation *AbstractInterface::internalSetProperty(const QString &name,
+ const QVariant &newValue)
+{
+ QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(),
+ TP_QT4_IFACE_PROPERTIES, QLatin1String("Set"));
+ msg << interface() << name << QVariant::fromValue(QDBusVariant(newValue));
+ QDBusPendingCall pendingCall = connection().asyncCall(msg);
+ DBusProxy *proxy = qobject_cast<DBusProxy*>(parent());
+ return new PendingVoid(pendingCall, DBusProxyPtr(proxy));
+}
+
+PendingVariantMap *AbstractInterface::internalRequestAllProperties() const
+{
+ QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(),
+ TP_QT4_IFACE_PROPERTIES, QLatin1String("GetAll"));
+ msg << interface();
+ QDBusPendingCall pendingCall = connection().asyncCall(msg);
+ DBusProxy *proxy = qobject_cast<DBusProxy*>(parent());
+ return new PendingVariantMap(pendingCall, DBusProxyPtr(proxy));
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/abstract-interface.h b/qt4/TelepathyQt4/abstract-interface.h
new file mode 100644
index 000000000..42337d73a
--- /dev/null
+++ b/qt4/TelepathyQt4/abstract-interface.h
@@ -0,0 +1,76 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_abstract_interface_h_HEADER_GUARD_
+#define _TelepathyQt4_abstract_interface_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QDBusAbstractInterface>
+
+namespace Tp
+{
+
+class DBusProxy;
+class PendingVariant;
+class PendingOperation;
+class PendingVariantMap;
+
+class TELEPATHY_QT4_EXPORT AbstractInterface : public QDBusAbstractInterface
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(AbstractInterface)
+
+public:
+ virtual ~AbstractInterface();
+
+ bool isValid() const;
+ QString invalidationReason() const;
+ QString invalidationMessage() const;
+
+protected Q_SLOTS:
+ virtual void invalidate(Tp::DBusProxy *proxy,
+ const QString &error, const QString &message);
+
+protected:
+ AbstractInterface(DBusProxy *proxy, const QLatin1String &interface);
+ AbstractInterface(const QString &busName, const QString &path,
+ const QLatin1String &interface, const QDBusConnection &connection,
+ QObject *parent);
+
+ PendingVariant *internalRequestProperty(const QString &name) const;
+ PendingOperation *internalSetProperty(const QString &name, const QVariant &newValue);
+ PendingVariantMap *internalRequestAllProperties() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account-capability-filter.dox b/qt4/TelepathyQt4/account-capability-filter.dox
new file mode 100644
index 000000000..c4805b887
--- /dev/null
+++ b/qt4/TelepathyQt4/account-capability-filter.dox
@@ -0,0 +1,30 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::AccountCapabilityFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/account-capability-filter.h <TelepathyQt4/AccountCapabilityFilter>
+ *
+ * \brief The AccountCapabilityFilter class provides a filter object to be used
+ * to filter accounts by capabilities.
+ */
diff --git a/qt4/TelepathyQt4/account-capability-filter.h b/qt4/TelepathyQt4/account-capability-filter.h
new file mode 100644
index 000000000..ac00f22ae
--- /dev/null
+++ b/qt4/TelepathyQt4/account-capability-filter.h
@@ -0,0 +1,39 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_capability_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_account_capability_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/GenericCapabilityFilter>
+
+namespace Tp
+{
+
+typedef GenericCapabilityFilter<Account> AccountCapabilityFilter;
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account-factory.cpp b/qt4/TelepathyQt4/account-factory.cpp
new file mode 100644
index 000000000..5dcac4b5a
--- /dev/null
+++ b/qt4/TelepathyQt4/account-factory.cpp
@@ -0,0 +1,157 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AccountFactory>
+
+#include "TelepathyQt4/_gen/account-factory.moc.hpp"
+
+#include <TelepathyQt4/Account>
+
+namespace Tp
+{
+
+/**
+ * \class AccountFactory
+ * \ingroup utils
+ * \headerfile TelepathyQt4/account-factory.h <TelepathyQt4/AccountFactory>
+ *
+ * \brief The AccountFactory class is responsible for constructing Account
+ * objects according to application-defined settings.
+ *
+ * The class is used by AccountManager and other classes which construct Account
+ * proxy instances to enable sharing instances of application-defined Account
+ * subclasses with certain features always ready.
+ */
+
+/**
+ * Create a new AccountFactory object.
+ *
+ * Optionally, the \a features to make ready on all constructed proxies can be specified. The
+ * default is to make no features ready. It should be noted that unlike Account::becomeReady(),
+ * FeatureCore isn't assumed. If no features are specified, which is the default behavior, no
+ * Account::becomeReady() call is made at all and the proxy won't be Account::isReady().
+ *
+ * \param bus The QDBusConnection for proxies constructed using this factory to use.
+ * \param features The features to make ready on constructed Accounts.
+ * \return An AccountFactoryPtr object pointing to the newly created
+ * AccountFactory object.
+ */
+AccountFactoryPtr AccountFactory::create(const QDBusConnection &bus, const Features &features)
+{
+ return AccountFactoryPtr(new AccountFactory(bus, features));
+}
+
+/**
+ * Construct a new AccountFactory object.
+ *
+ * As in create(), it should be noted that unlike Account::becomeReady(), FeatureCore isn't assumed.
+ * If no \a features are specified, no Account::becomeReady() call is made at all and the proxy
+ * won't be Account::isReady().
+ *
+ * \param bus The QDBusConnection for proxies constructed using this factory to use.
+ * \param features The features to make ready on constructed Accounts.
+ */
+AccountFactory::AccountFactory(const QDBusConnection &bus, const Features &features)
+ : FixedFeatureFactory(bus)
+{
+ addFeatures(features);
+}
+
+/**
+ * Class destructor.
+ */
+AccountFactory::~AccountFactory()
+{
+}
+
+/**
+ * Constructs an Account proxy and begins making it ready.
+ *
+ * If a valid proxy already exists in the factory cache for the given combination of \a busName and
+ * \a objectPath, it is returned instead. All newly created proxies are automatically cached until
+ * they're either DBusProxy::invalidated() or the last reference to them outside the factory has
+ * been dropped.
+ *
+ * The proxy can be accessed immediately after this function returns using PendingReady::proxy().
+ * The ready operation only finishes, however, when the features specified by features(), if any,
+ * are made ready as much as possible. If the service doesn't support a given feature, they won't
+ * obviously be ready even if the operation finished successfully, as is the case for
+ * Account::becomeReady().
+ *
+ * \param busName The bus/service name of the D-Bus account object the proxy is constructed for.
+ * (Usually #TP_QT4_ACCOUNT_MANAGER_BUS_NAME).
+ * \param objectPath The object path of the account.
+ * \param connFactory The connection factory to use for the Account.
+ * \param chanFactory The channel factory to use for the Account.
+ * \param contactFactory The channel factory to use for the Account.
+ * \return A PendingReady operation with the proxy in PendingReady::proxy().
+ */
+PendingReady *AccountFactory::proxy(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const
+{
+ DBusProxyPtr proxy = cachedProxy(busName, objectPath);
+ if (proxy.isNull()) {
+ proxy = construct(busName, objectPath, connFactory, chanFactory, contactFactory);
+ }
+
+ return nowHaveProxy(proxy);
+}
+
+/**
+ * Can be used by subclasses to override the Account subclass constructed by the factory.
+ *
+ * This is automatically called by proxy() to construct proxy instances if no valid cached proxy is
+ * found.
+ *
+ * The default implementation constructs Tp::Account objects.
+ *
+ * \param busName The bus/service name of the D-Bus account object the proxy is constructed for.
+ * (Usually #TP_QT4_ACCOUNT_MANAGER_BUS_NAME).
+ * \param objectPath The object path of the account.
+ * \param connFactory The connection factory to use for the Account.
+ * \param chanFactory The channel factory to use for the Account.
+ * \param contactFactory The channel factory to use for the Account.
+ * \return A pointer to the constructed Account object.
+ */
+AccountPtr AccountFactory::construct(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const
+{
+ return Account::create(dbusConnection(), busName, objectPath, connFactory, chanFactory,
+ contactFactory);
+}
+
+/**
+ * Identity transform, as is appropriate for Account objects.
+ *
+ * \param uniqueOrWellKnown The name to transform.
+ * \return \a uniqueOrWellKnown
+ */
+QString AccountFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const
+{
+ return uniqueOrWellKnown;
+}
+
+}
diff --git a/qt4/TelepathyQt4/account-factory.h b/qt4/TelepathyQt4/account-factory.h
new file mode 100644
index 000000000..c6f9f061c
--- /dev/null
+++ b/qt4/TelepathyQt4/account-factory.h
@@ -0,0 +1,79 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_account_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/FixedFeatureFactory>
+
+class QDBusConnection;
+
+namespace Tp
+{
+
+class PendingReady;
+
+class TELEPATHY_QT4_EXPORT AccountFactory : public FixedFeatureFactory
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(AccountFactory)
+
+public:
+ static AccountFactoryPtr create(const QDBusConnection &bus,
+ const Features &features = Features());
+
+ virtual ~AccountFactory();
+
+ PendingReady *proxy(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const;
+
+protected:
+ AccountFactory(const QDBusConnection &bus, const Features &features);
+
+ virtual AccountPtr construct(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const;
+ virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const;
+ // Nothing we'd like to prepare()
+ // Fixed features
+
+private:
+ struct Private;
+ Private *mPriv; // Currently unused, just for future-proofing
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account-filter.h b/qt4/TelepathyQt4/account-filter.h
new file mode 100644
index 000000000..903a7137b
--- /dev/null
+++ b/qt4/TelepathyQt4/account-filter.h
@@ -0,0 +1,39 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_account_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Filter>
+
+namespace Tp
+{
+
+typedef Filter<Account> AccountFilter;
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account-manager.cpp b/qt4/TelepathyQt4/account-manager.cpp
new file mode 100644
index 000000000..9fd85b6bc
--- /dev/null
+++ b/qt4/TelepathyQt4/account-manager.cpp
@@ -0,0 +1,1115 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AccountManager>
+
+#include "TelepathyQt4/_gen/account-manager.moc.hpp"
+#include "TelepathyQt4/_gen/cli-account-manager.moc.hpp"
+#include "TelepathyQt4/_gen/cli-account-manager-body.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/AccountCapabilityFilter>
+#include <TelepathyQt4/AccountFilter>
+#include <TelepathyQt4/AccountSet>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReadinessHelper>
+
+#include <QQueue>
+#include <QSet>
+#include <QTimer>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT AccountManager::Private
+{
+ Private(AccountManager *parent, const AccountFactoryConstPtr &accFactory,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ ~Private();
+
+ void init();
+
+ static void introspectMain(Private *self);
+
+ void checkIntrospectionCompleted();
+
+ QSet<QString> getAccountPathsFromProp(const QVariant &prop);
+ QSet<QString> getAccountPathsFromProps(const QVariantMap &props);
+ void addAccountForPath(const QString &accountObjectPath);
+
+ // Public object
+ AccountManager *parent;
+
+ // Instance of generated interface class
+ Client::AccountManagerInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ AccountFactoryConstPtr accFactory;
+ ConnectionFactoryConstPtr connFactory;
+ ChannelFactoryConstPtr chanFactory;
+ ContactFactoryConstPtr contactFactory;
+
+ // Introspection
+ int reintrospectionRetries;
+ bool gotInitialAccounts;
+ QHash<QString, AccountPtr> incompleteAccounts;
+ QHash<QString, AccountPtr> accounts;
+ QStringList supportedAccountProperties;
+};
+
+static const int maxReintrospectionRetries = 5;
+static const int reintrospectionRetryInterval = 3;
+
+AccountManager::Private::Private(AccountManager *parent,
+ const AccountFactoryConstPtr &accFactory, const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory)
+ : parent(parent),
+ baseInterface(new Client::AccountManagerInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ accFactory(accFactory),
+ connFactory(connFactory),
+ chanFactory(chanFactory),
+ contactFactory(contactFactory),
+ reintrospectionRetries(0),
+ gotInitialAccounts(false)
+{
+ debug() << "Creating new AccountManager:" << parent->busName();
+
+ if (accFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the account factory is not the proxy connection";
+ }
+
+ if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the connection factory is not the proxy connection";
+ }
+
+ if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection";
+ }
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As AccountManager does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+ readinessHelper->becomeReady(Features() << FeatureCore);
+
+ init();
+}
+
+AccountManager::Private::~Private()
+{
+ delete baseInterface;
+}
+
+void AccountManager::Private::init()
+{
+ if (!parent->isValid()) {
+ return;
+ }
+
+ parent->connect(baseInterface,
+ SIGNAL(AccountValidityChanged(QDBusObjectPath,bool)),
+ SLOT(onAccountValidityChanged(QDBusObjectPath,bool)));
+ parent->connect(baseInterface,
+ SIGNAL(AccountRemoved(QDBusObjectPath)),
+ SLOT(onAccountRemoved(QDBusObjectPath)));
+}
+
+void AccountManager::Private::introspectMain(AccountManager::Private *self)
+{
+ debug() << "Calling Properties::GetAll(AccountManager)";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_MANAGER)),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+}
+
+void AccountManager::Private::checkIntrospectionCompleted()
+{
+ if (!parent->isReady(FeatureCore) &&
+ incompleteAccounts.size() == 0) {
+ readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+}
+
+QSet<QString> AccountManager::Private::getAccountPathsFromProp(
+ const QVariant &prop)
+{
+ QSet<QString> set;
+
+ ObjectPathList paths = qdbus_cast<ObjectPathList>(prop);
+ if (paths.size() == 0) {
+ /* maybe the AccountManager is buggy, like Mission Control
+ * 5.0.beta45, and returns an array of strings rather than
+ * an array of object paths? */
+ QStringList wronglyTypedPaths = qdbus_cast<QStringList>(prop);
+ if (wronglyTypedPaths.size() > 0) {
+ warning() << "AccountManager returned wrong type for"
+ "Valid/InvalidAccounts (expected 'ao', got 'as'); "
+ "working around it";
+ foreach (QString path, wronglyTypedPaths) {
+ set << path;
+ }
+ }
+ } else {
+ foreach (const QDBusObjectPath &path, paths) {
+ set << path.path();
+ }
+ }
+
+ return set;
+}
+
+QSet<QString> AccountManager::Private::getAccountPathsFromProps(
+ const QVariantMap &props)
+{
+ return getAccountPathsFromProp(props[QLatin1String("ValidAccounts")]).unite(
+ getAccountPathsFromProp(props[QLatin1String("InvalidAccounts")]));
+}
+
+void AccountManager::Private::addAccountForPath(const QString &path)
+{
+ // Also check incompleteAccounts, because otherwise we end up introspecting an account twice
+ // when getting an AccountValidityChanged signal for a new account before we get the initial
+ // introspection accounts list from the GetAll return (the GetAll return function
+ // unconditionally calls addAccountForPath
+ if (accounts.contains(path) || incompleteAccounts.contains(path)) {
+ return;
+ }
+
+ PendingReady *readyOp = accFactory->proxy(parent->busName(), path, connFactory,
+ chanFactory, contactFactory);
+ AccountPtr account(AccountPtr::qObjectCast(readyOp->proxy()));
+ Q_ASSERT(!account.isNull());
+
+ parent->connect(readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAccountReady(Tp::PendingOperation*)));
+ incompleteAccounts.insert(path, account);
+}
+
+/**
+ * \class AccountManager
+ * \ingroup clientam
+ * \headerfile TelepathyQt4/account-manager.h <TelepathyQt4/AccountManager>
+ *
+ * \brief The AccountManager class represents a Telepathy account manager.
+ *
+ * The remote object accessor functions on this object (allAccounts(),
+ * validAccounts(), and so on) don't make any D-Bus calls; instead, they return/use
+ * values cached from a previous introspection run. The introspection process
+ * populates their values in the most efficient way possible based on what the
+ * service implements.
+ *
+ * To avoid unnecessary D-Bus traffic, some accessors only return valid
+ * information after AccountManager::FeatureCore has been enabled.
+ * See the individual methods descriptions for more details.
+ *
+ * AccountManager features can be enabled by calling becomeReady()
+ * with the desired set of features as an argument (currently only AccountManager::FeatureCore is
+ * supported), and waiting for the resulting PendingOperation to finish.
+ *
+ * All accounts returned by AccountManager are guaranteed to have the features set in the
+ * AccountFactory used by it ready.
+ *
+ * A signal is emitted to indicate that accounts are added. See newCreated() for more details.
+ *
+ * \section am_usage_sec Usage
+ *
+ * \subsection am_create_sec Creating an AccountManager object
+ *
+ * One way to create an AccountManager object is to just call the create method.
+ * For example:
+ *
+ * \code AccountManagerPtr am = AccountManager::create(); \endcode
+ *
+ * An AccountManagerPtr object is returned, which will automatically keep
+ * track of object lifetime.
+ *
+ * You can also provide a D-Bus connection as a QDBusConnection:
+ *
+ * \code AccountManagerPtr am = AccountManager::create(QDBusConnection::sessionBus()); \endcode
+ *
+ * \subsection am_ready_sec Making AccountManager ready to use
+ *
+ * An AccountManager object needs to become ready before usage, meaning that the
+ * introspection process finished and the object accessors can be used.
+ *
+ * To make the object ready, use becomeReady() and wait for the
+ * PendingOperation::finished() signal to be emitted.
+ *
+ * \code
+ *
+ * class MyClass : public QObject
+ * {
+ * QOBJECT
+ *
+ * public:
+ * MyClass(QObject *parent = 0);
+ * ~MyClass() { }
+ *
+ * private Q_SLOTS:
+ * void onAccountManagerReady(Tp::PendingOperation*);
+ *
+ * private:
+ * AccountManagerPtr mAM;
+ * };
+ *
+ * MyClass::MyClass(QObject *parent)
+ * : QObject(parent)
+ * mAM(AccountManager::create())
+ * {
+ * connect(mAM->becomeReady(),
+ * SIGNAL(finished(Tp::PendingOperation*)),
+ * SLOT(onAccountManagerReady(Tp::PendingOperation*)));
+ * }
+ *
+ * void MyClass::onAccountManagerReady(Tp::PendingOperation *op)
+ * {
+ * if (op->isError()) {
+ * qWarning() << "Account manager cannot become ready:" <<
+ * op->errorName() << "-" << op->errorMessage();
+ * return;
+ * }
+ *
+ * // AccountManager is now ready
+ * qDebug() << "All accounts:";
+ * foreach (const Tp::AccountPtr &acc, mAM->allAccounts()) {
+ * qDebug() << " path:" << acc->objectPath();
+ * }
+ * }
+ *
+ * \endcode
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * AccountManager object usable.
+ *
+ * Note that this feature must be enabled in order to use most AccountManager
+ * methods.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature AccountManager::FeatureCore = Feature(QLatin1String(AccountManager::staticMetaObject.className()), 0, true);
+
+/**
+ * Create a new AccountManager object using the given \a bus.
+ *
+ * The instance will use an account factory creating Tp::Account objects with Account::FeatureCore
+ * ready, a connection factory creating Tp::Connection objects with no features ready, a channel
+ * factory creating stock Tp::Channel subclasses, as appropriate, with no features ready, and a
+ * contact factory creating Tp::Contact objects with no features ready.
+ *
+ * \param bus QDBusConnection to use.
+ * \return An AccountManagerPtr object pointing to the newly created
+ * AccountManager object.
+ */
+AccountManagerPtr AccountManager::create(const QDBusConnection &bus)
+{
+ return AccountManagerPtr(new AccountManager(bus,
+ AccountFactory::create(bus, Account::FeatureCore), ConnectionFactory::create(bus),
+ ChannelFactory::create(bus), ContactFactory::create(),
+ AccountManager::FeatureCore));
+}
+
+/**
+ * Create a new AccountManager using QDBusConnection::sessionBus() and the given factories.
+ *
+ * The connection, channel and contact factories are passed to any Account objects created by this
+ * account manager object. In fact, they're not used directly by AccountManager at all.
+ *
+ * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus().
+ *
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return An AccountManagerPtr object pointing to the newly created
+ * AccountManager object.
+ */
+AccountManagerPtr AccountManager::create(const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return AccountManagerPtr(new AccountManager(QDBusConnection::sessionBus(),
+ accountFactory, connectionFactory, channelFactory, contactFactory,
+ AccountManager::FeatureCore));
+}
+
+/**
+ * Create a new AccountManager using the given \a bus and the given factories.
+ *
+ * The connection, channel and contact factories are passed to any Account objects created by this
+ * account manager object. In fact, they're not used directly by AccountManager at all.
+ *
+ * A warning is printed if the factories are not for \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return An AccountManagerPtr object pointing to the newly created
+ * AccountManager object.
+ */
+AccountManagerPtr AccountManager::create(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return AccountManagerPtr(new AccountManager(bus,
+ accountFactory, connectionFactory, channelFactory, contactFactory,
+ AccountManager::FeatureCore));
+}
+
+/**
+ * Construct a new AccountManager object using the given \a bus and the given factories.
+ *
+ * The connection, channel and contact factories are passed to any Account objects created by this
+ * account manager object. In fact, they're not used directly by AccountManager at all.
+ *
+ * A warning is printed if the factories are not for \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \param coreFeature The core feature of the Account subclass. The corresponding introspectable
+ * should depend on AccountManager::FeatureCore.
+ */
+AccountManager::AccountManager(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const Feature &coreFeature)
+ : StatelessDBusProxy(bus,
+ QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME),
+ QLatin1String(TELEPATHY_ACCOUNT_MANAGER_OBJECT_PATH), coreFeature),
+ OptionalInterfaceFactory<AccountManager>(this),
+ mPriv(new Private(this, accountFactory, connectionFactory, channelFactory, contactFactory))
+{
+}
+
+/**
+ * Class destructor.
+ */
+AccountManager::~AccountManager()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account factory used by this account manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the AccountFactory object.
+ */
+AccountFactoryConstPtr AccountManager::accountFactory() const
+{
+ return mPriv->accFactory;
+}
+
+/**
+ * Return the connection factory used by this account manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ConnectionFactory object.
+ */
+ConnectionFactoryConstPtr AccountManager::connectionFactory() const
+{
+ return mPriv->connFactory;
+}
+
+/**
+ * Return the channel factory used by this account manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ChannelFactory object.
+ */
+ChannelFactoryConstPtr AccountManager::channelFactory() const
+{
+ return mPriv->chanFactory;
+}
+
+/**
+ * Return the contact factory used by this account manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ContactFactory object.
+ */
+ContactFactoryConstPtr AccountManager::contactFactory() const
+{
+ return mPriv->contactFactory;
+}
+
+/**
+ * Return a list containing all accounts.
+ *
+ * Newly accounts added and/or discovered are signaled via newAccount().
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A list of pointers to Account objects.
+ */
+QList<AccountPtr> AccountManager::allAccounts() const
+{
+ QList<AccountPtr> ret;
+ foreach (const AccountPtr &account, mPriv->accounts) {
+ ret << account;
+ }
+ return ret;
+}
+
+/**
+ * Return a set of accounts containing all valid accounts.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::validAccounts() const
+{
+ QVariantMap filter;
+ filter.insert(QLatin1String("valid"), true);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all invalid accounts.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::invalidAccounts() const
+{
+ QVariantMap filter;
+ filter.insert(QLatin1String("valid"), false);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all enabled accounts.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::enabledAccounts() const
+{
+ QVariantMap filter;
+ filter.insert(QLatin1String("enabled"), true);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all disabled accounts.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::disabledAccounts() const
+{
+ QVariantMap filter;
+ filter.insert(QLatin1String("enabled"), false);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all online accounts.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::onlineAccounts() const
+{
+ QVariantMap filter;
+ filter.insert(QLatin1String("online"), true);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all offline accounts.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::offlineAccounts() const
+{
+ QVariantMap filter;
+ filter.insert(QLatin1String("online"), false);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support text chats by
+ * providing a contact identifier.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::textChatAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::textChat());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support text chat
+ * rooms.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::textChatroomAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::textChatroom());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support media calls (using the
+ * StreamedMedia interface) by providing a contact identifier.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::streamedMediaCallAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::streamedMediaCall());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support audio calls (using the
+ * StreamedMedia interface) by providing a contact identifier.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::streamedMediaAudioCallAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::streamedMediaAudioCall());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support video calls (using the
+ * StreamedMedia interface) by providing a contact identifier.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::streamedMediaVideoCallAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::streamedMediaVideoCall());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support video calls with audio (using the
+ * StreamedMedia interface) by providing a contact identifier.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::streamedMediaVideoCallWithAudioAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(
+ RequestableChannelClassSpec::streamedMediaVideoCallWithAudio());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that support file transfers by
+ * providing a contact identifier.
+ *
+ * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities
+ * ready.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::fileTransferAccounts() const
+{
+ if (!accountFactory()->features().contains(Account::FeatureCapabilities)) {
+ warning() << "Account filtering by capabilities can only be used with an AccountFactory"
+ << "which makes Account::FeatureCapabilities ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create();
+ filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::fileTransfer());
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts for the given \a
+ * protocolName.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \param protocolName The name of the protocol used to filter accounts.
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::accountsByProtocol(
+ const QString &protocolName) const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "Account filtering requires AccountManager to be ready";
+ return filterAccounts(AccountFilterConstPtr());
+ }
+
+ QVariantMap filter;
+ filter.insert(QLatin1String("protocolName"), protocolName);
+ return filterAccounts(filter);
+}
+
+/**
+ * Return a set of accounts containing all accounts that match the given \a
+ * filter criteria.
+ *
+ * For AccountCapabilityFilter filtering, an AccountFactory which makes
+ * Account::FeatureCapabilities ready must be used.
+ *
+ * See AccountSet documentation for more details.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \param filter The desired filter.
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::filterAccounts(const AccountFilterConstPtr &filter) const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "Account filtering requires AccountManager to be ready";
+ return AccountSetPtr(new AccountSet(AccountManagerPtr(
+ (AccountManager *) this), AccountFilterConstPtr()));
+ }
+
+ return AccountSetPtr(new AccountSet(AccountManagerPtr(
+ (AccountManager *) this), filter));
+}
+
+/**
+ * Return a set of accounts containing all accounts that match the given \a
+ * filter criteria.
+ *
+ * The \a filter is composed by Account property names and values as map items.
+ *
+ * The following example will return all jabber accounts that are enabled:
+ *
+ * \code
+ *
+ * void MyClass::init()
+ * {
+ * mAM = AccountManager::create();
+ * connect(mAM->becomeReady(),
+ * SIGNAL(finished(Tp::PendingOperation*)),
+ * SLOT(onAccountManagerReady(Tp::PendingOperation*)));
+ * }
+ *
+ * void MyClass::onAccountManagerReady(Tp::PendingOperation *op)
+ * {
+ * if (op->isError()) {
+ * qWarning() << "Account manager cannot become ready:" <<
+ * op->errorName() << "-" << op->errorMessage();
+ * return;
+ * }
+ *
+ * QVariantMap filter;
+ * filter.insert(QLatin1String("protocolName"), QLatin1String("jabber"));
+ * filter.insert(QLatin1String("enabled"), true);
+ * filteredAccountSet = mAM->filterAccounts(filter);
+ * // connect to AccountSet::accountAdded/accountRemoved signals
+ * QList<AccountPtr> accounts = filteredAccountSet->accounts();
+ * // do something with accounts
+ * }
+ *
+ * \endcode
+ *
+ * See AccountSet documentation for more details.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \param filter The desired filter.
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ */
+AccountSetPtr AccountManager::filterAccounts(const QVariantMap &filter) const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "Account filtering requires AccountManager to be ready";
+ return AccountSetPtr(new AccountSet(AccountManagerPtr(
+ (AccountManager *) this), QVariantMap()));
+ }
+
+ return AccountSetPtr(new AccountSet(AccountManagerPtr(
+ (AccountManager *) this), filter));
+}
+
+/**
+ * Return the account for the given \a path.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \param path The account object path.
+ * \return A pointer to an AccountSet object containing the matching accounts.
+ * \sa allAccounts(), accountsForPaths()
+ */
+AccountPtr AccountManager::accountForPath(const QString &path) const
+{
+ if (!isReady(FeatureCore)) {
+ return AccountPtr();
+ }
+
+ return mPriv->accounts.value(path);
+}
+
+/**
+ * Return a list of accounts for the given \a paths.
+ *
+ * The returned list will have one AccountPtr object for each given path. If
+ * a given path is invalid the returned AccountPtr object will point to 0.
+ * AccountPtr::isNull() will return true.
+ *
+ * This method requires AccountManager::FeatureCore to be ready.
+ *
+ * \param paths List of accounts object paths.
+ * \return A list of pointers to Account objects for the given
+ * \a paths. Null AccountPtr objects will be used as list elements for each invalid path.
+ * \sa allAccounts(), accountForPath()
+ */
+QList<AccountPtr> AccountManager::accountsForPaths(const QStringList &paths) const
+{
+ if (!isReady(FeatureCore)) {
+ return QList<AccountPtr>();
+ }
+
+ QList<AccountPtr> result;
+ foreach (const QString &path, paths) {
+ result << accountForPath(path);
+ }
+ return result;
+}
+
+/**
+ * Return a list of the fully qualified names of properties that can be set
+ * when calling createAccount().
+ *
+ * \return A list of fully qualified D-Bus property names,
+ * such as "org.freedesktop.Telepathy.Account.Enabled".
+ * \sa createAccount()
+ */
+QStringList AccountManager::supportedAccountProperties() const
+{
+ return mPriv->supportedAccountProperties;
+}
+
+/**
+ * Create an account with the given parameters.
+ *
+ * The optional \a properties argument can be used to set any property listed in
+ * supportedAccountProperties() at the time the account is created.
+ *
+ * \param connectionManager The name of the connection manager to create the account
+ * for.
+ * \param protocol The name of the protocol to create the account for.
+ * \param displayName The account display name.
+ * \param parameters The account parameters.
+ * \param properties An optional map from fully qualified D-Bus property
+ * names such as "org.freedesktop.Telepathy.Account.Enabled"
+ * to their values.
+ * \return A PendingAccount object which will emit PendingAccount::finished
+ * when the account has been created of failed its creation process.
+ * \sa supportedAccountProperties()
+ */
+PendingAccount *AccountManager::createAccount(const QString &connectionManager,
+ const QString &protocol, const QString &displayName,
+ const QVariantMap &parameters, const QVariantMap &properties)
+{
+ return new PendingAccount(AccountManagerPtr(this), connectionManager,
+ protocol, displayName, parameters, properties);
+}
+
+/**
+ * Return the Client::AccountManagerInterface interface proxy object for this
+ * account manager. This method is protected since the convenience methods
+ * provided by this class should generally be used instead of calling D-Bus
+ * methods directly.
+ *
+ * \return A pointer to the existing Client::AccountManagerInterface object for
+ * this AccountManager object.
+ */
+Client::AccountManagerInterface *AccountManager::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+void AccountManager::introspectMain()
+{
+ mPriv->introspectMain(mPriv);
+}
+
+void AccountManager::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ mPriv->gotInitialAccounts = true;
+
+ debug() << "Got reply to Properties.GetAll(AccountManager)";
+ props = reply.value();
+
+ if (props.contains(QLatin1String("Interfaces"))) {
+ setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")]));
+ mPriv->readinessHelper->setInterfaces(interfaces());
+ }
+
+ if (props.contains(QLatin1String("SupportedAccountProperties"))) {
+ mPriv->supportedAccountProperties =
+ qdbus_cast<QStringList>(props[QLatin1String("SupportedAccountProperties")]);
+ }
+
+ QSet<QString> paths = mPriv->getAccountPathsFromProps(props);
+ foreach (const QString &path, paths) {
+ mPriv->addAccountForPath(path);
+ }
+
+ mPriv->checkIntrospectionCompleted();
+ } else {
+ if (mPriv->reintrospectionRetries++ < maxReintrospectionRetries) {
+ int retryInterval = reintrospectionRetryInterval;
+ if (reply.error().type() == QDBusError::TimedOut) {
+ retryInterval = 0;
+ }
+ QTimer::singleShot(retryInterval, this, SLOT(introspectMain()));
+ } else {
+ warning() << "GetAll(AccountManager) failed with" <<
+ reply.error().name() << ":" << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore,
+ false, reply.error());
+ }
+ }
+
+ watcher->deleteLater();
+}
+
+void AccountManager::onAccountReady(Tp::PendingOperation *op)
+{
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ AccountPtr account(AccountPtr::qObjectCast(pr->proxy()));
+ QString path = account->objectPath();
+
+ /* Some error occurred or the account was removed before become ready */
+ if (op->isError() || !mPriv->incompleteAccounts.contains(path)) {
+ mPriv->incompleteAccounts.remove(path);
+ mPriv->checkIntrospectionCompleted();
+ return;
+ }
+
+ mPriv->incompleteAccounts.remove(path);
+
+ // We shouldn't end up here twice for the same account - that would also mean newAccount being
+ // emitted twice for an account, and AccountSets getting confused as a result
+ Q_ASSERT(!mPriv->accounts.contains(path));
+ mPriv->accounts.insert(path, account);
+
+ if (isReady(FeatureCore)) {
+ emit newAccount(account);
+ }
+
+ mPriv->checkIntrospectionCompleted();
+}
+
+void AccountManager::onAccountValidityChanged(const QDBusObjectPath &objectPath,
+ bool valid)
+{
+ if (!mPriv->gotInitialAccounts) {
+ return;
+ }
+
+ QString path = objectPath.path();
+
+ if (!mPriv->incompleteAccounts.contains(path) &&
+ !mPriv->accounts.contains(path)) {
+ debug() << "New account" << path;
+ mPriv->addAccountForPath(path);
+ }
+}
+
+void AccountManager::onAccountRemoved(const QDBusObjectPath &objectPath)
+{
+ if (!mPriv->gotInitialAccounts) {
+ return;
+ }
+
+ QString path = objectPath.path();
+
+ /* the account is either in mPriv->incompleteAccounts or mPriv->accounts */
+ if (mPriv->accounts.contains(path)) {
+ mPriv->accounts.remove(path);
+
+ if (isReady(FeatureCore)) {
+ debug() << "Account" << path << "removed";
+ } else {
+ debug() << "Account" << path << "removed while the AM "
+ "was not completely introspected";
+ }
+ } else if (mPriv->incompleteAccounts.contains(path)) {
+ mPriv->incompleteAccounts.remove(path);
+ debug() << "Account" << path << "was removed, but it was "
+ "not completely introspected, ignoring";
+ } else {
+ debug() << "Got AccountRemoved for unknown account" << path << ", ignoring";
+ }
+}
+
+/**
+ * \fn void AccountManager::newAccount(const Tp::AccountPtr &account)
+ *
+ * Emitted when a new account is created.
+ *
+ * The new \a account will have the features set in the AccountFactory used by this
+ * account manager ready and the same connection, channel and contact factories as used by this
+ * account manager.
+ *
+ * \param account The newly created account.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/account-manager.h b/qt4/TelepathyQt4/account-manager.h
new file mode 100644
index 000000000..7f5a7b256
--- /dev/null
+++ b/qt4/TelepathyQt4/account-manager.h
@@ -0,0 +1,152 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_manager_h_HEADER_GUARD_
+#define _TelepathyQt4_account_manager_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-account-manager.h>
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/StatelessDBusProxy>
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+#include <QDBusObjectPath>
+#include <QSet>
+#include <QString>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class PendingAccount;
+
+class TELEPATHY_QT4_EXPORT AccountManager : public StatelessDBusProxy,
+ public OptionalInterfaceFactory<AccountManager>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(AccountManager)
+
+public:
+ static const Feature FeatureCore;
+
+ static AccountManagerPtr create(const QDBusConnection &bus);
+ static AccountManagerPtr create(
+ const AccountFactoryConstPtr &accountFactory =
+ AccountFactory::create(QDBusConnection::sessionBus(), Account::FeatureCore),
+ const ConnectionFactoryConstPtr &connectionFactory =
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ const ChannelFactoryConstPtr &channelFactory =
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+ static AccountManagerPtr create(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+
+ virtual ~AccountManager();
+
+ AccountFactoryConstPtr accountFactory() const;
+ ConnectionFactoryConstPtr connectionFactory() const;
+ ChannelFactoryConstPtr channelFactory() const;
+ ContactFactoryConstPtr contactFactory() const;
+
+ QList<AccountPtr> allAccounts() const;
+
+ AccountSetPtr validAccounts() const;
+ AccountSetPtr invalidAccounts() const;
+
+ AccountSetPtr enabledAccounts() const;
+ AccountSetPtr disabledAccounts() const;
+
+ AccountSetPtr onlineAccounts() const;
+ AccountSetPtr offlineAccounts() const;
+
+ AccountSetPtr textChatAccounts() const;
+ AccountSetPtr textChatroomAccounts() const;
+
+ AccountSetPtr streamedMediaCallAccounts() const;
+ AccountSetPtr streamedMediaAudioCallAccounts() const;
+ AccountSetPtr streamedMediaVideoCallAccounts() const;
+ AccountSetPtr streamedMediaVideoCallWithAudioAccounts() const;
+
+ AccountSetPtr fileTransferAccounts() const;
+
+ AccountSetPtr accountsByProtocol(const QString &protocolName) const;
+
+ AccountSetPtr filterAccounts(const AccountFilterConstPtr &filter) const;
+ AccountSetPtr filterAccounts(const QVariantMap &filter) const;
+
+ AccountPtr accountForPath(const QString &path) const;
+ QList<AccountPtr> accountsForPaths(const QStringList &paths) const;
+
+ QStringList supportedAccountProperties() const;
+ PendingAccount *createAccount(const QString &connectionManager,
+ const QString &protocol, const QString &displayName,
+ const QVariantMap &parameters,
+ const QVariantMap &properties = QVariantMap());
+
+Q_SIGNALS:
+ void newAccount(const Tp::AccountPtr &account);
+
+protected:
+ AccountManager(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const Feature &coreFeature);
+
+ Client::AccountManagerInterface *baseInterface() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void introspectMain();
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onAccountReady(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onAccountValidityChanged(const QDBusObjectPath &objectPath,
+ bool valid);
+ TELEPATHY_QT4_NO_EXPORT void onAccountRemoved(const QDBusObjectPath &objectPath);
+
+private:
+ friend class PendingAccount;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account-manager.xml b/qt4/TelepathyQt4/account-manager.xml
new file mode 100644
index 000000000..278f4276f
--- /dev/null
+++ b/qt4/TelepathyQt4/account-manager.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Account Manager interfaces</tp:title>
+
+<xi:include href="../spec/Account_Manager.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/account-property-filter.cpp b/qt4/TelepathyQt4/account-property-filter.cpp
new file mode 100644
index 000000000..24343b42e
--- /dev/null
+++ b/qt4/TelepathyQt4/account-property-filter.cpp
@@ -0,0 +1,94 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AccountPropertyFilter>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <QLatin1String>
+#include <QStringList>
+#include <QMetaObject>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT AccountPropertyFilter::Private
+{
+ Private()
+ {
+ if (supportedAccountProperties.isEmpty()) {
+ const QMetaObject metaObject = Account::staticMetaObject;
+ for (int i = metaObject.propertyOffset(); i < metaObject.propertyCount(); ++i) {
+ supportedAccountProperties << QLatin1String(metaObject.property(i).name());
+ }
+ }
+ }
+
+ static QStringList supportedAccountProperties;
+};
+
+QStringList AccountPropertyFilter::Private::supportedAccountProperties;
+
+/**
+ * \class Tp::AccountPropertyFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/account-property-filter.h <TelepathyQt4/AccountPropertyFilter>
+ *
+ * \brief The AccountPropertyFilter class provides a filter object to be used
+ * to filter accounts by properties.
+ */
+
+AccountPropertyFilter::AccountPropertyFilter()
+ : GenericPropertyFilter<Account>(),
+ mPriv(new Private())
+{
+}
+
+AccountPropertyFilter::~AccountPropertyFilter()
+{
+ delete mPriv;
+}
+
+bool AccountPropertyFilter::isValid() const
+{
+ QVariantMap mFilter = filter();
+ if (mFilter.isEmpty()) {
+ return false;
+ }
+
+ QVariantMap::const_iterator i = mFilter.constBegin();
+ QVariantMap::const_iterator end = mFilter.constEnd();
+ while (i != end) {
+ QString propertyName = i.key();
+ if (!mPriv->supportedAccountProperties.contains(propertyName)) {
+ warning() << "Invalid filter key" << propertyName <<
+ "while filtering account by properties";
+ return false;
+ }
+ ++i;
+ }
+
+ return true;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/account-property-filter.h b/qt4/TelepathyQt4/account-property-filter.h
new file mode 100644
index 000000000..31cc23988
--- /dev/null
+++ b/qt4/TelepathyQt4/account-property-filter.h
@@ -0,0 +1,59 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_property_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_account_property_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/GenericPropertyFilter>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT AccountPropertyFilter : public GenericPropertyFilter<Account>
+{
+public:
+ static AccountPropertyFilterPtr create()
+ {
+ return AccountPropertyFilterPtr(new AccountPropertyFilter);
+ }
+
+ ~AccountPropertyFilter();
+
+ bool isValid() const;
+
+private:
+ AccountPropertyFilter();
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account-set-internal.h b/qt4/TelepathyQt4/account-set-internal.h
new file mode 100644
index 000000000..8c9fc73be
--- /dev/null
+++ b/qt4/TelepathyQt4/account-set-internal.h
@@ -0,0 +1,82 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AccountPropertyFilter>
+
+namespace Tp
+{
+
+class ConnectionCapabilities;
+
+struct TELEPATHY_QT4_NO_EXPORT AccountSet::Private
+{
+ class AccountWrapper;
+
+ Private(AccountSet *parent, const AccountManagerPtr &accountManager,
+ const AccountFilterConstPtr &filter);
+ Private(AccountSet *parent, const AccountManagerPtr &accountManager,
+ const QVariantMap &filter);
+
+ void init();
+ void connectSignals();
+ void insertAccounts();
+ void insertAccount(const AccountPtr &account);
+ void removeAccount(const AccountPtr &account);
+ void wrapAccount(const AccountPtr &account);
+ void filterAccount(const AccountPtr &account);
+ bool accountMatchFilter(AccountWrapper *account);
+
+ AccountSet *parent;
+ AccountManagerPtr accountManager;
+ AccountFilterConstPtr filter;
+ QHash<QString, AccountWrapper *> wrappers;
+ QHash<QString, AccountPtr> accounts;
+ bool ready;
+};
+
+class TELEPATHY_QT4_NO_EXPORT AccountSet::Private::AccountWrapper : public QObject
+{
+ Q_OBJECT
+
+public:
+ AccountWrapper(const AccountPtr &account, QObject *parent = 0);
+ ~AccountWrapper();
+
+ AccountPtr account() const { return mAccount; }
+
+Q_SIGNALS:
+ void accountRemoved(const Tp::AccountPtr &account);
+ void accountPropertyChanged(const Tp::AccountPtr &account,
+ const QString &propertyName);
+ void accountCapabilitiesChanged(const Tp::AccountPtr &account,
+ const Tp::ConnectionCapabilities &capabilities);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onAccountRemoved();
+ TELEPATHY_QT4_NO_EXPORT void onAccountPropertyChanged(const QString &propertyName);
+ TELEPATHY_QT4_NO_EXPORT void onAccountCapalitiesChanged(const Tp::ConnectionCapabilities &capabilities);
+
+private:
+ AccountPtr mAccount;
+};
+
+} // Tp
diff --git a/qt4/TelepathyQt4/account-set.cpp b/qt4/TelepathyQt4/account-set.cpp
new file mode 100644
index 000000000..c6fc086ef
--- /dev/null
+++ b/qt4/TelepathyQt4/account-set.cpp
@@ -0,0 +1,418 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AccountSet>
+#include "TelepathyQt4/account-set-internal.h"
+
+#include "TelepathyQt4/_gen/account-set.moc.hpp"
+#include "TelepathyQt4/_gen/account-set-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFilter>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ConnectionManager>
+
+namespace Tp
+{
+
+AccountSet::Private::Private(AccountSet *parent,
+ const AccountManagerPtr &accountManager,
+ const AccountFilterConstPtr &filter)
+ : parent(parent),
+ accountManager(accountManager),
+ filter(filter),
+ ready(false)
+{
+ init();
+}
+
+AccountSet::Private::Private(AccountSet *parent,
+ const AccountManagerPtr &accountManager,
+ const QVariantMap &filterMap)
+ : parent(parent),
+ accountManager(accountManager),
+ ready(false)
+{
+ AccountPropertyFilterPtr propertyFilter = AccountPropertyFilter::create();
+ for (QVariantMap::const_iterator i = filterMap.constBegin();
+ i != filterMap.constEnd(); ++i) {
+ propertyFilter->addProperty(i.key(), i.value());
+ }
+ filter = AccountFilterPtr::dynamicCast(propertyFilter);
+ init();
+}
+
+void AccountSet::Private::init()
+{
+ if (filter->isValid()) {
+ connectSignals();
+ insertAccounts();
+ ready = true;
+ }
+}
+
+void AccountSet::Private::connectSignals()
+{
+ parent->connect(accountManager.data(),
+ SIGNAL(newAccount(Tp::AccountPtr)),
+ SLOT(onNewAccount(Tp::AccountPtr)));
+}
+
+void AccountSet::Private::insertAccounts()
+{
+ foreach (const Tp::AccountPtr &account, accountManager->allAccounts()) {
+ insertAccount(account);
+ }
+}
+
+void AccountSet::Private::insertAccount(const Tp::AccountPtr &account)
+{
+ QString accountPath = account->objectPath();
+ Q_ASSERT(!wrappers.contains(accountPath));
+ wrapAccount(account);
+ filterAccount(account);
+}
+
+void AccountSet::Private::removeAccount(const Tp::AccountPtr &account)
+{
+ QString accountPath = account->objectPath();
+ Q_ASSERT(wrappers.contains(accountPath));
+ accounts.remove(accountPath);
+
+ AccountWrapper *wrapper = wrappers.take(accountPath);
+ Q_ASSERT(wrapper->disconnect(parent));
+ wrapper->deleteLater();
+
+ emit parent->accountRemoved(account);
+}
+
+void AccountSet::Private::wrapAccount(const AccountPtr &account)
+{
+ AccountWrapper *wrapper = new AccountWrapper(account, parent);
+ parent->connect(wrapper,
+ SIGNAL(accountRemoved(Tp::AccountPtr)),
+ SLOT(onAccountRemoved(Tp::AccountPtr)));
+ parent->connect(wrapper,
+ SIGNAL(accountPropertyChanged(Tp::AccountPtr,QString)),
+ SLOT(onAccountChanged(Tp::AccountPtr)));
+ parent->connect(wrapper,
+ SIGNAL(accountCapabilitiesChanged(Tp::AccountPtr,Tp::ConnectionCapabilities)),
+ SLOT(onAccountChanged(Tp::AccountPtr)));
+ wrappers.insert(account->objectPath(), wrapper);
+}
+
+void AccountSet::Private::filterAccount(const AccountPtr &account)
+{
+ QString accountPath = account->objectPath();
+ Q_ASSERT(wrappers.contains(accountPath));
+ AccountWrapper *wrapper = wrappers[accountPath];
+
+ /* account changed, let's check if it matches filter */
+ if (accountMatchFilter(wrapper)) {
+ if (!accounts.contains(account->objectPath())) {
+ accounts.insert(account->objectPath(), account);
+ if (ready) {
+ emit parent->accountAdded(account);
+ }
+ }
+ } else {
+ if (accounts.contains(account->objectPath())) {
+ accounts.remove(account->objectPath());
+ if (ready) {
+ emit parent->accountRemoved(account);
+ }
+ }
+ }
+}
+
+bool AccountSet::Private::accountMatchFilter(AccountWrapper *wrapper)
+{
+ if (!filter) {
+ return true;
+ }
+
+ return filter->matches(wrapper->account());
+}
+
+AccountSet::Private::AccountWrapper::AccountWrapper(
+ const AccountPtr &account, QObject *parent)
+ : QObject(parent),
+ mAccount(account)
+{
+ connect(account.data(),
+ SIGNAL(removed()),
+ SLOT(onAccountRemoved()));
+ connect(account.data(),
+ SIGNAL(propertyChanged(QString)),
+ SLOT(onAccountPropertyChanged(QString)));
+ connect(account.data(),
+ SIGNAL(capabilitiesChanged(Tp::ConnectionCapabilities)),
+ SLOT(onAccountCapalitiesChanged(Tp::ConnectionCapabilities)));
+}
+
+AccountSet::Private::AccountWrapper::~AccountWrapper()
+{
+}
+
+void AccountSet::Private::AccountWrapper::onAccountRemoved()
+{
+ emit accountRemoved(mAccount);
+}
+
+void AccountSet::Private::AccountWrapper::onAccountPropertyChanged(
+ const QString &propertyName)
+{
+ emit accountPropertyChanged(mAccount, propertyName);
+}
+
+void AccountSet::Private::AccountWrapper::onAccountCapalitiesChanged(
+ const ConnectionCapabilities &caps)
+{
+ emit accountCapabilitiesChanged(mAccount, caps);
+}
+
+/**
+ * \class AccountSet
+ * \ingroup clientaccount
+ * \headerfile TelepathyQt4/account-set.h <TelepathyQt4/AccountSet>
+ *
+ * \brief The AccountSet class represents a set of Telepathy accounts
+ * filtered by a given criteria.
+ *
+ * AccountSet is automatically updated whenever accounts that match the given
+ * criteria are added, removed or updated.
+ *
+ * \section account_set_usage_sec Usage
+ *
+ * \subsection account_set_create_sec Creating an AccountSet object
+ *
+ * The easiest way to create AccountSet objects is through AccountManager. One
+ * can just use the AccountManager convenience methods such as
+ * AccountManager::validAccounts() to get a set of account objects
+ * representing valid accounts.
+ *
+ * For example:
+ *
+ * \code
+ *
+ * class MyClass : public QObject
+ * {
+ * QOBJECT
+ *
+ * public:
+ * MyClass(QObject *parent = 0);
+ * ~MyClass() { }
+ *
+ * private Q_SLOTS:
+ * void onAccountManagerReady(Tp::PendingOperation *);
+ * void onValidAccountAdded(const Tp::AccountPtr &);
+ * void onValidAccountRemoved(const Tp::AccountPtr &);
+ *
+ * private:
+ * AccountManagerPtr am;
+ * AccountSetPtr validAccountsSet;
+ * };
+ *
+ * MyClass::MyClass(QObject *parent)
+ * : QObject(parent)
+ * am(AccountManager::create())
+ * {
+ * connect(am->becomeReady(),
+ * SIGNAL(finished(Tp::PendingOperation*)),
+ * SLOT(onAccountManagerReady(Tp::PendingOperation*)));
+ * }
+ *
+ * void MyClass::onAccountManagerReady(Tp::PendingOperation *op)
+ * {
+ * if (op->isError()) {
+ * qWarning() << "Account manager cannot become ready:" <<
+ * op->errorName() << "-" << op->errorMessage();
+ * return;
+ * }
+ *
+ * validAccountsSet = am->validAccounts();
+ * connect(validAccountsSet.data(),
+ * SIGNAL(accountAdded(const Tp::AccountPtr &)),
+ * SLOT(onValidAccountAdded(const Tp::AccountPtr &)));
+ * connect(validAccountsSet.data(),
+ * SIGNAL(accountRemoved(const Tp::AccountPtr &)),
+ * SLOT(onValidAccountRemoved(const Tp::AccountPtr &)));
+ *
+ * QList<AccountPtr> accounts = validAccountsSet->accounts();
+ * // do something with accounts
+ * }
+ *
+ * void MyClass::onValidAccountAdded(const Tp::AccountPtr &account)
+ * {
+ * // do something with account
+ * }
+ *
+ * void MyClass::onValidAccountRemoved(const Tp::AccountPtr &account)
+ * {
+ * // do something with account
+ * }
+ *
+ * \endcode
+ *
+ * You can also define your own filter using AccountManager::filterAccounts:
+ *
+ * \code
+ *
+ * void MyClass::onAccountManagerReady(Tp::PendingOperation *op)
+ * {
+ * ...
+ *
+ * AccountPropertyFilterPtr filter = AccountPropertyFilter::create();
+ * filter->addProperty(QLatin1String("protocolName"), QLatin1String("jabber"));
+ * filter->addProperty(QLatin1String("enabled"), true);
+ *
+ * AccountSetPtr filteredAccountSet = am->filterAccounts(filter);
+ * // connect to AccountSet::accountAdded/accountRemoved signals
+ * QList<AccountPtr> accounts = filteredAccountSet->accounts();
+ * // do something with accounts
+ *
+ * ....
+ * }
+ *
+ * \endcode
+ *
+ * Note that for AccountSet to property work with AccountCapabilityFilter
+ * objects, the feature Account::FeatureCapabilities need to be enabled in all
+ * accounts return by the AccountManager passed as param in the constructor.
+ * The easiest way to do this is to enable AccountManager feature
+ * AccountManager::FeatureFilterByCapabilities.
+ *
+ * AccountSet can also be instantiated directly, but when doing it,
+ * the AccountManager object passed as param in the constructor must be ready
+ * for AccountSet properly work.
+ */
+
+/**
+ * Construct a new AccountSet object.
+ *
+ * \param accountManager An account manager object used to filter accounts.
+ * The account manager object must be ready.
+ * \param filter The desired filter.
+ */
+AccountSet::AccountSet(const AccountManagerPtr &accountManager,
+ const AccountFilterConstPtr &filter)
+ : Object(),
+ mPriv(new Private(this, accountManager, filter))
+{
+}
+
+/**
+ * Construct a new AccountSet object.
+ *
+ * The \a filter must contain Account property names and values as map items.
+ *
+ * \param accountManager An account manager object used to filter accounts.
+ * The account manager object must be ready.
+ * \param filter The desired filter.
+ */
+AccountSet::AccountSet(const AccountManagerPtr &accountManager,
+ const QVariantMap &filter)
+ : Object(),
+ mPriv(new Private(this, accountManager, filter))
+{
+}
+
+/**
+ * Class destructor.
+ */
+AccountSet::~AccountSet()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account manager object used to filter accounts.
+ *
+ * \return A pointer to the AccountManager object.
+ */
+AccountManagerPtr AccountSet::accountManager() const
+{
+ return mPriv->accountManager;
+}
+
+/**
+ * Return the filter used to filter accounts.
+ *
+ * \return A read-only pointer the AccountFilter object.
+ */
+AccountFilterConstPtr AccountSet::filter() const
+{
+ return mPriv->filter;
+}
+
+/**
+ * Return a list of account objects that match filter.
+ *
+ * Change notification is via the accountAdded() and accountRemoved() signals.
+ *
+ * \return A list of pointers to Account objects.
+ * \sa accountAdded(), accountRemoved()
+ */
+QList<AccountPtr> AccountSet::accounts() const
+{
+ return mPriv->accounts.values();
+}
+
+/**
+ * \fn void AccountSet::accountAdded(const Tp::AccountPtr &account)
+ *
+ * Emitted whenever an account that matches filter is added to
+ * this set.
+ *
+ * \param account The account that was added to this set.
+ * \sa accounts()
+ */
+
+/**
+ * \fn void AccountSet::accountRemoved(const Tp::AccountPtr &account)
+ *
+ * Emitted whenever an account that matches filter is removed
+ * from this set.
+ *
+ * \param account The account that was removed from this set.
+ * \sa accounts()
+ */
+
+void AccountSet::onNewAccount(const AccountPtr &account)
+{
+ mPriv->insertAccount(account);
+}
+
+void AccountSet::onAccountRemoved(const AccountPtr &account)
+{
+ mPriv->removeAccount(account);
+}
+
+void AccountSet::onAccountChanged(const AccountPtr &account)
+{
+ mPriv->filterAccount(account);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/account-set.h b/qt4/TelepathyQt4/account-set.h
new file mode 100644
index 000000000..713ed5250
--- /dev/null
+++ b/qt4/TelepathyQt4/account-set.h
@@ -0,0 +1,79 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_set_h_HEADER_GUARD_
+#define _TelepathyQt4_account_set_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/Types>
+
+#include <QList>
+#include <QString>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT AccountSet : public Object
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(AccountSet)
+ Q_PROPERTY(AccountManagerPtr accountManager READ accountManager)
+ Q_PROPERTY(AccountFilterConstPtr filter READ filter)
+ Q_PROPERTY(QList<AccountPtr> accounts READ accounts)
+
+public:
+ AccountSet(const AccountManagerPtr &accountManager,
+ const AccountFilterConstPtr &filter);
+ AccountSet(const AccountManagerPtr &accountManager,
+ const QVariantMap &filter);
+ virtual ~AccountSet();
+
+ AccountManagerPtr accountManager() const;
+
+ AccountFilterConstPtr filter() const;
+
+ QList<AccountPtr> accounts() const;
+
+Q_SIGNALS:
+ void accountAdded(const Tp::AccountPtr &account);
+ void accountRemoved(const Tp::AccountPtr &account);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onNewAccount(const Tp::AccountPtr &account);
+ TELEPATHY_QT4_NO_EXPORT void onAccountRemoved(const Tp::AccountPtr &account);
+ TELEPATHY_QT4_NO_EXPORT void onAccountChanged(const Tp::AccountPtr &account);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account.cpp b/qt4/TelepathyQt4/account.cpp
new file mode 100644
index 000000000..3ea5b4f9d
--- /dev/null
+++ b/qt4/TelepathyQt4/account.cpp
@@ -0,0 +1,4472 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Account>
+
+#include "TelepathyQt4/_gen/account.moc.hpp"
+#include "TelepathyQt4/_gen/cli-account.moc.hpp"
+#include "TelepathyQt4/_gen/cli-account-body.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include "TelepathyQt4/connection-internal.h"
+
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelDispatcherInterface>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingChannelRequest>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingStringList>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/Profile>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+
+#include <QQueue>
+#include <QRegExp>
+#include <QSharedPointer>
+#include <QTimer>
+#include <QWeakPointer>
+
+#include <string.h>
+
+namespace Tp
+{
+
+namespace
+{
+
+struct PresenceStatusInfo
+{
+ QString name;
+ Tp::SimpleStatusSpec spec;
+};
+
+Tp::ConnectionPresenceType presenceTypeForStatus(const QString &status, bool &maySetOnSelf)
+{
+ static PresenceStatusInfo statuses[] = {
+ { QLatin1String("available"), { Tp::ConnectionPresenceTypeAvailable, true, true } },
+ { QLatin1String("chat"), { Tp::ConnectionPresenceTypeAvailable, true, true } },
+ { QLatin1String("chatty"), { Tp::ConnectionPresenceTypeAvailable, true, true } },
+ { QLatin1String("away"), { Tp::ConnectionPresenceTypeAway, true, true } },
+ { QLatin1String("brb"), { Tp::ConnectionPresenceTypeAway, true, true } },
+ { QLatin1String("out-to-lunch"), { Tp::ConnectionPresenceTypeAway, true, true } },
+ { QLatin1String("xa"), { Tp::ConnectionPresenceTypeExtendedAway, true, true } },
+ { QLatin1String("hidden"), { Tp::ConnectionPresenceTypeHidden, true, true } },
+ { QLatin1String("invisible"), { Tp::ConnectionPresenceTypeHidden, true, true } },
+ { QLatin1String("offline"), { Tp::ConnectionPresenceTypeOffline, true, false } },
+ { QLatin1String("unknown"), { Tp::ConnectionPresenceTypeUnknown, false, false } },
+ { QLatin1String("error"), { Tp::ConnectionPresenceTypeError, false, false } }
+ };
+
+ for (uint i = 0; i < sizeof(statuses) / sizeof(PresenceStatusInfo); ++i) {
+ if (status == statuses[i].name) {
+ maySetOnSelf = statuses[i].spec.maySetOnSelf;
+ return (Tp::ConnectionPresenceType) statuses[i].spec.type;
+ }
+ }
+
+ // fallback to type away if we don't know status
+ maySetOnSelf = true;
+ return Tp::ConnectionPresenceTypeAway;
+}
+
+Tp::PresenceSpec presenceSpecForStatus(const QString &status, bool canHaveStatusMessage)
+{
+ Tp::SimpleStatusSpec spec;
+ spec.type = presenceTypeForStatus(status, spec.maySetOnSelf);
+ spec.canHaveMessage = canHaveStatusMessage;
+ return Tp::PresenceSpec(status, spec);
+}
+
+QVariantMap textChatCommonRequest()
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ return request;
+}
+
+QVariantMap textChatRequest(const QString &contactIdentifier)
+{
+ QVariantMap request = textChatCommonRequest();
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ contactIdentifier);
+ return request;
+}
+
+QVariantMap textChatRequest(const Tp::ContactPtr &contact)
+{
+ QVariantMap request = textChatCommonRequest();
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ contact ? contact->handle().at(0) : (uint) 0);
+ return request;
+}
+
+QVariantMap textChatroomRequest(const QString &roomName)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeRoom);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ roomName);
+ return request;
+}
+
+QVariantMap streamedMediaCallCommonRequest()
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ return request;
+}
+
+QVariantMap streamedMediaCallRequest(const QString &contactIdentifier)
+{
+ QVariantMap request = streamedMediaCallCommonRequest();
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ contactIdentifier);
+ return request;
+}
+
+QVariantMap streamedMediaCallRequest(const Tp::ContactPtr &contact)
+{
+ QVariantMap request = streamedMediaCallCommonRequest();
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ contact ? contact->handle().at(0) : (uint) 0);
+ return request;
+}
+
+QVariantMap streamedMediaAudioCallRequest(const QString &contactIdentifier)
+{
+ QVariantMap request = streamedMediaCallRequest(contactIdentifier);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"),
+ true);
+ return request;
+}
+
+QVariantMap streamedMediaAudioCallRequest(const Tp::ContactPtr &contact)
+{
+ QVariantMap request = streamedMediaCallRequest(contact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"),
+ true);
+ return request;
+}
+
+QVariantMap streamedMediaVideoCallRequest(const QString &contactIdentifier, bool withAudio)
+{
+ QVariantMap request = streamedMediaCallRequest(contactIdentifier);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"),
+ true);
+ if (withAudio) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"),
+ true);
+ }
+ return request;
+}
+
+QVariantMap streamedMediaVideoCallRequest(const Tp::ContactPtr &contact, bool withAudio)
+{
+ QVariantMap request = streamedMediaCallRequest(contact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"),
+ true);
+ if (withAudio) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"),
+ true);
+ }
+ return request;
+}
+
+QVariantMap fileTransferCommonRequest(const Tp::FileTransferChannelCreationProperties &properties)
+{
+ if (!properties.isValid()) {
+ warning() << "Invalid file transfer creation properties";
+ return QVariantMap();
+ }
+
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename"),
+ properties.suggestedFileName());
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType"),
+ properties.contentType());
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size"),
+ properties.size());
+
+ if (properties.hasContentHash()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType"),
+ (uint) properties.contentHashType());
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash"),
+ properties.contentHash());
+ }
+
+ if (properties.hasDescription()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Description"),
+ properties.description());
+ }
+
+ if (properties.hasLastModificationTime()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date"),
+ (qulonglong) properties.lastModificationTime().toTime_t());
+ }
+
+ if (properties.hasUri()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".URI"),
+ properties.uri());
+ }
+
+ return request;
+}
+
+QVariantMap fileTransferRequest(const QString &contactIdentifier,
+ const Tp::FileTransferChannelCreationProperties &properties)
+{
+ QVariantMap request = fileTransferCommonRequest(properties);
+
+ if (!request.isEmpty()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ contactIdentifier);
+ }
+
+ return request;
+}
+
+QVariantMap fileTransferRequest(const Tp::ContactPtr &contact,
+ const Tp::FileTransferChannelCreationProperties &properties)
+{
+ QVariantMap request = fileTransferCommonRequest(properties);
+
+ if (!request.isEmpty()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ contact ? contact->handle().at(0) : (uint) 0);
+ }
+
+ return request;
+}
+
+QVariantMap streamTubeCommonRequest(const QString &service)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"),
+ service);
+ return request;
+}
+
+QVariantMap streamTubeRequest(const QString &contactIdentifier, const QString &service)
+{
+ QVariantMap request = streamTubeCommonRequest(service);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ contactIdentifier);
+ return request;
+}
+
+QVariantMap streamTubeRequest(const Tp::ContactPtr &contact, const QString &service)
+{
+ QVariantMap request = streamTubeCommonRequest(service);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ contact ? contact->handle().at(0) : (uint) 0);
+ return request;
+}
+
+QVariantMap conferenceCommonRequest(const char *channelType, Tp::HandleType targetHandleType,
+ const QList<Tp::ChannelPtr> &channels)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(channelType));
+ if (targetHandleType != Tp::HandleTypeNone) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) targetHandleType);
+ }
+
+ Tp::ObjectPathList objectPaths;
+ foreach (const Tp::ChannelPtr &channel, channels) {
+ objectPaths << QDBusObjectPath(channel->objectPath());
+ }
+
+ request.insert(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"),
+ qVariantFromValue(objectPaths));
+ return request;
+}
+
+QVariantMap conferenceRequest(const char *channelType, Tp::HandleType targetHandleType,
+ const QList<Tp::ChannelPtr> &channels, const QStringList &initialInviteeContactsIdentifiers)
+{
+ QVariantMap request = conferenceCommonRequest(channelType, targetHandleType, channels);
+ if (!initialInviteeContactsIdentifiers.isEmpty()) {
+ request.insert(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeIDs"),
+ initialInviteeContactsIdentifiers);
+ }
+ return request;
+}
+
+QVariantMap conferenceRequest(const char *channelType, Tp::HandleType targetHandleType,
+ const QList<Tp::ChannelPtr> &channels, const QList<Tp::ContactPtr> &initialInviteeContacts)
+{
+ QVariantMap request = conferenceCommonRequest(channelType, targetHandleType, channels);
+ if (!initialInviteeContacts.isEmpty()) {
+ Tp::UIntList handles;
+ foreach (const Tp::ContactPtr &contact, initialInviteeContacts) {
+ if (!contact) {
+ continue;
+ }
+ handles << contact->handle()[0];
+ }
+ if (!handles.isEmpty()) {
+ request.insert(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE +
+ QLatin1String(".InitialInviteeHandles"), qVariantFromValue(handles));
+ }
+ }
+ return request;
+}
+
+QVariantMap conferenceTextChatRequest(const QList<Tp::ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers)
+{
+ QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT,
+ Tp::HandleTypeNone, channels, initialInviteeContactsIdentifiers);
+ return request;
+}
+
+QVariantMap conferenceTextChatRequest(const QList<Tp::ChannelPtr> &channels,
+ const QList<Tp::ContactPtr> &initialInviteeContacts)
+{
+ QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT,
+ Tp::HandleTypeNone, channels, initialInviteeContacts);
+ return request;
+}
+
+QVariantMap conferenceTextChatroomRequest(const QString &roomName,
+ const QList<Tp::ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers)
+{
+ QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT,
+ Tp::HandleTypeRoom, channels, initialInviteeContactsIdentifiers);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), roomName);
+ return request;
+}
+
+QVariantMap conferenceTextChatroomRequest(const QString &roomName,
+ const QList<Tp::ChannelPtr> &channels,
+ const QList<Tp::ContactPtr> &initialInviteeContacts)
+{
+ QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT,
+ Tp::HandleTypeRoom, channels, initialInviteeContacts);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), roomName);
+ return request;
+}
+
+QVariantMap conferenceStreamedMediaCallRequest(const QList<Tp::ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers)
+{
+ QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ Tp::HandleTypeNone, channels, initialInviteeContactsIdentifiers);
+ return request;
+}
+
+QVariantMap conferenceStreamedMediaCallRequest(const QList<Tp::ChannelPtr> &channels,
+ const QList<Tp::ContactPtr> &initialInviteeContacts)
+{
+ QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ Tp::HandleTypeNone, channels, initialInviteeContacts);
+ return request;
+}
+
+QVariantMap contactSearchRequest(const ConnectionCapabilities &capabilities,
+ const QString &server, uint limit)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH));
+ if (capabilities.contactSearchesWithSpecificServer()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH ".Server"),
+ server);
+ } else if (!server.isEmpty()) {
+ warning() << "Ignoring Server parameter for contact search, since the protocol does not support it.";
+ }
+ if (capabilities.contactSearchesWithLimit()) {
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH ".Limit"), limit);
+ } else if (limit > 0) {
+ warning() << "Ignoring Limit parameter for contact search, since the protocol does not support it.";
+ }
+ return request;
+}
+
+} // anonymous namespace
+
+struct TELEPATHY_QT4_NO_EXPORT Account::Private
+{
+ Private(Account *parent, const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ ~Private();
+
+ void init();
+
+ static void introspectMain(Private *self);
+ static void introspectAvatar(Private *self);
+ static void introspectProtocolInfo(Private *self);
+ static void introspectCapabilities(Private *self);
+
+ void updateProperties(const QVariantMap &props);
+ void retrieveAvatar();
+ bool processConnQueue();
+
+ bool checkCapabilitiesChanged(bool profileChanged);
+
+ QString connectionObjectPath() const;
+
+ // Public object
+ Account *parent;
+
+ // Factories
+ ConnectionFactoryConstPtr connFactory;
+ ChannelFactoryConstPtr chanFactory;
+ ContactFactoryConstPtr contactFactory;
+
+ // Instance of generated interface class
+ Client::AccountInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ QVariantMap parameters;
+ bool valid;
+ bool enabled;
+ bool connectsAutomatically;
+ bool hasBeenOnline;
+ bool changingPresence;
+ QString cmName;
+ QString protocolName;
+ QString serviceName;
+ ProfilePtr profile;
+ QString displayName;
+ QString nickname;
+ QString iconName;
+ QQueue<QString> connObjPathQueue;
+ ConnectionPtr connection;
+ bool mayFinishCore, coreFinished;
+ QString normalizedName;
+ Avatar avatar;
+ ConnectionManagerPtr cm;
+ ConnectionStatus connectionStatus;
+ ConnectionStatusReason connectionStatusReason;
+ QString connectionError;
+ Connection::ErrorDetails connectionErrorDetails;
+ Presence automaticPresence;
+ Presence currentPresence;
+ Presence requestedPresence;
+ bool usingConnectionCaps;
+ ConnectionCapabilities customCaps;
+
+ // The contexts should never be removed from the map, to guarantee O(1) CD introspections per bus
+ struct DispatcherContext;
+ static QMap<QString, QSharedPointer<DispatcherContext> > dispatcherContexts;
+ QSharedPointer<DispatcherContext> dispatcherContext;
+};
+
+struct Account::Private::DispatcherContext
+{
+ DispatcherContext(const QDBusConnection &bus)
+ : iface(new Client::ChannelDispatcherInterface(bus, TP_QT4_CHANNEL_DISPATCHER_BUS_NAME, TP_QT4_CHANNEL_DISPATCHER_OBJECT_PATH)),
+ introspected(false), supportsHints(false)
+ {
+ }
+
+ ~DispatcherContext()
+ {
+ delete iface;
+ }
+
+ Client::ChannelDispatcherInterface *iface;
+
+ bool introspected, supportsHints;
+ QWeakPointer<PendingVariant> introspectOp;
+
+private:
+ DispatcherContext(const DispatcherContext &);
+ void operator=(const DispatcherContext &);
+};
+
+Account::Private::Private(Account *parent, const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory)
+ : parent(parent),
+ connFactory(connFactory),
+ chanFactory(chanFactory),
+ contactFactory(contactFactory),
+ baseInterface(new Client::AccountInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ valid(false),
+ enabled(false),
+ connectsAutomatically(false),
+ hasBeenOnline(false),
+ changingPresence(false),
+ mayFinishCore(false),
+ coreFinished(false),
+ connectionStatus(ConnectionStatusDisconnected),
+ connectionStatusReason(ConnectionStatusReasonNoneSpecified),
+ usingConnectionCaps(false),
+ dispatcherContext(dispatcherContexts.value(parent->dbusConnection().name()))
+{
+ // FIXME: QRegExp probably isn't the most efficient possible way to parse
+ // this :-)
+ QRegExp rx(QLatin1String("^" TELEPATHY_ACCOUNT_OBJECT_PATH_BASE
+ "/([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM
+ "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol
+ "/([_A-Za-z][_A-Za-z0-9]*)" // account-specific part
+ ));
+
+ if (rx.exactMatch(parent->objectPath())) {
+ cmName = rx.cap(1);
+ protocolName = rx.cap(2).replace(QLatin1Char('_'), QLatin1Char('-'));
+ } else {
+ warning() << "Account object path is not spec-compliant, "
+ "trying again with a different account-specific part check";
+
+ rx = QRegExp(QLatin1String("^" TELEPATHY_ACCOUNT_OBJECT_PATH_BASE
+ "/([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM
+ "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol
+ "/([_A-Za-z0-9]*)" // account-specific part
+ ));
+ if (rx.exactMatch(parent->objectPath())) {
+ cmName = rx.cap(1);
+ protocolName = rx.cap(2).replace(QLatin1Char('_'), QLatin1Char('-'));
+ } else {
+ warning() << "Not a valid Account object path:" <<
+ parent->objectPath();
+ }
+ }
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As Account does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ ReadinessHelper::Introspectable introspectableAvatar(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectAvatar,
+ this);
+ introspectables[FeatureAvatar] = introspectableAvatar;
+
+ ReadinessHelper::Introspectable introspectableProtocolInfo(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectProtocolInfo,
+ this);
+ introspectables[FeatureProtocolInfo] = introspectableProtocolInfo;
+
+ ReadinessHelper::Introspectable introspectableCapabilities(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << FeatureCore << FeatureProtocolInfo << FeatureProfile, // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectCapabilities,
+ this);
+ introspectables[FeatureCapabilities] = introspectableCapabilities;
+
+ readinessHelper->addIntrospectables(introspectables);
+
+ if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the conn factory is not the proxy connection for"
+ << parent->objectPath();
+ }
+
+ if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection for"
+ << parent->objectPath();
+ }
+
+ if (!dispatcherContext) {
+ dispatcherContext = QSharedPointer<DispatcherContext>(new DispatcherContext(parent->dbusConnection()));
+ dispatcherContexts.insert(parent->dbusConnection().name(), dispatcherContext);
+ }
+
+ init();
+}
+
+Account::Private::~Private()
+{
+}
+
+bool Account::Private::checkCapabilitiesChanged(bool profileChanged)
+{
+ /* when the capabilities changed:
+ *
+ * - We were using the connection caps and now we don't have connection or
+ * the connection we have is not connected (changed to CM caps)
+ * - We were using the CM caps and now we have a connected connection
+ * (changed to new connection caps)
+ */
+ bool changed = false;
+
+ if (usingConnectionCaps &&
+ (parent->connection().isNull() ||
+ connection->status() != ConnectionStatusConnected)) {
+ usingConnectionCaps = false;
+ changed = true;
+ } else if (!usingConnectionCaps &&
+ !parent->connection().isNull() &&
+ connection->status() == ConnectionStatusConnected) {
+ usingConnectionCaps = true;
+ changed = true;
+ } else if (!usingConnectionCaps && profileChanged) {
+ changed = true;
+ }
+
+ if (changed && parent->isReady(FeatureCapabilities)) {
+ emit parent->capabilitiesChanged(parent->capabilities());
+ }
+
+ return changed;
+}
+
+QString Account::Private::connectionObjectPath() const
+{
+ return !connection.isNull() ? connection->objectPath() : QString();
+}
+
+QMap<QString, QSharedPointer<Account::Private::DispatcherContext> > Account::Private::dispatcherContexts;
+
+/**
+ * \class Account
+ * \ingroup clientaccount
+ * \headerfile TelepathyQt4/account.h <TelepathyQt4/Account>
+ *
+ * \brief The Account class represents a Telepathy account.
+ *
+ * The remote object accessor functions on this object (isValidAccount(),
+ * isEnabled(), and so on) don't make any D-Bus calls; instead, they return/use
+ * values cached from a previous introspection run. The introspection process
+ * populates their values in the most efficient way possible based on what the
+ * service implements.
+ *
+ * To avoid unnecessary D-Bus traffic, some accessors only return valid
+ * information after specific features have been enabled.
+ * For instance, to retrieve the account protocol information, it is necessary to
+ * enable the feature Account::FeatureProtocolInfo.
+ * See the individual methods descriptions for more details.
+ *
+ * Account features can be enabled by constructing an AccountFactory and enabling
+ * the desired features, and passing it to AccountManager or ClientRegistrar
+ * when creating them as appropriate. However, if a particular
+ * feature is only ever used in a specific circumstance, such as an user opening
+ * some settings dialog separate from the general view of the application,
+ * features can be later enabled as needed by calling becomeReady() with the additional
+ * features, and waiting for the resulting PendingOperation to finish.
+ *
+ * As an addition to accessors, signals are emitted to indicate that properties have
+ * changed, for example displayNameChanged(), iconNameChanged(), etc.
+ *
+ * Convenience methods to create channels using the channel dispatcher such as
+ * ensureTextChat(), createFileTransfer() are also provided.
+ *
+ * If the account is deleted from the AccountManager, this object
+ * will not be deleted automatically; however, it will emit invalidated()
+ * with error code #TP_QT4_ERROR_OBJECT_REMOVED and will cease to
+ * be useful.
+ *
+ * \section account_usage_sec Usage
+ *
+ * \subsection account_create_sec Creating an account object
+ *
+ * The easiest way to create account objects is through AccountManager. One can
+ * just use the AccountManager convenience methods such as
+ * AccountManager::validAccounts() to get a list of account objects representing
+ * valid accounts.
+ *
+ * If you already know the object path, you can just call create().
+ * For example:
+ *
+ * \code AccountPtr acc = Account::create(busName, objectPath); \endcode
+ *
+ * An AccountPtr object is returned, which will automatically keep
+ * track of object lifetime.
+ *
+ * You can also provide a D-Bus connection as a QDBusConnection:
+ *
+ * \code
+ *
+ * AccountPtr acc = Account::create(QDBusConnection::sessionBus(),
+ * busName, objectPath);
+ *
+ * \endcode
+ *
+ * \subsection account_ready_sec Making account ready to use
+ *
+ * An Account object needs to become ready before usage, meaning that the
+ * introspection process finished and the object accessors can be used.
+ *
+ * To make the object ready, use becomeReady() and wait for the
+ * PendingOperation::finished() signal to be emitted.
+ *
+ * \code
+ *
+ * class MyClass : public QObject
+ * {
+ * QOBJECT
+ *
+ * public:
+ * MyClass(QObject *parent = 0);
+ * ~MyClass() { }
+ *
+ * private Q_SLOTS:
+ * void onAccountReady(Tp::PendingOperation*);
+ *
+ * private:
+ * AccountPtr acc;
+ * };
+ *
+ * MyClass::MyClass(const QString &busName, const QString &objectPath,
+ * QObject *parent)
+ * : QObject(parent)
+ * acc(Account::create(busName, objectPath))
+ * {
+ * connect(acc->becomeReady(),
+ * SIGNAL(finished(Tp::PendingOperation*)),
+ * SLOT(onAccountReady(Tp::PendingOperation*)));
+ * }
+ *
+ * void MyClass::onAccountReady(Tp::PendingOperation *op)
+ * {
+ * if (op->isError()) {
+ * qWarning() << "Account cannot become ready:" <<
+ * op->errorName() << "-" << op->errorMessage();
+ * return;
+ * }
+ *
+ * // Account is now ready
+ * qDebug() << "Display name:" << acc->displayName();
+ * }
+ *
+ * \endcode
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the Account
+ * object usable.
+ *
+ * Note that this feature must be enabled in order to use most Account methods.
+ * See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature Account::FeatureCore = Feature(QLatin1String(Account::staticMetaObject.className()), 0, true);
+
+/**
+ * Feature used in order to access account avatar info.
+ *
+ * See avatar specific methods' documentation for more details.
+ *
+ * \sa avatar(), avatarChanged()
+ */
+const Feature Account::FeatureAvatar = Feature(QLatin1String(Account::staticMetaObject.className()), 1);
+
+/**
+ * Feature used in order to access account protocol info.
+ *
+ * See protocol info specific methods' documentation for more details.
+ *
+ * \sa protocolInfo()
+ */
+const Feature Account::FeatureProtocolInfo = Feature(QLatin1String(Account::staticMetaObject.className()), 2);
+
+/**
+ * Feature used in order to access account capabilities.
+ *
+ * Enabling this feature will also enable FeatureProtocolInfo and FeatureProfile.
+ *
+ * See capabilities specific methods' documentation for more details.
+ *
+ * \sa capabilities(), capabilitiesChanged()
+ */
+const Feature Account::FeatureCapabilities = Feature(QLatin1String(Account::staticMetaObject.className()), 3);
+
+/**
+ * Feature used in order to access account profile info.
+ *
+ * See profile specific methods' documentation for more details.
+ *
+ * \sa profile(), profileChanged()
+ */
+const Feature Account::FeatureProfile = FeatureProtocolInfo;
+// FeatureProfile is the same as FeatureProtocolInfo for now, as it only needs
+// the protocol info, cm name and protocol name to build a fake profile. Make it
+// a full-featured feature if needed later.
+
+/**
+ * Create a new Account object using QDBusConnection::sessionBus() and the given factories.
+ *
+ * A warning is printed if the factories are not for QDBusConnection::sessionBus().
+ *
+ * \param busName The account well-known bus name (sometimes called a "service
+ * name"). This is usually the same as the account manager
+ * bus name #TP_QT4_ACCOUNT_MANAGER_BUS_NAME.
+ * \param objectPath The account object path.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return An AccountPtr object pointing to the newly created Account object.
+ */
+AccountPtr Account::create(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return AccountPtr(new Account(QDBusConnection::sessionBus(), busName, objectPath,
+ connectionFactory, channelFactory, contactFactory, Account::FeatureCore));
+}
+
+/**
+ * Create a new Account object using the given \a bus and the given factories.
+ *
+ * A warning is printed if the factories are not for \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param busName The account well-known bus name (sometimes called a "service
+ * name"). This is usually the same as the account manager
+ * bus name #TP_QT4_ACCOUNT_MANAGER_BUS_NAME.
+ * \param objectPath The account object path.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return An AccountPtr object pointing to the newly created Account object.
+ */
+AccountPtr Account::create(const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return AccountPtr(new Account(bus, busName, objectPath, connectionFactory, channelFactory,
+ contactFactory, Account::FeatureCore));
+}
+
+/**
+ * Construct a new Account object using the given \a bus and the given factories.
+ *
+ * A warning is printed if the factories are not for \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param busName The account well-known bus name (sometimes called a "service
+ * name"). This is usually the same as the account manager
+ * bus name #TP_QT4_ACCOUNT_MANAGER_BUS_NAME.
+ * \param objectPath The account object path.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \param coreFeature The core feature of the Account subclass. The corresponding introspectable
+ * should depend on Account::FeatureCore.
+ */
+Account::Account(const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const Feature &coreFeature)
+ : StatelessDBusProxy(bus, busName, objectPath, coreFeature),
+ OptionalInterfaceFactory<Account>(this),
+ mPriv(new Private(this, connectionFactory, channelFactory, contactFactory))
+{
+}
+
+/**
+ * Class destructor.
+ */
+Account::~Account()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection factory used by this account.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the account would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ConnectionFactory object.
+ */
+ConnectionFactoryConstPtr Account::connectionFactory() const
+{
+ return mPriv->connFactory;
+}
+
+/**
+ * Return the channel factory used by this account.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the account would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ChannelFactory object.
+ */
+ChannelFactoryConstPtr Account::channelFactory() const
+{
+ return mPriv->chanFactory;
+}
+
+/**
+ * Return the contact factory used by this account.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the account would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ContactFactory object.
+ */
+ContactFactoryConstPtr Account::contactFactory() const
+{
+ return mPriv->contactFactory;
+}
+
+/**
+ * Return whether this account is valid.
+ *
+ * If \c true, this account is considered by the account manager to be complete
+ * and usable. If \c false, user action is required to make it usable, and it will
+ * never attempt to connect. For instance, this might be caused by the absence
+ * or misconfiguration of a required parameter, in which case updateParameters()
+ * may be used to properly set the parameters values.
+ *
+ * Change notification is via the validityChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if valid, \c false otherwise.
+ * \sa validityChanged(), updateParameters()
+ */
+bool Account::isValidAccount() const
+{
+ return mPriv->valid;
+}
+
+/**
+ * Return whether this account is enabled.
+ *
+ * Change notification is via the stateChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if enabled, \c false otherwise.
+ * \sa stateChanged(), setEnabled()
+ */
+bool Account::isEnabled() const
+{
+ return mPriv->enabled;
+}
+
+/**
+ * Set whether this account should be enabled or disabled.
+ *
+ * This gives users the possibility to prevent an account from
+ * being used.
+ *
+ * Note that changing this property won't change the validity of the account.
+ *
+ * \param value Whether this account should be enabled or disabled.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa stateChanged(), isEnabled()
+ */
+PendingOperation *Account::setEnabled(bool value)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("Enabled"),
+ QDBusVariant(value)),
+ AccountPtr(this));
+}
+
+/**
+ * Return the connection manager name of this account.
+ *
+ * \return The connection manager name.
+ */
+QString Account::cmName() const
+{
+ return mPriv->cmName;
+}
+
+/**
+ * Return the protocol name of this account.
+ *
+ * \return The protocol name.
+ */
+QString Account::protocolName() const
+{
+ return mPriv->protocolName;
+}
+
+/**
+ * Return the service name of this account.
+ *
+ * Note that this method will fallback to protocolName() if service name
+ * is not known.
+ *
+ * Change notification is via the serviceNameChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The service name.
+ * \sa serviceNameChanged(), setServiceName(), protocolName()
+ */
+QString Account::serviceName() const
+{
+ if (mPriv->serviceName.isEmpty()) {
+ return mPriv->protocolName;
+ }
+ return mPriv->serviceName;
+}
+
+/**
+ * Set the service name of this account.
+ *
+ * Some protocols, like XMPP and SIP, are used by various different user-recognised brands,
+ * such as Google Talk. On accounts for such services, this method can be used
+ * to set the name describing the service, which must consist only of ASCII letters, numbers and
+ * hyphen/minus signs, and start with a letter.
+ * For the jabber protocol, one of the following service names should be used if possible:
+ *
+ * google-talk (for Google's IM service)
+ * facebook (for Facebook's IM service)
+ * lj-talk (for LiveJournal's IM service)
+ *
+ * The service name may also be set, if appropriate, when creating the account. See
+ * AccountManager::createAccount() for more details.
+ *
+ * Note that changing this property may also change the profile() used by this account,
+ * in which case profileChanged() will be emitted in addition to serviceNameChanged(), if
+ * Account::FeatureProfile is enabled.
+ *
+ * \param value The service name of this account.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa serviceNameChanged(), serviceName()
+ */
+PendingOperation *Account::setServiceName(const QString &value)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("Service"),
+ QDBusVariant(value)),
+ AccountPtr(this));
+}
+
+/**
+ * Return the profile used by this account.
+ *
+ * Profiles are intended to describe variants of the basic protocols supported by Telepathy
+ * connection managers.
+ * An example of this would be Google Talk vs Facebook chat vs Jabber/XMPP. Google Talk is a
+ * specific case of XMPP with well-known capabilities, quirks and settings, and Facebook chat is
+ * a subset of the standard XMPP offering.
+ *
+ * This method will return the profile for this account based on the service used by it.
+ *
+ * Note that if a profile for serviceName() is not available, a fake profile
+ * (Profile::isFake() is \c true) will be returned in case protocolInfo() is valid.
+ *
+ * The fake profile will contain the following info:
+ * - Profile::type() will return "IM"
+ * - Profile::provider() will return an empty string
+ * - Profile::serviceName() will return "cmName()-serviceName()"
+ * - Profile::name() and Profile::protocolName() will return protocolName()
+ * - Profile::iconName() will return "im-protocolName()"
+ * - Profile::cmName() will return cmName()
+ * - Profile::parameters() will return a list matching CM default parameters for protocol with name
+ * protocolName()
+ * - Profile::presences() will return an empty list and
+ * Profile::allowOtherPresences() will return \c true, meaning that CM
+ * presences should be used
+ * - Profile::unsupportedChannelClassSpecs() will return an empty list
+ *
+ * Change notification is via the profileChanged() signal.
+ *
+ * This method requires Account::FeatureProfile to be ready.
+ *
+ * \return A pointer to the Profile object.
+ * \sa profileChanged(), serviceName()
+ */
+ProfilePtr Account::profile() const
+{
+ if (!isReady(FeatureProfile)) {
+ warning() << "Account::profile() requires Account::FeatureProfile to be ready";
+ return ProfilePtr();
+ }
+
+ if (!mPriv->profile) {
+ mPriv->profile = Profile::createForServiceName(serviceName());
+ if (!mPriv->profile->isValid()) {
+ if (protocolInfo().isValid()) {
+ mPriv->profile = ProfilePtr(new Profile(
+ QString(QLatin1String("%1-%2")).arg(mPriv->cmName).arg(serviceName()),
+ mPriv->cmName,
+ mPriv->protocolName,
+ protocolInfo()));
+ } else {
+ warning() << "Cannot create profile as neither a .profile is installed for service" <<
+ serviceName() << "nor protocol info can be retrieved";
+ }
+ }
+ }
+ return mPriv->profile;
+}
+
+/**
+ * Return the display name of this account.
+ *
+ * Change notification is via the displayNameChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The display name.
+ * \sa displayNameChanged(), setDisplayName()
+ */
+QString Account::displayName() const
+{
+ return mPriv->displayName;
+}
+
+/**
+ * Set the display name of this account.
+ *
+ * The display name is the user-visible name of this account.
+ * This is usually chosen by the user at account creation time.
+ * See AccountManager::createAccount() for more details.
+ *
+ * \param value The display name of this account.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa displayNameChanged(), displayName()
+ */
+PendingOperation *Account::setDisplayName(const QString &value)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("DisplayName"),
+ QDBusVariant(value)),
+ AccountPtr(this));
+}
+
+/**
+ * Return the icon name of this account.
+ *
+ * If the account has no icon, and Account::FeatureProfile is enabled, the icon from the result of
+ * profile() will be used.
+ *
+ * If neither the account nor the profile has an icon, and Account::FeatureProtocolInfo is
+ * enabled, the icon from protocolInfo() will be used if set.
+ *
+ * As a last resort, "im-" + protocolName() will be returned.
+ *
+ * This matches the fallbacks recommended by the \telepathy_spec.
+ *
+ * Change notification is via the iconNameChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The icon name.
+ * \sa iconNameChanged(), setIconName()
+ */
+QString Account::iconName() const
+{
+ if (mPriv->iconName.isEmpty()) {
+ if (isReady(FeatureProfile)) {
+ ProfilePtr pr = profile();
+ if (pr && pr->isValid()) {
+ QString iconName = pr->iconName();
+ if (!iconName.isEmpty()) {
+ return iconName;
+ }
+ }
+ }
+
+ if (isReady(FeatureProtocolInfo) && protocolInfo().isValid()) {
+ return protocolInfo().iconName();
+ }
+
+ return QString(QLatin1String("im-%1")).arg(protocolName());
+ }
+
+ return mPriv->iconName;
+}
+
+/**
+ * Set the icon name of this account.
+ *
+ * \param value The icon name of this account.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa iconNameChanged(), iconName()
+ */
+PendingOperation *Account::setIconName(const QString &value)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("Icon"),
+ QDBusVariant(value)),
+ AccountPtr(this));
+}
+
+/**
+ * Return the nickname of this account.
+ *
+ * Change notification is via the nicknameChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The nickname.
+ * \sa nicknameChanged(), setNickname()
+ */
+QString Account::nickname() const
+{
+ return mPriv->nickname;
+}
+
+/**
+ * Set the nickname of this account as seen to other contacts.
+ *
+ * \param value The nickname of this account.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa nicknameChanged(), nickname()
+ */
+PendingOperation *Account::setNickname(const QString &value)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("Nickname"),
+ QDBusVariant(value)),
+ AccountPtr(this));
+}
+
+/**
+ * Return the avatar requirements (size limits, supported MIME types, etc)
+ * for avatars passed to setAvatar() on this account.
+ *
+ * For now this method will only return the avatar requirements found in protocolInfo() if
+ * Account::FeatureProtocolInfo is ready, otherwise an invalid AvatarSpec instance is returned.
+ *
+ * \return The requirements as an AvatarSpec object.
+ * \sa avatar(), setAvatar()
+ */
+AvatarSpec Account::avatarRequirements() const
+{
+ // TODO Once connection has support for avatar requirements use it if the connection is usable
+ ProtocolInfo pi = protocolInfo();
+ if (pi.isValid()) {
+ return pi.avatarRequirements();
+ }
+ return AvatarSpec();
+}
+
+/**
+ * Return the avatar of this account.
+ *
+ * Change notification is via the avatarChanged() signal.
+ *
+ * This method requires Account::FeatureAvatar to be ready.
+ *
+ * \return The avatar as an Avatar object.
+ * \sa avatarChanged(), setAvatar()
+ */
+const Avatar &Account::avatar() const
+{
+ if (!isReady(Features() << FeatureAvatar)) {
+ warning() << "Trying to retrieve avatar from account, but "
+ "avatar is not supported or was not requested. "
+ "Use becomeReady(FeatureAvatar)";
+ }
+
+ return mPriv->avatar;
+}
+
+/**
+ * Set avatar of this account as seen to other contacts.
+ *
+ * Note that \a avatar must match the requirements as returned by avatarRequirements().
+ *
+ * \param avatar The avatar of this account.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa avatarChanged(), avatar(), avatarRequirements()
+ */
+PendingOperation *Account::setAvatar(const Avatar &avatar)
+{
+ if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR))) {
+ return new PendingFailure(
+ QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Account does not support Avatar"),
+ AccountPtr(this));
+ }
+
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR),
+ QLatin1String("Avatar"),
+ QDBusVariant(QVariant::fromValue(avatar))),
+ AccountPtr(this));
+}
+
+/**
+ * Return the parameters of this account.
+ *
+ * The account parameters are represented as a map from connection manager parameter names
+ * to their values.
+ *
+ * Change notification is via the parametersChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The parameters as QVariantMap.
+ * \sa parametersChanged(), updateParameters()
+ */
+QVariantMap Account::parameters() const
+{
+ return mPriv->parameters;
+}
+
+/**
+ * Update this account parameters.
+ *
+ * On success, the PendingOperation returned by this method will produce a
+ * list of strings, which are the names of parameters whose changes will not
+ * take effect until the account is disconnected and reconnected (for instance
+ * by calling reconnect()).
+ *
+ * \param set Parameters to set.
+ * \param unset Parameters to unset.
+ * \return A PendingStringList which will emit PendingStringList::finished
+ * when the request has been made
+ * \sa parametersChanged(), parameters(), reconnect()
+ */
+PendingStringList *Account::updateParameters(const QVariantMap &set,
+ const QStringList &unset)
+{
+ return new PendingStringList(
+ baseInterface()->UpdateParameters(set, unset),
+ AccountPtr(this));
+}
+
+/**
+ * Return the protocol info of this account protocol.
+ *
+ * This method requires Account::FeatureProtocolInfo to be ready.
+ *
+ * \return The protocol info as a ProtocolInfo object.
+ */
+ProtocolInfo Account::protocolInfo() const
+{
+ if (!isReady(Features() << FeatureProtocolInfo)) {
+ warning() << "Trying to retrieve protocol info from account, but "
+ "protocol info is not supported or was not requested. "
+ "Use becomeReady(FeatureProtocolInfo)";
+ return ProtocolInfo();
+ }
+
+ return mPriv->cm->protocol(mPriv->protocolName);
+}
+
+/**
+ * Return the capabilities for this account.
+ *
+ * Note that this method will return the connection() capabilities if the
+ * account is online and ready. If the account is disconnected, it will fallback
+ * to return the subtraction of the protocolInfo() capabilities and the profile() unsupported
+ * capabilities.
+ *
+ * Change notification is via the capabilitiesChanged() signal.
+ *
+ * This method requires Account::FeatureCapabilities to be ready.
+ *
+ * \return The capabilities as a ConnectionCapabilities object.
+ * \sa capabilitiesChanged(), protocolInfo(), profile()
+ */
+ConnectionCapabilities Account::capabilities() const
+{
+ if (!isReady(FeatureCapabilities)) {
+ warning() << "Trying to retrieve capabilities from account, but "
+ "FeatureCapabilities was not requested. "
+ "Use becomeReady(FeatureCapabilities)";
+ return ConnectionCapabilities();
+ }
+
+ // if the connection is online and ready use its caps
+ if (mPriv->connection &&
+ mPriv->connection->status() == ConnectionStatusConnected) {
+ return mPriv->connection->capabilities();
+ }
+
+ // if we are here it means FeatureProtocolInfo and FeatureProfile are ready, as
+ // FeatureCapabilities depend on them, so let's use the subtraction of protocol info caps rccs
+ // and profile unsupported rccs.
+ //
+ // However, if we failed to introspect the CM (eg. this is a test), then let's not try to use
+ // the protocolInfo because it'll be NULL! Profile may also be NULL in case a .profile for
+ // serviceName() is not present and protocolInfo is NULL.
+ ProtocolInfo pi = protocolInfo();
+ if (!pi.isValid()) {
+ return ConnectionCapabilities();
+ }
+ ProfilePtr pr;
+ if (isReady(FeatureProfile)) {
+ pr = profile();
+ }
+ if (!pr || !pr->isValid()) {
+ return pi.capabilities();
+ }
+
+ RequestableChannelClassSpecList piClassSpecs = pi.capabilities().allClassSpecs();
+ RequestableChannelClassSpecList prUnsupportedClassSpecs = pr->unsupportedChannelClassSpecs();
+ RequestableChannelClassSpecList classSpecs;
+ bool unsupported;
+ foreach (const RequestableChannelClassSpec &piClassSpec, piClassSpecs) {
+ unsupported = false;
+ foreach (const RequestableChannelClassSpec &prUnsuportedClassSpec, prUnsupportedClassSpecs) {
+ // Here we check the following:
+ // - If the unsupported spec has no allowed property it means it does not support any
+ // class whose fixed properties match.
+ // E.g: Doesn't support any media calls, be it audio or video.
+ // - If the unsupported spec has allowed properties it means it does not support a
+ // specific class whose fixed properties and allowed properties should match.
+ // E.g: Doesn't support video calls but does support audio calls.
+ if (prUnsuportedClassSpec.allowedProperties().isEmpty()) {
+ if (piClassSpec.fixedProperties() == prUnsuportedClassSpec.fixedProperties()) {
+ unsupported = true;
+ break;
+ }
+ } else {
+ if (piClassSpec == prUnsuportedClassSpec) {
+ unsupported = true;
+ break;
+ }
+ }
+ }
+ if (!unsupported) {
+ classSpecs.append(piClassSpec);
+ }
+ }
+ mPriv->customCaps = ConnectionCapabilities(classSpecs);
+ return mPriv->customCaps;
+}
+
+/**
+ * Return whether this account should be put online automatically whenever
+ * possible.
+ *
+ * Change notification is via the connectsAutomaticallyPropertyChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if it should try to connect automatically, \c false
+ * otherwise.
+ * \sa connectsAutomaticallyPropertyChanged(), setConnectsAutomatically()
+ */
+bool Account::connectsAutomatically() const
+{
+ return mPriv->connectsAutomatically;
+}
+
+/**
+ * Set whether this account should be put online automatically whenever
+ * possible.
+ *
+ * \param value Value indicating if this account should be put online whenever
+ * possible.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa connectsAutomaticallyPropertyChanged(), connectsAutomatically()
+ */
+PendingOperation *Account::setConnectsAutomatically(bool value)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("ConnectAutomatically"),
+ QDBusVariant(value)),
+ AccountPtr(this));
+}
+
+/**
+ * Return whether this account has ever been put online successfully.
+ *
+ * This property cannot change from \c true to \c false, only from \c false to \c true.
+ * When the account successfully goes online for the first time, or when it
+ * is detected that this has already happened, the firstOnline() signal is
+ * emitted.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if ever been online, \c false otherwise.
+ */
+bool Account::hasBeenOnline() const
+{
+ return mPriv->hasBeenOnline;
+}
+
+/**
+ * Return the status of this account connection.
+ *
+ * Note that this method may return a different value from the one returned by Connection::status()
+ * on this account connection. This happens because this value will change whenever the connection
+ * status of this account connection changes and won't consider the Connection introspection
+ * process. The same rationale also applies to connectionStatusReason() and
+ * connectionErrorDetails().
+ *
+ * A valid use case for this is for account creation UIs that wish to display the accounts
+ * connection status and nothing else on the connections (e.g. retrieve the contact list).
+ *
+ * Change notification is via the connectionStatusChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The connection status as #ConnectionStatus.
+ * \sa connectionStatusChanged(), connectionStatusReason(), connectionError(),
+ * connectionErrorDetails()
+ */
+ConnectionStatus Account::connectionStatus() const
+{
+ return mPriv->connectionStatus;
+}
+
+/**
+ * Return the reason for this account connection status.
+ *
+ * This represents the reason for the last change to connectionStatus().
+ *
+ * Note that this method may return a different value from the one returned by
+ * Connection::statusReason() on this account connection.
+ * See connectionStatus() for more details.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The connection status reason as #ConnectionStatusReason.
+ * \sa connectionStatusChanged(), connectionStatus(), connectionError(), connectionErrorDetails()
+ */
+ConnectionStatusReason Account::connectionStatusReason() const
+{
+ return mPriv->connectionStatusReason;
+}
+
+/**
+ * Return the D-Bus error name for the last disconnection or connection failure,
+ * (in particular, #TP_QT4_ERROR_CANCELLED if it was disconnected by user
+ * request), or an empty string if the account is connected.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The D-Bus error name for the last disconnection or connection failure.
+ * \sa connectionErrorDetails(), connectionStatus(), connectionStatusReason(),
+ * connectionStatusChanged()
+ */
+QString Account::connectionError() const
+{
+ return mPriv->connectionError;
+}
+
+/**
+ * Return detailed information related to connectionError().
+ *
+ * Note that this method may return a different value from the one returned by
+ * Connection::errorDetails() on this account connection.
+ * See connectionStatus() for more details.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The connection error details as a Connection::ErrorDetails object.
+ * \sa connectionError(), connectionStatus(), connectionStatusReason(), connectionStatusChanged(),
+ * Connection::ErrorDetails.
+ */
+Connection::ErrorDetails Account::connectionErrorDetails() const
+{
+ return mPriv->connectionErrorDetails;
+}
+
+/**
+ * Return the object representing the connection of this account.
+ *
+ * Note that the Connection object returned by this method will have the
+ * features set in the connectionFactory() used by this account ready.
+ *
+ * Change notification is via the connectionChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return A pointer to the Connection object, or a null ConnectionPtr if
+ * there is no connection currently or if an error occurred.
+ * \sa connectionChanged()
+ */
+ConnectionPtr Account::connection() const
+{
+ return mPriv->connection;
+}
+
+/**
+ * Return whether this account connection is changing presence.
+ *
+ * Change notification is via the changingPresence() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if changing presence, \c false otherwise.
+ * \sa changingPresence(), currentPresenceChanged(), setRequestedPresence()
+ */
+bool Account::isChangingPresence() const
+{
+ return mPriv->changingPresence;
+}
+
+/**
+ * Return a list of presences allowed by a connection to this account.
+ *
+ * In particular, for the statuses reported here it can be assumed that setting them as the
+ * requested presence via setRequestedPresence() will eventually result in currentPresence()
+ * changing to exactly said presence. Other statuses are only guaranteed to be matched as closely as
+ * possible.
+ *
+ * The statuses can be also used for the automatic presence, as set by setAutomaticPresence(), with
+ * the exception of any status specifications for which Presence::type() is
+ * Tp::ConnectionPresenceTypeOffline for the Presence returned by PresenceSpec::presence().
+ *
+ * However, the optional parameter can be used to allow reporting also other possible presence
+ * statuses on this protocol besides the others that can be set on yourself. These are purely
+ * informatory, for e.g. adjusting an UI to allow all possible remote contact statuses to be
+ * displayed.
+ *
+ * An offline presence status is always included, because it's always valid to make an account
+ * offline by setting the requested presence to an offline status.
+ *
+ * Full functionality requires Account::FeatureProtocolInfo and Account::FeatureProfile to be ready
+ * as well as connection with Connection::FeatureSimplePresence enabled. If the connection is online
+ * and Connection::FeatureSimplePresence is enabled, it will return the connection allowed statuses,
+ * otherwise it will return a list os statuses based on profile() and protocolInfo() information
+ * if the corresponding features are enabled.
+ *
+ * If there's a mismatch between the presence status info provided in the .profile file and/or the
+ * .manager file and what an online Connection actually reports (for example, the said data files
+ * are missing or too old to include presence information), the returned value can change, in
+ * particular when connectionChanged() is emitted with a connection for which Connection::status()
+ * is Tp::ConnectionStatusConnected.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \param includeAllStatuses Whether the returned list will include all statuses or just the ones
+ * that can be settable using setRequestedPresence().
+ * \return The allowed statuses as a list of PresenceSpec objects.
+ */
+PresenceSpecList Account::allowedPresenceStatuses(bool includeAllStatuses) const
+{
+ QMap<QString, PresenceSpec> specMap;
+
+ // if the connection is online and ready use it
+ if (mPriv->connection &&
+ mPriv->connection->status() == ConnectionStatusConnected &&
+ mPriv->connection->actualFeatures().contains(Connection::FeatureSimplePresence)) {
+ SimpleStatusSpecMap connectionAllowedPresences =
+ mPriv->connection->lowlevel()->allowedPresenceStatuses();
+ SimpleStatusSpecMap::const_iterator i = connectionAllowedPresences.constBegin();
+ SimpleStatusSpecMap::const_iterator end = connectionAllowedPresences.constEnd();
+ for (; i != end; ++i) {
+ PresenceSpec presence = PresenceSpec(i.key(), i.value());
+ specMap.insert(i.key(), presence);
+ }
+ } else {
+ ProtocolInfo pi = protocolInfo();
+ if (pi.isValid()) {
+ // add all ProtocolInfo presences to the returned map
+ foreach (const PresenceSpec &piPresence, pi.allowedPresenceStatuses()) {
+ QString piStatus = piPresence.presence().status();
+ specMap.insert(piStatus, piPresence);
+ }
+ }
+
+ ProfilePtr pr;
+ if (isReady(FeatureProfile)) {
+ pr = profile();
+ }
+ if (pr && pr->isValid()) {
+ // add all Profile presences to the returned map
+ foreach (const Profile::Presence &prPresence, pr->presences()) {
+ QString prStatus = prPresence.id();
+ if (specMap.contains(prStatus)) {
+ // we already got the presence from ProtocolInfo, just update
+ // canHaveStatusMessage if needed
+ PresenceSpec presence = specMap.value(prStatus);
+ if (presence.canHaveStatusMessage() != prPresence.canHaveStatusMessage()) {
+ SimpleStatusSpec spec;
+ spec.type = presence.presence().type();
+ spec.maySetOnSelf = presence.maySetOnSelf();
+ spec.canHaveMessage = prPresence.canHaveStatusMessage();
+ specMap.insert(prStatus, PresenceSpec(prStatus, spec));
+ }
+ } else {
+ // presence not found in ProtocolInfo, adding it
+ specMap.insert(prStatus, presenceSpecForStatus(prStatus,
+ prPresence.canHaveStatusMessage()));
+ }
+ }
+
+ // now remove all presences that are not in the Profile, if it does
+ // not allow other presences, and the ones that are disabled
+ QMap<QString, PresenceSpec>::iterator i = specMap.begin();
+ QMap<QString, PresenceSpec>::iterator end = specMap.end();
+ while (i != end) {
+ PresenceSpec presence = i.value();
+ QString status = presence.presence().status();
+ bool hasPresence = pr->hasPresence(status);
+ Profile::Presence prPresence = pr->presence(status);
+ if ((!hasPresence && !pr->allowOtherPresences()) || (hasPresence && prPresence.isDisabled())) {
+ i = specMap.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ }
+ }
+
+ // filter out presences that may not be set on self if includeAllStatuses is false
+ if (!includeAllStatuses) {
+ QMap<QString, PresenceSpec>::iterator i = specMap.begin();
+ QMap<QString, PresenceSpec>::iterator end = specMap.end();
+ while (i != end) {
+ PresenceSpec presence = i.value();
+ if (!presence.maySetOnSelf()) {
+ i = specMap.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ }
+
+ if (!specMap.size()) {
+ // If we didn't discover any statuses, either the protocol doesn't really support presence,
+ // or we lack information (e.g. features not enabled or info not provided in the .manager or
+ // .profile files). "available" - just the fact that you're online in the first place, is at
+ // least a valid option for any protocol, so we'll include it as a fallback.
+
+ specMap.insert(QLatin1String("available"),
+ presenceSpecForStatus(QLatin1String("available"), false));
+ }
+
+ // We'll always include "offline". It is always valid to make an account offline via
+ // setRequestedPresence().
+ if (!specMap.contains(QLatin1String("offline"))) {
+ specMap.insert(QLatin1String("offline"),
+ presenceSpecForStatus(QLatin1String("offline"), false));
+ }
+
+ return specMap.values();
+}
+
+/**
+ * Return the maximum length for a presence status message.
+ *
+ * If a status message set using setRequestedPresence() (or setAutomaticPresence()) is longer than
+ * the maximum length allowed, the message will be truncated and
+ * currentPresenceChanged() will be emitted (if setting the presence worked)
+ * with the truncated message.
+ *
+ * Full functionality requires Connection with Connection::FeatureSimplePresence
+ * enabled. If the connection is online and Connection::FeatureSimplePresence is
+ * enabled, it will return the connection maximum status message length,
+ * otherwise it will return 0.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The maximum length, or 0 if there is no limit.
+ */
+uint Account::maxPresenceStatusMessageLength() const
+{
+ // if the connection is online and ready use it
+ if (mPriv->connection &&
+ mPriv->connection->status() == ConnectionStatusConnected &&
+ mPriv->connection->actualFeatures().contains(Connection::FeatureSimplePresence)) {
+ return mPriv->connection->lowlevel()->maxPresenceStatusMessageLength();
+ }
+
+ return 0;
+}
+
+/**
+ * Return the presence status that this account will have set on it by the
+ * account manager if it brings it online automatically.
+ *
+ * Change notification is via the automaticPresenceChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The automatic presence as a Presence object.
+ * \sa automaticPresenceChanged(), setAutomaticPresence()
+ */
+Presence Account::automaticPresence() const
+{
+ return mPriv->automaticPresence;
+}
+
+/**
+ * Set the presence status that this account should have if it is brought
+ * online automatically by the account manager.
+ *
+ * Note that changing this property won't actually change the account's status
+ * until the next time it is (re)connected for some reason.
+ *
+ * The value of this property must be one that would be acceptable for setRequestedPresence(),
+ * as returned by allowedPresenceStatuses(), with the additional restriction that the offline
+ * presence cannot be used.
+ *
+ * \param presence The presence to set when this account is brought
+ * online automatically by the account manager.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa automaticPresenceChanged(), automaticPresence(), setRequestedPresence()
+ */
+PendingOperation *Account::setAutomaticPresence(const Presence &presence)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("AutomaticPresence"),
+ QDBusVariant(QVariant::fromValue(presence.barePresence()))),
+ AccountPtr(this));
+}
+
+/**
+ * Return the actual presence of this account.
+ *
+ * Change notification is via the currentPresenceChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The current presence as a Presence object.
+ * \sa currentPresenceChanged(), setRequestedPresence(), requestedPresence(), automaticPresence()
+ */
+Presence Account::currentPresence() const
+{
+ return mPriv->currentPresence;
+}
+
+/**
+ * Return the requested presence of this account.
+ *
+ * Change notification is via the requestedPresenceChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The requested presence as a Presence object.
+ * \sa requestedPresenceChanged(), setRequestedPresence(), currentPresence(), automaticPresence()
+ */
+Presence Account::requestedPresence() const
+{
+ return mPriv->requestedPresence;
+}
+
+/**
+ * Set the requested presence of this account.
+ *
+ * When the requested presence is changed, the account manager will attempt to
+ * manipulate the connection to make currentPresence() match requestedPresence()
+ * as closely as possible.
+ *
+ * \param presence The requested presence.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa requestedPresenceChanged(), currentPresence(), automaticPresence(), setAutomaticPresence()
+ */
+PendingOperation *Account::setRequestedPresence(const Presence &presence)
+{
+ return new PendingVoid(
+ mPriv->properties->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("RequestedPresence"),
+ QDBusVariant(QVariant::fromValue(presence.barePresence()))),
+ AccountPtr(this));
+}
+
+/**
+ * Return whether this account is online.
+ *
+ * Change notification is via the onlinenessChanged() signal.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if online, otherwise \c false.
+ * \sa onlinenessChanged()
+ */
+bool Account::isOnline() const
+{
+ return mPriv->currentPresence.type() != ConnectionPresenceTypeOffline;
+}
+
+/**
+ * Return the unique identifier of this account.
+ *
+ * \return The unique identifier.
+ */
+QString Account::uniqueIdentifier() const
+{
+ QString path = objectPath();
+ return path.right(path.length() -
+ strlen("/org/freedesktop/Telepathy/Account/"));
+}
+
+/**
+ * Return the normalized user ID of the local user of this account.
+ *
+ * It is unspecified whether this user ID is globally unique.
+ *
+ * As currently implemented, IRC user IDs are only unique within the same
+ * IRCnet. On some saner protocols, the user ID includes a DNS name which
+ * provides global uniqueness.
+ *
+ * If this value is not known yet (which will always be the case for accounts
+ * that have never been online), it will be an empty string.
+ *
+ * It is possible that this value will change if the connection manager's
+ * normalization algorithm changes.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return The normalized user ID of the local user.
+ * \sa normalizedNameChanged()
+ */
+QString Account::normalizedName() const
+{
+ return mPriv->normalizedName;
+}
+
+/**
+ * If this account is currently connected, disconnect and reconnect it. If it
+ * is currently trying to connect, cancel the attempt to connect and start
+ * another. If it is currently disconnected, do nothing.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ */
+PendingOperation *Account::reconnect()
+{
+ return new PendingVoid(baseInterface()->Reconnect(), AccountPtr(this));
+}
+
+/**
+ * Delete this account.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa removed()
+ */
+PendingOperation *Account::remove()
+{
+ return new PendingVoid(baseInterface()->Remove(), AccountPtr(this));
+}
+
+/**
+ * Return whether passing hints on channel requests on this account is known to be supported.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool Account::supportsRequestHints() const
+{
+ return mPriv->dispatcherContext->supportsHints;
+}
+
+/**
+ * Return whether the ChannelRequest::succeeded(const Tp::ChannelPtr &channel) signal is expected to
+ * be emitted with a non-NULL channel parameter for requests made using this account.
+ *
+ * This can be used as a run-time check for the Channel Dispatcher implementation being new enough.
+ * In particular, similarly old Channel Dispatchers don't support request hints either, so the
+ * return value for this function and Account::supportsRequestHints() will bet he same.
+ *
+ * This method requires Account::FeatureCore to be ready.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool Account::requestsSucceedWithChannel() const
+{
+ return supportsRequestHints();
+}
+
+/**
+ * Same as \c ensureTextChat(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureTextChat(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureTextChat(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a text channel with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * \param contactIdentifier The identifier of the contact to chat with.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureTextChat(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = textChatRequest(contactIdentifier);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureTextChat(contact, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureTextChat(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureTextChat(contact, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a text channel with the given
+ * contact \a contact exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * \param contact The contact to chat with.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureTextChat(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = textChatRequest(contact);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureTextChatroom(roomName, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureTextChatroom(
+ const QString &roomName,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureTextChatroom(roomName, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a text chat room with the given
+ * room name \a roomName exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * \param roomName The name of the chat room.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureTextChatroom(
+ const QString &roomName,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = textChatroomRequest(roomName);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureStreamedMediaCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureStreamedMediaCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureStreamedMediaCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a media channel with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureStreamedMediaCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamedMediaCallRequest(contactIdentifier);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureStreamedMediaCall(contact, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureStreamedMediaCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureStreamedMediaCall(contact, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a media channel with the given
+ * contact \a contact exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * \param contact The contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureStreamedMediaCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamedMediaCallRequest(contact);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureStreamedMediaAudioCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureStreamedMediaAudioCall(
+ const QString &contactIdentifier,
+ QDateTime userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureStreamedMediaAudioCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that an audio call with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureStreamedMediaAudioCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamedMediaAudioCallRequest(contactIdentifier);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureStreamedMediaAudioCall(contact, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureStreamedMediaAudioCall(
+ const ContactPtr &contact,
+ QDateTime userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureStreamedMediaAudioCall(contact, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that an audio call with the given
+ * contact \a contact exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contact The contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureStreamedMediaAudioCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamedMediaAudioCallRequest(contact);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureStreamedMediaVideoCall(contactIdentifier, withAudio, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureStreamedMediaVideoCall(
+ const QString &contactIdentifier,
+ bool withAudio,
+ QDateTime userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureStreamedMediaVideoCall(contactIdentifier, withAudio, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a video call with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param withAudio true if both audio and video are required, false for a
+ * video-only call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureStreamedMediaVideoCall(
+ const QString &contactIdentifier,
+ bool withAudio,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamedMediaVideoCallRequest(contactIdentifier, withAudio);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c ensureStreamedMediaVideoCall(contact, withAudio, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureStreamedMediaVideoCall(
+ const ContactPtr &contact,
+ bool withAudio,
+ QDateTime userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureStreamedMediaVideoCall(contact, withAudio, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a video call with the given
+ * contact \a contact exists, creating it if necessary.
+ *
+ * See ensureChannel() for more details.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contact The contact to call.
+ * \param withAudio true if both audio and video are required, false for a
+ * video-only call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::ensureStreamedMediaVideoCall(
+ const ContactPtr &contact,
+ bool withAudio,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamedMediaVideoCallRequest(contact, withAudio);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Same as \c createFileTransfer(contactIdentifier, properties, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createFileTransfer(
+ const QString &contactIdentifier,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createFileTransfer(contactIdentifier, properties, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a file transfer channel with the given
+ * contact \a contact.
+ *
+ * \param contactIdentifier The identifier of the contact to send a file.
+ * \param properties The desired properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createFileTransfer(
+ const QString &contactIdentifier,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = fileTransferRequest(contactIdentifier, properties);
+
+ if (request.isEmpty()) {
+ return new PendingChannelRequest(AccountPtr(this), TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Cannot create a file transfer with invalid parameters"));
+ }
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createFileTransfer(contact, properties, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createFileTransfer(
+ const ContactPtr &contact,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createFileTransfer(contact, properties, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a file transfer channel with the given
+ * contact \a contact.
+ *
+ * \param contact The contact to send a file.
+ * \param properties The desired properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createFileTransfer(
+ const ContactPtr &contact,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = fileTransferRequest(contact, properties);
+
+ if (request.isEmpty()) {
+ return new PendingChannelRequest(AccountPtr(this), TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Cannot create a file transfer with invalid parameters"));
+ }
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Start a request to create a stream tube channel with the given
+ * contact identifier \a contactIdentifier.
+ *
+ * \param contactIdentifier The identifier of the contact to open a stream tube with.
+ * \param service The stream tube service.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createStreamTube(
+ const QString &contactIdentifier,
+ const QString &service,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamTubeRequest(contactIdentifier, service);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Start a request to create a stream tube channel with the given
+ * contact \a contact.
+ *
+ * \param contact The contact to open a stream tube with.
+ * \param service The stream tube service.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createStreamTube(
+ const ContactPtr &contact,
+ const QString &service,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = streamTubeRequest(contact, service);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createConferenceStreamedMediaCall(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createConferenceStreamedMediaCall(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a conference media call with the given
+ * channels \a channels.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContactsIdentifiers A list of additional contacts
+ * identifiers to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = conferenceStreamedMediaCallRequest(channels,
+ initialInviteeContactsIdentifiers);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createConferenceStreamedMediaCall(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createConferenceStreamedMediaCall(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a conference media call with the given
+ * channels \a channels.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContacts A list of additional contacts
+ * to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = conferenceStreamedMediaCallRequest(channels, initialInviteeContacts);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createConferenceTextChat(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createConferenceTextChat(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a conference text chat with the given
+ * channels \a channels.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContactsIdentifiers A list of additional contacts
+ * identifiers to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContactsIdentifiers);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createConferenceTextChat(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createConferenceTextChat(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a conference text chat with the given
+ * channels \a channels.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContacts A list of additional contacts
+ * to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContacts);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createConferenceTextChatroom(roomName, channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createConferenceTextChatRoom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createConferenceTextChatroom(roomName, channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a conference text chat room with the given
+ * channels \a channels and room name \a roomName.
+ *
+ * \param roomName The room name.
+ * \param channels The conference channels.
+ * \param initialInviteeContactsIdentifiers A list of additional contacts
+ * identifiers to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = conferenceTextChatroomRequest(roomName, channels,
+ initialInviteeContactsIdentifiers);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createConferenceTextChatroom(roomName, channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createConferenceTextChatRoom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createConferenceTextChatroom(roomName, channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a conference text chat room with the given
+ * channels \a channels and room name \a roomName.
+ *
+ * \param roomName The room name.
+ * \param channels The conference channels.
+ * \param initialInviteeContacts A list of additional contacts
+ * to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa ensureChannel(), createChannel()
+ */
+PendingChannelRequest *Account::createConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = conferenceTextChatroomRequest(roomName, channels,
+ initialInviteeContacts);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c createContactSearch(server, limit, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createContactSearch(
+ const QString &server,
+ uint limit,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createContactSearch(server, limit, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a contact search channel with the given
+ * server \a server and limit \a limit.
+ *
+ * \param server For protocols which support searching for contacts on multiple servers with
+ * different DNS names (like XMPP), the DNS name of the server to be searched,
+ * e.g. "characters.shakespeare.lit". Otherwise, an empty string.
+ * \param limit The desired maximum number of results that should be returned by a doing a search.
+ * If the protocol does not support specifying a limit for the number of results
+ * returned at a time, this will be ignored.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa createChannel()
+ */
+PendingChannelRequest *Account::createContactSearch(
+ const QString &server,
+ uint limit,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ QVariantMap request = contactSearchRequest(capabilities(), server, limit);
+
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Start a request to ensure that a text channel with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contactIdentifier The identifier of the contact to chat with.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleTextChat(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = textChatRequest(contactIdentifier);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that a text channel with the given
+ * contact \a contact exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contact The contact to chat with.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleTextChat(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = textChatRequest(contact);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that a text chat room with the given
+ * room name \a roomName exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param roomName The name of the chat room.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleTextChatroom(
+ const QString &roomName,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = textChatroomRequest(roomName);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that a media channel with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleStreamedMediaCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamedMediaCallRequest(contactIdentifier);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that a media channel with the given
+ * contact \a contact exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contact The contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleStreamedMediaCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamedMediaCallRequest(contact);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that an audio call with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleStreamedMediaAudioCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamedMediaAudioCallRequest(contactIdentifier);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that an audio call with the given
+ * contact \a contact exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contact The contact to call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleStreamedMediaAudioCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamedMediaAudioCallRequest(contact);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that a video call with the given
+ * contact \a contactIdentifier exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contactIdentifier The identifier of the contact to call.
+ * \param withAudio true if both audio and video are required, false for a
+ * video-only call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleStreamedMediaVideoCall(
+ const QString &contactIdentifier,
+ bool withAudio,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamedMediaVideoCallRequest(contactIdentifier, withAudio);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to ensure that a video call with the given
+ * contact \a contact exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * This will only work on relatively modern connection managers,
+ * like telepathy-gabble 0.9.0 or later.
+ *
+ * \param contact The contact to call.
+ * \param withAudio true if both audio and video are required, false for a
+ * video-only call.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleStreamedMediaVideoCall(
+ const ContactPtr &contact,
+ bool withAudio,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamedMediaVideoCallRequest(contact, withAudio);
+
+ return ensureAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a file transfer channel with the given
+ * contact \a contactIdentifier.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contactIdentifier The identifier of the contact to send a file.
+ * \param properties The desired properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleFileTransfer(
+ const QString &contactIdentifier,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = fileTransferRequest(contactIdentifier, properties);
+
+ if (request.isEmpty()) {
+ return new PendingChannel(TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Cannot create a file transfer with invalid parameters"));
+ }
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a file transfer channel with the given
+ * contact \a contact.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contact The contact to send a file.
+ * \param properties The desired properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleFileTransfer(
+ const ContactPtr &contact,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = fileTransferRequest(contact, properties);
+
+ if (request.isEmpty()) {
+ return new PendingChannel(TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Cannot create a file transfer with invalid parameters"));
+ }
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a stream tube channel with the given
+ * contact identifier \a contactIdentifier.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contactIdentifier The identifier of the contact to open a stream tube with.
+ * \param service The stream tube service.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleStreamTube(
+ const QString &contactIdentifier,
+ const QString &service,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamTubeRequest(contactIdentifier, service);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a stream tube channel with the given
+ * contact \a contact.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param contact The contact to open a stream tube with.
+ * \param service The stream tube service.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleStreamTube(
+ const ContactPtr &contact,
+ const QString &service,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = streamTubeRequest(contact, service);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a conference text chat with the given
+ * channels \a channels.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContactsIdentifiers A list of additional contacts
+ * identifiers to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContactsIdentifiers);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a conference text chat with the given
+ * channels \a channels.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContacts A list of additional contacts
+ * to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContacts);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a conference text chat room with the given
+ * channels \a channels and room name \a roomName.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param roomName The room name.
+ * \param channels The conference channels.
+ * \param initialInviteeContactsIdentifiers A list of additional contacts
+ * identifiers to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = conferenceTextChatroomRequest(roomName, channels,
+ initialInviteeContactsIdentifiers);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a conference text chat room with the given
+ * channels \a channels and room name \a roomName.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param roomName The room name.
+ * \param channels The conference channels.
+ * \param initialInviteeContacts A list of additional contacts
+ * to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = conferenceTextChatroomRequest(roomName, channels,
+ initialInviteeContacts);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a conference media call with the given
+ * channels \a channels.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContactsIdentifiers A list of additional contacts
+ * identifiers to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = conferenceStreamedMediaCallRequest(channels,
+ initialInviteeContactsIdentifiers);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a conference media call with the given
+ * channels \a channels.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param channels The conference channels.
+ * \param initialInviteeContacts A list of additional contacts
+ * to be invited to this
+ * conference when it is created.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = conferenceStreamedMediaCallRequest(channels, initialInviteeContacts);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Start a request to create a contact search channel with the given
+ * server \a server and limit \a limit.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * \param server For protocols which support searching for contacts on multiple servers with
+ * different DNS names (like XMPP), the DNS name of the server to be searched,
+ * e.g. "characters.shakespeare.lit". Otherwise, an empty string.
+ * If the protocol does not support specifying a search server, this will be ignored.
+ * \param limit The desired maximum number of results that should be returned by a doing a search.
+ * If the protocol does not support specifying a limit for the number of results
+ * returned at a time, this will be ignored.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel(), createAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleContactSearch(
+ const QString &server,
+ uint limit,
+ const QDateTime &userActionTime)
+{
+ QVariantMap request = contactSearchRequest(capabilities(), server, limit);
+
+ return createAndHandleChannel(request, userActionTime);
+}
+
+/**
+ * Same as \c createChannel(request, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::createChannel(
+ const QVariantMap &request,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return createChannel(request, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to create a channel.
+ * This initially just creates a PendingChannelRequest object,
+ * which can be used to track the success or failure of the request,
+ * or to cancel it.
+ *
+ * Helper methods for text chat, text chat room, media call and conference are
+ * provided and should be used if appropriate.
+ *
+ * \param request A dictionary containing desirable properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa createChannel()
+ */
+PendingChannelRequest *Account::createChannel(
+ const QVariantMap &request,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, true, hints);
+}
+
+/**
+ * Same as \c ensureChannel(request, userActionTime, preferredHandler, ChannelRequestHints())
+ */
+PendingChannelRequest *Account::ensureChannel(
+ const QVariantMap &request,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler)
+{
+ return ensureChannel(request, userActionTime, preferredHandler, ChannelRequestHints());
+}
+
+/**
+ * Start a request to ensure that a channel exists, creating it if necessary.
+ * This initially just creates a PendingChannelRequest object,
+ * which can be used to track the success or failure of the request,
+ * or to cancel it.
+ *
+ * Helper methods for text chat, text chat room, media call and conference are
+ * provided and should be used if appropriate.
+ *
+ * \param request A dictionary containing desirable properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param hints Arbitrary metadata which will be relayed to the handler if supported,
+ * as indicated by supportsRequestHints().
+ * \return A PendingChannelRequest which will emit PendingChannelRequest::finished
+ * when the request has been made.
+ * \sa createChannel()
+ */
+PendingChannelRequest *Account::ensureChannel(
+ const QVariantMap &request,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints)
+{
+ return new PendingChannelRequest(AccountPtr(this), request, userActionTime,
+ preferredHandler, false, hints);
+}
+
+/**
+ * Start a request to create channel.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * Helper methods for text chat, text chat room, media call and conference are
+ * provided and should be used if appropriate.
+ *
+ * The caller is responsible for closing the channel with
+ * Channel::requestClose() or Channel::requestLeave() when it has finished handling it.
+ *
+ * A possible error returned by this method is #TP_QT4_ERROR_NOT_AVAILABLE, in case a conflicting
+ * channel that matches \a request already exists.
+ *
+ * \param request A dictionary containing desirable properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa ensureAndHandleChannel()
+ */
+PendingChannel *Account::createAndHandleChannel(
+ const QVariantMap &request,
+ const QDateTime &userActionTime)
+{
+ return new PendingChannel(AccountPtr(this), request, userActionTime, true);
+}
+
+/**
+ * Start a request to ensure that a channel exists, creating it if necessary.
+ * This initially just creates a PendingChannel object,
+ * which can be used to track the success or failure of the request.
+ *
+ * Helper methods for text chat, text chat room, media call and conference are
+ * provided and should be used if appropriate.
+ *
+ * The caller is responsible for closing the channel with
+ * Channel::requestClose() or Channel::requestLeave() when it has finished handling it.
+ *
+ * A possible error returned by this method is #TP_QT4_ERROR_NOT_YOURS, in case somebody else is
+ * already handling a channel that matches \a request.
+ *
+ * \param request A dictionary containing desirable properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * successfully, when the Channel is available for handling using
+ * PendingChannel::channel(), or with an error if one has been encountered.
+ * \sa createAndHandleChannel()
+ */
+PendingChannel *Account::ensureAndHandleChannel(
+ const QVariantMap &request,
+ const QDateTime &userActionTime)
+{
+ return new PendingChannel(AccountPtr(this), request, userActionTime, false);
+}
+
+/**
+ * Return the Client::AccountInterface interface proxy object for this account.
+ * This method is protected since the convenience methods provided by this
+ * class should generally be used instead of calling D-Bus methods
+ * directly.
+ *
+ * \return A pointer to the existing Client::AccountInterface object for this
+ * Account object.
+ */
+Client::AccountInterface *Account::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+/**
+ * Return the Client::ChannelDispatcherInterface interface proxy object to use for requesting
+ * channels on this account.
+ *
+ * This method is protected since the convenience methods provided by this
+ * class should generally be used instead of calling D-Bus methods
+ * directly.
+ *
+ * \return A pointer to the existing Client::ChannelDispatcherInterface object for this
+ * Account object.
+ */
+Client::ChannelDispatcherInterface *Account::dispatcherInterface() const
+{
+ return mPriv->dispatcherContext->iface;
+}
+
+/**** Private ****/
+void Account::Private::init()
+{
+ if (!parent->isValid()) {
+ return;
+ }
+
+ parent->connect(baseInterface,
+ SIGNAL(Removed()),
+ SLOT(onRemoved()));
+ parent->connect(baseInterface,
+ SIGNAL(AccountPropertyChanged(QVariantMap)),
+ SLOT(onPropertyChanged(QVariantMap)));
+}
+
+void Account::Private::introspectMain(Account::Private *self)
+{
+ if (self->dispatcherContext->introspected) {
+ self->parent->onDispatcherIntrospected(0);
+ return;
+ }
+
+ if (!self->dispatcherContext->introspectOp) {
+ debug() << "Discovering if the Channel Dispatcher supports request hints";
+ self->dispatcherContext->introspectOp =
+ self->dispatcherContext->iface->requestPropertySupportsRequestHints();
+ }
+
+ connect(self->dispatcherContext->introspectOp.data(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ self->parent,
+ SLOT(onDispatcherIntrospected(Tp::PendingOperation*)));
+}
+
+void Account::Private::introspectAvatar(Account::Private *self)
+{
+ debug() << "Calling GetAvatar(Account)";
+ // we already checked if avatar interface exists, so bypass avatar interface
+ // checking
+ Client::AccountInterfaceAvatarInterface *iface =
+ self->parent->interface<Client::AccountInterfaceAvatarInterface>();
+
+ // If we are here it means the user cares about avatar, so
+ // connect to avatar changed signal, so we update the avatar
+ // when it changes.
+ self->parent->connect(iface,
+ SIGNAL(AvatarChanged()),
+ SLOT(onAvatarChanged()));
+
+ self->retrieveAvatar();
+}
+
+void Account::Private::introspectProtocolInfo(Account::Private *self)
+{
+ Q_ASSERT(!self->cm);
+
+ self->cm = ConnectionManager::create(
+ self->parent->dbusConnection(), self->cmName,
+ self->connFactory, self->chanFactory, self->contactFactory);
+ self->parent->connect(self->cm->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onConnectionManagerReady(Tp::PendingOperation*)));
+}
+
+void Account::Private::introspectCapabilities(Account::Private *self)
+{
+ if (!self->connection) {
+ // there is no connection, just make capabilities ready
+ self->readinessHelper->setIntrospectCompleted(FeatureCapabilities, true);
+ return;
+ }
+
+ self->parent->connect(self->connection->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onConnectionReady(Tp::PendingOperation*)));
+}
+
+void Account::Private::updateProperties(const QVariantMap &props)
+{
+ debug() << "Account::updateProperties: changed:";
+
+ if (props.contains(QLatin1String("Interfaces"))) {
+ parent->setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")]));
+ debug() << " Interfaces:" << parent->interfaces();
+ }
+
+ QString oldIconName = parent->iconName();
+ bool serviceNameChanged = false;
+ bool profileChanged = false;
+ if (props.contains(QLatin1String("Service")) &&
+ serviceName != qdbus_cast<QString>(props[QLatin1String("Service")])) {
+ serviceNameChanged = true;
+ serviceName = qdbus_cast<QString>(props[QLatin1String("Service")]);
+ debug() << " Service Name:" << parent->serviceName();
+ /* use parent->serviceName() here as if the service name is empty we are going to use the
+ * protocol name */
+ emit parent->serviceNameChanged(parent->serviceName());
+ parent->notify("serviceName");
+
+ /* if we had a profile and the service changed, it means the profile also changed */
+ if (parent->isReady(Account::FeatureProfile)) {
+ /* service name changed, let's recreate profile */
+ profileChanged = true;
+ profile.reset();
+ emit parent->profileChanged(parent->profile());
+ parent->notify("profile");
+ }
+ }
+
+ if (props.contains(QLatin1String("DisplayName")) &&
+ displayName != qdbus_cast<QString>(props[QLatin1String("DisplayName")])) {
+ displayName = qdbus_cast<QString>(props[QLatin1String("DisplayName")]);
+ debug() << " Display Name:" << displayName;
+ emit parent->displayNameChanged(displayName);
+ parent->notify("displayName");
+ }
+
+ if ((props.contains(QLatin1String("Icon")) &&
+ oldIconName != qdbus_cast<QString>(props[QLatin1String("Icon")])) ||
+ serviceNameChanged) {
+
+ if (props.contains(QLatin1String("Icon"))) {
+ iconName = qdbus_cast<QString>(props[QLatin1String("Icon")]);
+ }
+
+ QString newIconName = parent->iconName();
+ if (oldIconName != newIconName) {
+ debug() << " Icon:" << newIconName;
+ emit parent->iconNameChanged(newIconName);
+ parent->notify("iconName");
+ }
+ }
+
+ if (props.contains(QLatin1String("Nickname")) &&
+ nickname != qdbus_cast<QString>(props[QLatin1String("Nickname")])) {
+ nickname = qdbus_cast<QString>(props[QLatin1String("Nickname")]);
+ debug() << " Nickname:" << nickname;
+ emit parent->nicknameChanged(nickname);
+ parent->notify("nickname");
+ }
+
+ if (props.contains(QLatin1String("NormalizedName")) &&
+ normalizedName != qdbus_cast<QString>(props[QLatin1String("NormalizedName")])) {
+ normalizedName = qdbus_cast<QString>(props[QLatin1String("NormalizedName")]);
+ debug() << " Normalized Name:" << normalizedName;
+ emit parent->normalizedNameChanged(normalizedName);
+ parent->notify("normalizedName");
+ }
+
+ if (props.contains(QLatin1String("Valid")) &&
+ valid != qdbus_cast<bool>(props[QLatin1String("Valid")])) {
+ valid = qdbus_cast<bool>(props[QLatin1String("Valid")]);
+ debug() << " Valid:" << (valid ? "true" : "false");
+ emit parent->validityChanged(valid);
+ parent->notify("valid");
+ }
+
+ if (props.contains(QLatin1String("Enabled")) &&
+ enabled != qdbus_cast<bool>(props[QLatin1String("Enabled")])) {
+ enabled = qdbus_cast<bool>(props[QLatin1String("Enabled")]);
+ debug() << " Enabled:" << (enabled ? "true" : "false");
+ emit parent->stateChanged(enabled);
+ parent->notify("enabled");
+ }
+
+ if (props.contains(QLatin1String("ConnectAutomatically")) &&
+ connectsAutomatically !=
+ qdbus_cast<bool>(props[QLatin1String("ConnectAutomatically")])) {
+ connectsAutomatically =
+ qdbus_cast<bool>(props[QLatin1String("ConnectAutomatically")]);
+ debug() << " Connects Automatically:" << (connectsAutomatically ? "true" : "false");
+ emit parent->connectsAutomaticallyPropertyChanged(connectsAutomatically);
+ parent->notify("connectsAutomatically");
+ }
+
+ if (props.contains(QLatin1String("HasBeenOnline")) &&
+ !hasBeenOnline &&
+ qdbus_cast<bool>(props[QLatin1String("HasBeenOnline")])) {
+ hasBeenOnline = true;
+ debug() << " HasBeenOnline changed to true";
+ // don't emit firstOnline unless we're already ready, that would be
+ // misleading - we'd emit it just before any already-used account
+ // became ready
+ if (parent->isReady(Account::FeatureCore)) {
+ emit parent->firstOnline();
+ }
+ parent->notify("hasBeenOnline");
+ }
+
+ if (props.contains(QLatin1String("Parameters")) &&
+ parameters != qdbus_cast<QVariantMap>(props[QLatin1String("Parameters")])) {
+ parameters = qdbus_cast<QVariantMap>(props[QLatin1String("Parameters")]);
+ emit parent->parametersChanged(parameters);
+ parent->notify("parameters");
+ }
+
+ if (props.contains(QLatin1String("AutomaticPresence")) &&
+ automaticPresence.barePresence() != qdbus_cast<SimplePresence>(
+ props[QLatin1String("AutomaticPresence")])) {
+ automaticPresence = Presence(qdbus_cast<SimplePresence>(
+ props[QLatin1String("AutomaticPresence")]));
+ debug() << " Automatic Presence:" << automaticPresence.type() <<
+ "-" << automaticPresence.status();
+ emit parent->automaticPresenceChanged(automaticPresence);
+ parent->notify("automaticPresence");
+ }
+
+ if (props.contains(QLatin1String("CurrentPresence")) &&
+ currentPresence.barePresence() != qdbus_cast<SimplePresence>(
+ props[QLatin1String("CurrentPresence")])) {
+ currentPresence = Presence(qdbus_cast<SimplePresence>(
+ props[QLatin1String("CurrentPresence")]));
+ debug() << " Current Presence:" << currentPresence.type() <<
+ "-" << currentPresence.status();
+ emit parent->currentPresenceChanged(currentPresence);
+ parent->notify("currentPresence");
+ emit parent->onlinenessChanged(parent->isOnline());
+ parent->notify("online");
+ }
+
+ if (props.contains(QLatin1String("RequestedPresence")) &&
+ requestedPresence.barePresence() != qdbus_cast<SimplePresence>(
+ props[QLatin1String("RequestedPresence")])) {
+ requestedPresence = Presence(qdbus_cast<SimplePresence>(
+ props[QLatin1String("RequestedPresence")]));
+ debug() << " Requested Presence:" << requestedPresence.type() <<
+ "-" << requestedPresence.status();
+ emit parent->requestedPresenceChanged(requestedPresence);
+ parent->notify("requestedPresence");
+ }
+
+ if (props.contains(QLatin1String("ChangingPresence")) &&
+ changingPresence != qdbus_cast<bool>(
+ props[QLatin1String("ChangingPresence")])) {
+ changingPresence = qdbus_cast<bool>(
+ props[QLatin1String("ChangingPresence")]);
+ debug() << " Changing Presence:" << changingPresence;
+ emit parent->changingPresence(changingPresence);
+ parent->notify("changingPresence");
+ }
+
+ if (props.contains(QLatin1String("Connection"))) {
+ QString path = qdbus_cast<QDBusObjectPath>(props[QLatin1String("Connection")]).path();
+ if (path.isEmpty()) {
+ debug() << " The map contains \"Connection\" but it's empty as a QDBusObjectPath!";
+ debug() << " Trying QString (known bug in some MC/dbus-glib versions)";
+ path = qdbus_cast<QString>(props[QLatin1String("Connection")]);
+ }
+
+ debug() << " Connection Object Path:" << path;
+ if (path == QLatin1String("/")) {
+ path = QString();
+ }
+
+ connObjPathQueue.enqueue(path);
+
+ if (connObjPathQueue.size() == 1) {
+ processConnQueue();
+ }
+
+ // onConnectionBuilt for a previous path will make sure the path we enqueued is processed if
+ // the queue wasn't empty (so is now size() > 1)
+ }
+
+ bool connectionStatusChanged = false;
+ if (props.contains(QLatin1String("ConnectionStatus")) ||
+ props.contains(QLatin1String("ConnectionStatusReason")) ||
+ props.contains(QLatin1String("ConnectionError")) ||
+ props.contains(QLatin1String("ConnectionErrorDetails"))) {
+ ConnectionStatus oldConnectionStatus = connectionStatus;
+
+ if (props.contains(QLatin1String("ConnectionStatus")) &&
+ connectionStatus != ConnectionStatus(
+ qdbus_cast<uint>(props[QLatin1String("ConnectionStatus")]))) {
+ connectionStatus = ConnectionStatus(
+ qdbus_cast<uint>(props[QLatin1String("ConnectionStatus")]));
+ debug() << " Connection Status:" << connectionStatus;
+ connectionStatusChanged = true;
+ }
+
+ if (props.contains(QLatin1String("ConnectionStatusReason")) &&
+ connectionStatusReason != ConnectionStatusReason(
+ qdbus_cast<uint>(props[QLatin1String("ConnectionStatusReason")]))) {
+ connectionStatusReason = ConnectionStatusReason(
+ qdbus_cast<uint>(props[QLatin1String("ConnectionStatusReason")]));
+ debug() << " Connection StatusReason:" << connectionStatusReason;
+ connectionStatusChanged = true;
+ }
+
+ if (connectionStatusChanged) {
+ parent->notify("connectionStatus");
+ parent->notify("connectionStatusReason");
+ }
+
+ if (props.contains(QLatin1String("ConnectionError")) &&
+ connectionError != qdbus_cast<QString>(
+ props[QLatin1String("ConnectionError")])) {
+ connectionError = qdbus_cast<QString>(
+ props[QLatin1String("ConnectionError")]);
+ debug() << " Connection Error:" << connectionError;
+ connectionStatusChanged = true;
+ }
+
+ if (props.contains(QLatin1String("ConnectionErrorDetails")) &&
+ connectionErrorDetails.allDetails() != qdbus_cast<QVariantMap>(
+ props[QLatin1String("ConnectionErrorDetails")])) {
+ connectionErrorDetails = Connection::ErrorDetails(qdbus_cast<QVariantMap>(
+ props[QLatin1String("ConnectionErrorDetails")]));
+ debug() << " Connection Error Details:" << connectionErrorDetails.allDetails();
+ connectionStatusChanged = true;
+ }
+
+ if (connectionStatusChanged) {
+ /* Something other than status changed, let's not emit connectionStatusChanged
+ * and keep the error/errorDetails, for the next interaction.
+ * It may happen if ConnectionError changes and in another property
+ * change the status changes to Disconnected, so we use the error
+ * previously signalled. If the status changes to something other
+ * than Disconnected later, the error is cleared. */
+ if (oldConnectionStatus != connectionStatus) {
+ /* We don't signal error for status other than Disconnected */
+ if (connectionStatus != ConnectionStatusDisconnected) {
+ connectionError = QString();
+ connectionErrorDetails = Connection::ErrorDetails();
+ } else if (connectionError.isEmpty()) {
+ connectionError = ConnectionHelper::statusReasonToErrorName(
+ connectionStatusReason, oldConnectionStatus);
+ }
+
+ checkCapabilitiesChanged(profileChanged);
+
+ emit parent->connectionStatusChanged(connectionStatus);
+ parent->notify("connectionError");
+ parent->notify("connectionErrorDetails");
+ } else {
+ connectionStatusChanged = false;
+ }
+ }
+ }
+
+ if (!connectionStatusChanged && profileChanged) {
+ checkCapabilitiesChanged(profileChanged);
+ }
+}
+
+void Account::Private::retrieveAvatar()
+{
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ parent->mPriv->properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR),
+ QLatin1String("Avatar")), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotAvatar(QDBusPendingCallWatcher*)));
+}
+
+bool Account::Private::processConnQueue()
+{
+ while (!connObjPathQueue.isEmpty()) {
+ QString path = connObjPathQueue.head();
+ if (path.isEmpty()) {
+ if (!connection.isNull()) {
+ debug() << "Dropping connection for account" << parent->objectPath();
+
+ connection.reset();
+ emit parent->connectionChanged(connection);
+ parent->notify("connection");
+ parent->notify("connectionObjectPath");
+ }
+
+ connObjPathQueue.dequeue();
+ } else {
+ debug() << "Building connection" << path << "for account" << parent->objectPath();
+
+ if (connection && connection->objectPath() == path) {
+ debug() << " Connection already built";
+ connObjPathQueue.dequeue();
+ continue;
+ }
+
+ QString busName = path.mid(1).replace(QLatin1String("/"), QLatin1String("."));
+ parent->connect(connFactory->proxy(busName, path, chanFactory, contactFactory),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onConnectionBuilt(Tp::PendingOperation*)));
+
+ // No dequeue here, but only in onConnectionBuilt, so we will queue future changes
+ return false; // Only move on to the next paths when that build finishes
+ }
+ }
+
+ return true;
+}
+
+void Account::onDispatcherIntrospected(Tp::PendingOperation *op)
+{
+ if (!mPriv->dispatcherContext->introspected) {
+ Tp::PendingVariant *pv = static_cast<Tp::PendingVariant *>(op);
+ Q_ASSERT(pv != NULL);
+
+ // Only the first Account for a given dispatcher will enter this branch, and will
+ // immediately make further created accounts skip the whole waiting for CD to get
+ // introspected part entirely
+ mPriv->dispatcherContext->introspected = true;
+
+ if (pv->isValid()) {
+ mPriv->dispatcherContext->supportsHints = qdbus_cast<bool>(pv->result());
+ debug() << "Discovered channel dispatcher support for request hints: "
+ << mPriv->dispatcherContext->supportsHints;
+ } else {
+ if (pv->errorName() == TP_QT4_ERROR_NOT_IMPLEMENTED) {
+ debug() << "Channel Dispatcher does not implement support for request hints";
+ } else {
+ warning() << "(Too old?) Channel Dispatcher failed to tell us whether"
+ << "it supports request hints, assuming it doesn't:"
+ << pv->errorName() << ':' << pv->errorMessage();
+ }
+ mPriv->dispatcherContext->supportsHints = false;
+ }
+ }
+
+ debug() << "Calling Properties::GetAll(Account) on " << objectPath();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ mPriv->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT)), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+}
+
+void Account::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties.GetAll(Account) for" << objectPath();
+ mPriv->updateProperties(reply.value());
+
+ mPriv->readinessHelper->setInterfaces(interfaces());
+ mPriv->mayFinishCore = true;
+
+ if (mPriv->connObjPathQueue.isEmpty()) {
+ debug() << "Account basic functionality is ready";
+ mPriv->coreFinished = true;
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ debug() << "Deferring finishing Account::FeatureCore until the connection is built";
+ }
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error());
+
+ warning().nospace() <<
+ "GetAll(Account) failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ }
+
+ watcher->deleteLater();
+}
+
+void Account::gotAvatar(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to GetAvatar(Account)";
+ mPriv->avatar = qdbus_cast<Avatar>(reply);
+
+ // It could be in either of actual or missing from the first time in corner cases like the
+ // object going away, so let's be prepared for both (only checking for actualFeatures here
+ // actually used to trigger a rare bug)
+ //
+ // Anyway, the idea is to not do setIntrospectCompleted twice
+ if (!mPriv->readinessHelper->actualFeatures().contains(FeatureAvatar) &&
+ !mPriv->readinessHelper->missingFeatures().contains(FeatureAvatar)) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureAvatar, true);
+ }
+
+ emit avatarChanged(mPriv->avatar);
+ notify("avatar");
+ } else {
+ // check if the feature is already there, and for some reason retrieveAvatar
+ // failed when called the second time
+ if (!mPriv->readinessHelper->actualFeatures().contains(FeatureAvatar) &&
+ !mPriv->readinessHelper->missingFeatures().contains(FeatureAvatar)) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureAvatar, false, reply.error());
+ }
+
+ warning().nospace() <<
+ "GetAvatar(Account) failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ }
+
+ watcher->deleteLater();
+}
+
+void Account::onAvatarChanged()
+{
+ debug() << "Avatar changed, retrieving it";
+ mPriv->retrieveAvatar();
+}
+
+void Account::onConnectionManagerReady(PendingOperation *operation)
+{
+ bool error = operation->isError();
+ if (!error) {
+ error = !mPriv->cm->hasProtocol(mPriv->protocolName);
+ }
+
+ if (!error) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureProtocolInfo, true);
+ }
+ else {
+ warning() << "Failed to find the protocol in the CM protocols for account" << objectPath();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureProtocolInfo, false,
+ operation->errorName(), operation->errorMessage());
+ }
+}
+
+void Account::onConnectionReady(PendingOperation *op)
+{
+ mPriv->checkCapabilitiesChanged(false);
+
+ /* let's not fail if connection can't become ready, the caps will still
+ * work, but return the CM caps instead. Also no need to call
+ * setIntrospectCompleted if the feature was already set to complete once,
+ * since this method will be called whenever the account connection
+ * changes */
+ if (!isReady(FeatureCapabilities)) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCapabilities, true);
+ }
+}
+
+void Account::onPropertyChanged(const QVariantMap &delta)
+{
+ mPriv->updateProperties(delta);
+}
+
+void Account::onRemoved()
+{
+ mPriv->valid = false;
+ mPriv->enabled = false;
+ invalidate(QLatin1String(TELEPATHY_QT4_ERROR_OBJECT_REMOVED),
+ QLatin1String("Account removed from AccountManager"));
+ emit removed();
+}
+
+void Account::onConnectionBuilt(PendingOperation *op)
+{
+ PendingReady *readyOp = qobject_cast<PendingReady *>(op);
+ Q_ASSERT(readyOp != NULL);
+
+ if (op->isError()) {
+ warning() << "Building connection" << mPriv->connObjPathQueue.head() << "failed with" <<
+ op->errorName() << "-" << op->errorMessage();
+
+ if (!mPriv->connection.isNull()) {
+ mPriv->connection.reset();
+ emit connectionChanged(mPriv->connection);
+ notify("connection");
+ notify("connectionObjectPath");
+ }
+ } else {
+ ConnectionPtr prevConn = mPriv->connection;
+ QString prevConnPath = mPriv->connectionObjectPath();
+
+ mPriv->connection = ConnectionPtr::qObjectCast(readyOp->proxy());
+ Q_ASSERT(mPriv->connection);
+
+ debug() << "Connection" << mPriv->connectionObjectPath() << "built for" << objectPath();
+
+ if (prevConn != mPriv->connection) {
+ notify("connection");
+ emit connectionChanged(mPriv->connection);
+ }
+
+ if (prevConnPath != mPriv->connectionObjectPath()) {
+ notify("connectionObjectPath");
+ }
+ }
+
+ mPriv->connObjPathQueue.dequeue();
+
+ if (mPriv->processConnQueue() && !mPriv->coreFinished && mPriv->mayFinishCore) {
+ debug() << "Account" << objectPath() << "basic functionality is ready (connections built)";
+ mPriv->coreFinished = true;
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+}
+
+/**
+ * \fn void Account::removed()
+ *
+ * Emitted when this account is removed from the account manager it belonged.
+ *
+ * \sa remove().
+ */
+
+/**
+ * \fn void Account::validityChanged(bool validity)
+ *
+ * Emitted when the value of isValidAccount() changes.
+ *
+ * \param validity The new validity of this account.
+ * \sa isValidAccount()
+ */
+
+/**
+ * \fn void Account::stateChanged(bool state)
+ *
+ * Emitted when the value of isEnabled() changes.
+ *
+ * \param state The new state of this account.
+ * \sa isEnabled()
+ */
+
+/**
+ * \fn void Account::serviceNameChanged(const QString &serviceName)
+ *
+ * Emitted when the value of serviceName() changes.
+ *
+ * \param serviceName The new service name of this account.
+ * \sa serviceName(), setServiceName()
+ */
+
+/**
+ * \fn void Account::profileChanged(const Tp::ProfilePtr &profile)
+ *
+ * Emitted when the value of profile() changes.
+ *
+ * \param profile The new profile of this account.
+ * \sa profile()
+ */
+
+/**
+ * \fn void Account::displayNameChanged(const QString &displayName)
+ *
+ * Emitted when the value of displayName() changes.
+ *
+ * \param displayName The new display name of this account.
+ * \sa displayName(), setDisplayName()
+ */
+
+/**
+ * \fn void Account::iconNameChanged(const QString &iconName)
+ *
+ * Emitted when the value of iconName() changes.
+ *
+ * \param iconName The new icon name of this account.
+ * \sa iconName(), setIconName()
+ */
+
+/**
+ * \fn void Account::nicknameChanged(const QString &nickname)
+ *
+ * Emitted when the value of nickname() changes.
+ *
+ * \param nickname The new nickname of this account.
+ * \sa nickname(), setNickname()
+ */
+
+/**
+ * \fn void Account::normalizedNameChanged(const QString &normalizedName)
+ *
+ * Emitted when the value of normalizedName() changes.
+ *
+ * \param normalizedName The new normalized name of this account.
+ * \sa normalizedName()
+ */
+
+/**
+ * \fn void Account::capabilitiesChanged(const Tp::ConnectionCapabilities &capabilities)
+ *
+ * Emitted when the value of capabilities() changes.
+ *
+ * \param capabilities The new capabilities of this account.
+ * \sa capabilities()
+ */
+
+/**
+ * \fn void Account::connectsAutomaticallyPropertyChanged(bool connectsAutomatically)
+ *
+ * Emitted when the value of connectsAutomatically() changes.
+ *
+ * \param connectsAutomatically The new value of connects automatically property
+ * of this account.
+ * \sa isEnabled()
+ */
+
+/**
+ * \fn void Account::firstOnline()
+ *
+ * Emitted when this account is first put online.
+ *
+ * \sa hasBeenOnline()
+ */
+
+/**
+ * \fn void Account::parametersChanged(const QVariantMap &parameters)
+ *
+ * Emitted when the value of parameters() changes.
+ *
+ * \param parameters The new parameters of this account.
+ * \sa parameters()
+ */
+
+/**
+ * \fn void Account::changingPresence(bool value)
+ *
+ * Emitted when the value of isChangingPresence() changes.
+ *
+ * \param value Whether this account's connection is changing presence.
+ * \sa isChangingPresence()
+ */
+
+/**
+ * \fn void Account::automaticPresenceChanged(const Tp::Presence &automaticPresence)
+ *
+ * Emitted when the value of automaticPresence() changes.
+ *
+ * \param automaticPresence The new value of automatic presence property of this
+ * account.
+ * \sa automaticPresence(), currentPresenceChanged()
+ */
+
+/**
+ * \fn void Account::currentPresenceChanged(const Tp::Presence &currentPresence)
+ *
+ * Emitted when the value of currentPresence() changes.
+ *
+ * \param currentPresence The new value of the current presence property of this
+ * account.
+ * \sa currentPresence()
+ */
+
+/**
+ * \fn void Account::requestedPresenceChanged(const Tp::Presence &requestedPresence)
+ *
+ * Emitted when the value of requestedPresence() changes.
+ *
+ * \param requestedPresence The new value of the requested presence property of this
+ * account.
+ * \sa requestedPresence(), currentPresenceChanged()
+ */
+
+/**
+ * \fn void Account::onlinenessChanged(bool online)
+ *
+ * Emitted when the value of isOnline() changes.
+ *
+ * \param online Whether this account is online.
+ * \sa isOnline(), currentPresence()
+ */
+
+/**
+ * \fn void Account::avatarChanged(const Tp::Avatar &avatar)
+ *
+ * Emitted when the value of avatar() changes.
+ *
+ * \param avatar The new avatar of this account.
+ * \sa avatar()
+ */
+
+/**
+ * \fn void Account::connectionStatusChanged(Tp::ConnectionStatus status)
+ *
+ * Emitted when the connection status changes.
+ *
+ * \param status The new status of this account connection.
+ * \sa connectionStatus(), connectionStatusReason(), connectionError(), connectionErrorDetails(),
+ * Connection::ErrorDetails
+ */
+
+/**
+ * \fn void Account::connectionChanged(const Tp::ConnectionPtr &connection)
+ *
+ * Emitted when the value of connection() changes.
+ *
+ * The \a connection will have the features set in the ConnectionFactory used by this account ready
+ * and the same channel and contact factories used by this account.
+ *
+ * \param connection A ConnectionPtr pointing to the new Connection object or a null ConnectionPtr
+ * if there is no connection.
+ * \sa connection()
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/account.h b/qt4/TelepathyQt4/account.h
new file mode 100644
index 000000000..3a8df411d
--- /dev/null
+++ b/qt4/TelepathyQt4/account.h
@@ -0,0 +1,598 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_account_h_HEADER_GUARD_
+#define _TelepathyQt4_account_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-account.h>
+
+#include <TelepathyQt4/ChannelRequestHints>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ChannelDispatcherInterface>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/FileTransferChannelCreationProperties>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/Presence>
+#include <TelepathyQt4/PresenceSpec>
+#include <TelepathyQt4/ProtocolInfo>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/SharedPtr>
+
+#include <QSet>
+#include <QString>
+#include <QStringList>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class Account;
+class Connection;
+class PendingChannel;
+class PendingChannelRequest;
+class PendingConnection;
+class PendingOperation;
+class PendingReady;
+class PendingStringList;
+
+class TELEPATHY_QT4_EXPORT Account : public StatelessDBusProxy,
+ public OptionalInterfaceFactory<Account>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Account)
+ Q_PROPERTY(bool valid READ isValidAccount NOTIFY validityChanged)
+ Q_PROPERTY(bool enabled READ isEnabled NOTIFY stateChanged)
+ Q_PROPERTY(QString cmName READ cmName)
+ Q_PROPERTY(QString protocolName READ protocolName)
+ Q_PROPERTY(QString serviceName READ serviceName NOTIFY serviceNameChanged)
+ Q_PROPERTY(ProfilePtr profile READ profile NOTIFY profileChanged)
+ Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
+ Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged)
+ Q_PROPERTY(QString nickname READ nickname NOTIFY nicknameChanged)
+ Q_PROPERTY(AvatarSpec avatarRequirements READ avatarRequirements)
+ Q_PROPERTY(Avatar avatar READ avatar NOTIFY avatarChanged)
+ Q_PROPERTY(QVariantMap parameters READ parameters NOTIFY parametersChanged)
+ Q_PROPERTY(ProtocolInfo protocolInfo READ protocolInfo)
+ Q_PROPERTY(ConnectionCapabilities capabilities READ capabilities NOTIFY capabilitiesChanged)
+ Q_PROPERTY(bool hasBeenOnline READ hasBeenOnline)
+ Q_PROPERTY(bool connectsAutomatically READ connectsAutomatically NOTIFY connectsAutomaticallyPropertyChanged)
+ Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus NOTIFY connectionStatusChanged)
+ Q_PROPERTY(ConnectionStatusReason connectionStatusReason READ connectionStatusReason)
+ Q_PROPERTY(QString connectionError READ connectionError)
+ Q_PROPERTY(Tp::Connection::ErrorDetails connectionErrorDetails READ connectionErrorDetails)
+ Q_PROPERTY(ConnectionPtr connection READ connection NOTIFY connectionChanged)
+ Q_PROPERTY(bool changingPresence READ isChangingPresence NOTIFY changingPresence)
+ Q_PROPERTY(Presence automaticPresence READ automaticPresence NOTIFY automaticPresenceChanged)
+ Q_PROPERTY(Presence currentPresence READ currentPresence NOTIFY currentPresenceChanged)
+ Q_PROPERTY(Presence requestedPresence READ requestedPresence NOTIFY requestedPresenceChanged)
+ Q_PROPERTY(bool online READ isOnline NOTIFY onlinenessChanged)
+ Q_PROPERTY(QString uniqueIdentifier READ uniqueIdentifier)
+ Q_PROPERTY(QString normalizedName READ normalizedName NOTIFY normalizedNameChanged)
+
+public:
+ static const Feature FeatureCore;
+ static const Feature FeatureAvatar;
+ static const Feature FeatureProtocolInfo;
+ static const Feature FeatureCapabilities;
+ static const Feature FeatureProfile;
+
+ static AccountPtr create(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connectionFactory =
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ const ChannelFactoryConstPtr &channelFactory =
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+ static AccountPtr create(const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+ virtual ~Account();
+
+ ConnectionFactoryConstPtr connectionFactory() const;
+ ChannelFactoryConstPtr channelFactory() const;
+ ContactFactoryConstPtr contactFactory() const;
+
+ bool isValidAccount() const;
+
+ bool isEnabled() const;
+ PendingOperation *setEnabled(bool value);
+
+ QString cmName() const;
+
+ QString protocolName() const;
+
+ QString serviceName() const;
+ PendingOperation *setServiceName(const QString &value);
+
+ ProfilePtr profile() const;
+
+ QString displayName() const;
+ PendingOperation *setDisplayName(const QString &value);
+
+ QString iconName() const;
+ PendingOperation *setIconName(const QString &value);
+
+ QString nickname() const;
+ PendingOperation *setNickname(const QString &value);
+
+ AvatarSpec avatarRequirements() const;
+ // TODO: We probably want to expose the avatar file name once we have the avatar token and MC
+ // starts sharing the cache used by tp-qt4 and tp-glib and use Tp::AvatarData to represent
+ // it as used in Tp::Contact
+ const Avatar &avatar() const;
+ PendingOperation *setAvatar(const Avatar &avatar);
+
+ QVariantMap parameters() const;
+ PendingStringList *updateParameters(const QVariantMap &set,
+ const QStringList &unset);
+
+ ProtocolInfo protocolInfo() const;
+
+ ConnectionCapabilities capabilities() const;
+
+ bool connectsAutomatically() const;
+ PendingOperation *setConnectsAutomatically(bool value);
+
+ bool hasBeenOnline() const;
+
+ ConnectionStatus connectionStatus() const;
+ ConnectionStatusReason connectionStatusReason() const;
+ QString connectionError() const;
+ Connection::ErrorDetails connectionErrorDetails() const;
+ ConnectionPtr connection() const;
+
+ bool isChangingPresence() const;
+
+ PresenceSpecList allowedPresenceStatuses(bool includeAllStatuses = false) const;
+ uint maxPresenceStatusMessageLength() const;
+
+ // TODO: Add overload methods to set presence from a Profile::Presence
+ // TODO: Add usablePresences() that would return a list of presences that could be set on the
+ // account
+ Presence automaticPresence() const;
+ PendingOperation *setAutomaticPresence(const Presence &presence);
+
+ Presence currentPresence() const;
+
+ Presence requestedPresence() const;
+ PendingOperation *setRequestedPresence(const Presence &presence);
+
+ bool isOnline() const;
+
+ QString uniqueIdentifier() const;
+
+ QString normalizedName() const;
+
+ PendingOperation *reconnect();
+
+ PendingOperation *remove();
+
+ bool supportsRequestHints() const;
+ bool requestsSucceedWithChannel() const;
+
+ // TODO ABI break: collapse all of the overloads without a hints arg with the corresponding
+ // hints versions and add a default param for the hints args
+
+ PendingChannelRequest *ensureTextChat(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureTextChat(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureTextChat(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureTextChat(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureTextChatroom(
+ const QString &roomName,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureTextChatroom(
+ const QString &roomName,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureStreamedMediaCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureStreamedMediaCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureStreamedMediaCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureStreamedMediaCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureStreamedMediaAudioCall(
+ const QString &contactIdentifier,
+ QDateTime userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureStreamedMediaAudioCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureStreamedMediaAudioCall(
+ const ContactPtr &contact,
+ QDateTime userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureStreamedMediaAudioCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureStreamedMediaVideoCall(
+ const QString &contactIdentifier,
+ bool withAudio = true,
+ QDateTime userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureStreamedMediaVideoCall(
+ const QString &contactIdentifier,
+ bool withAudio,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureStreamedMediaVideoCall(
+ const ContactPtr &contact,
+ bool withAudio = true,
+ QDateTime userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureStreamedMediaVideoCall(
+ const ContactPtr &contact,
+ bool withAudio,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *createFileTransfer(
+ const QString &contactIdentifier,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createFileTransfer(
+ const QString &contactIdentifier,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *createFileTransfer(
+ const ContactPtr &contact,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createFileTransfer(
+ const ContactPtr &contact,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *createStreamTube(
+ const QString &contactIdentifier,
+ const QString &service,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString(),
+ const ChannelRequestHints &hints = ChannelRequestHints());
+ PendingChannelRequest *createStreamTube(
+ const ContactPtr &contact,
+ const QString &service,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString(),
+ const ChannelRequestHints &hints = ChannelRequestHints());
+
+ PendingChannelRequest *createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ TELEPATHY_QT4_DEPRECATED PendingChannelRequest *createConferenceTextChatRoom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString(),
+ const ChannelRequestHints &hints = ChannelRequestHints());
+
+ TELEPATHY_QT4_DEPRECATED PendingChannelRequest *createConferenceTextChatRoom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString(),
+ const ChannelRequestHints &hints = ChannelRequestHints());
+
+ PendingChannelRequest *createContactSearch(
+ const QString &server = QString(),
+ uint limit = 0,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createContactSearch(
+ const QString &server,
+ uint limit,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannel *ensureAndHandleTextChat(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *ensureAndHandleTextChat(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *ensureAndHandleTextChatroom(
+ const QString &roomName,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *ensureAndHandleStreamedMediaCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *ensureAndHandleStreamedMediaCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *ensureAndHandleStreamedMediaAudioCall(
+ const QString &contactIdentifier,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *ensureAndHandleStreamedMediaAudioCall(
+ const ContactPtr &contact,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *ensureAndHandleStreamedMediaVideoCall(
+ const QString &contactIdentifier,
+ bool withAudio = true,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *ensureAndHandleStreamedMediaVideoCall(
+ const ContactPtr &contact,
+ bool withAudio = true,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *createAndHandleFileTransfer(
+ const QString &contactIdentifier,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *createAndHandleFileTransfer(
+ const ContactPtr &contact,
+ const FileTransferChannelCreationProperties &properties,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *createAndHandleStreamTube(
+ const QString &contactIdentifier,
+ const QString &service,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *createAndHandleStreamTube(
+ const ContactPtr &contact,
+ const QString &service,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *createAndHandleConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *createAndHandleConferenceTextChat(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *createAndHandleConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *createAndHandleConferenceTextChatroom(
+ const QString &roomName,
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *createAndHandleConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QStringList &initialInviteeContactsIdentifiers = QStringList(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+ PendingChannel *createAndHandleConferenceStreamedMediaCall(
+ const QList<ChannelPtr> &channels,
+ const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(),
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ PendingChannel *createAndHandleContactSearch(
+ const QString &server = QString(),
+ uint limit = 0,
+ const QDateTime &userActionTime = QDateTime::currentDateTime());
+
+ // advanced
+ PendingChannelRequest *createChannel(
+ const QVariantMap &requestedProperties,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *createChannel(
+ const QVariantMap &requestedProperties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannelRequest *ensureChannel(
+ const QVariantMap &requestedProperties,
+ const QDateTime &userActionTime = QDateTime::currentDateTime(),
+ const QString &preferredHandler = QString());
+ PendingChannelRequest *ensureChannel(
+ const QVariantMap &requestedProperties,
+ const QDateTime &userActionTime,
+ const QString &preferredHandler,
+ const ChannelRequestHints &hints);
+
+ PendingChannel *createAndHandleChannel(
+ const QVariantMap &requestedProperties,
+ const QDateTime &userActionTime);
+ PendingChannel *ensureAndHandleChannel(
+ const QVariantMap &requestedProperties,
+ const QDateTime &userActionTime);
+
+Q_SIGNALS:
+ void removed();
+ void serviceNameChanged(const QString &serviceName);
+ void profileChanged(const Tp::ProfilePtr &profile);
+ void displayNameChanged(const QString &displayName);
+ void iconNameChanged(const QString &iconName);
+ void nicknameChanged(const QString &nickname);
+ void normalizedNameChanged(const QString &normalizedName);
+ void validityChanged(bool validity);
+ void stateChanged(bool state);
+ void capabilitiesChanged(const Tp::ConnectionCapabilities &capabilities);
+ void connectsAutomaticallyPropertyChanged(bool connectsAutomatically);
+ void firstOnline();
+ void parametersChanged(const QVariantMap &parameters);
+ void changingPresence(bool value);
+ void automaticPresenceChanged(const Tp::Presence &automaticPresence);
+ void currentPresenceChanged(const Tp::Presence &currentPresence);
+ void requestedPresenceChanged(const Tp::Presence &requestedPresence);
+ void onlinenessChanged(bool online);
+ void avatarChanged(const Tp::Avatar &avatar);
+ void connectionStatusChanged(Tp::ConnectionStatus status);
+ void connectionChanged(const Tp::ConnectionPtr &connection);
+
+protected:
+ friend class PendingChannelRequest; // to access dispatcherInterface()
+
+ Account(const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const Feature &coreFeature);
+
+ Client::AccountInterface *baseInterface() const;
+ Client::ChannelDispatcherInterface *dispatcherInterface() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onDispatcherIntrospected(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void gotAvatar(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void onAvatarChanged();
+ TELEPATHY_QT4_NO_EXPORT void onConnectionManagerReady(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionReady(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onPropertyChanged(const QVariantMap &delta);
+ TELEPATHY_QT4_NO_EXPORT void onRemoved();
+ TELEPATHY_QT4_NO_EXPORT void onConnectionBuilt(Tp::PendingOperation *);
+
+private:
+ struct Private;
+ friend struct Private;
+
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/account.xml b/qt4/TelepathyQt4/account.xml
new file mode 100644
index 000000000..1cc5b2a15
--- /dev/null
+++ b/qt4/TelepathyQt4/account.xml
@@ -0,0 +1,13 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Account interfaces</tp:title>
+
+<xi:include href="../spec/Account.xml"/>
+
+<xi:include href="../spec/Account_Interface_Addressing.xml"/>
+<xi:include href="../spec/Account_Interface_Avatar.xml"/>
+<xi:include href="../spec/Account_Interface_Storage.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/and-filter.dox b/qt4/TelepathyQt4/and-filter.dox
new file mode 100644
index 000000000..47d6e52e1
--- /dev/null
+++ b/qt4/TelepathyQt4/and-filter.dox
@@ -0,0 +1,33 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::AndFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/and-filter.h <TelepathyQt4/AndFilter>
+ *
+ * \brief The AndFilter class provides a generic filter object to be used
+ * in conjunction of other filters.
+ *
+ * The AndFilter will match if all of its given list of filters matches
+ * their criteria.
+ */
diff --git a/qt4/TelepathyQt4/and-filter.h b/qt4/TelepathyQt4/and-filter.h
new file mode 100644
index 000000000..db22170f8
--- /dev/null
+++ b/qt4/TelepathyQt4/and-filter.h
@@ -0,0 +1,83 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_and_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_and_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+template <class T>
+class AndFilter : public Filter<T>
+{
+public:
+ static SharedPtr<AndFilter<T> > create(
+ const QList<SharedPtr<const Filter<T> > > &filters = QList<SharedPtr<const Filter<T> > >())
+ {
+ return SharedPtr<AndFilter<T> >(new AndFilter<T>(filters));
+ }
+
+ inline virtual ~AndFilter() { }
+
+ inline virtual bool isValid() const
+ {
+ Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) {
+ if (!filter || !filter->isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ inline virtual bool matches(const SharedPtr<T> &t) const
+ {
+ if (!isValid()) {
+ return false;
+ }
+
+ Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) {
+ if (!filter->matches(t)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ inline QList<SharedPtr<const Filter<T> > > filters() const { return mFilters; }
+
+private:
+ AndFilter(const QList<SharedPtr<const Filter<T> > > &filters)
+ : Filter<T>(), mFilters(filters) { }
+
+ QList<SharedPtr<const Filter<T> > > mFilters;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/async-model.dox b/qt4/TelepathyQt4/async-model.dox
new file mode 100644
index 000000000..f849272b0
--- /dev/null
+++ b/qt4/TelepathyQt4/async-model.dox
@@ -0,0 +1,56 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \page async_model Asynchronous Object Model
+ *
+ * \section async_model_overview Overview
+ *
+ * Telepathy-Qt4 uses \dbus to communicate with applications implementing the \telepathy_spec.
+ *
+ * When dealing with D-Bus, method calls can take some time to return,
+ * and in this case is not desirable to make synchronous calls,
+ * which could turn into applications hanging waiting for method returns.
+ * In order to avoid this issue, all Telepathy-Qt4 high-level methods requiring
+ * D-Bus method calls will return a \link Tp::PendingOperation \endlink which will
+ * emit the signal \link Tp::PendingOperation::finished() \endlink when the operation
+ * has ended. See individual methods' documentation for more details.
+ *
+ * Additionally Telepathy-Qt4 introduces a concept in which object features need
+ * to be enabled before usage. Information corresponding to enabled features can be inspected
+ * synchronously with no need for asynchronous D-Bus method calls and the associated
+ * programming complexity.
+ *
+ * To avoid the complexity of doing asynchronous calls when making object features ready
+ * Telepathy-Qt4 also provides so called factories for the main objects.
+ * These object features can be enabled by constructing a corresponding factory and enabling the
+ * desired features, and passing these factories to the objects responsible for creating
+ * the objects whose features are required.
+ * Doing that, applications are guaranteed that the specified features are ready in objects
+ * signaled to them by the library.
+ *
+ * However, if a particular feature is only ever used in a specific circumstance, such as an user
+ * opening some settings dialog separate from the general view of the application,
+ * features can be later enabled as needed by calling becomeReady(), or in the
+ * \link Tp::Contact \endlink case by calling \link Tp::ContactManager::upgradeContacts() \endlink,
+ * with the additional features on the object, and waiting for the resulting PendingOperation to
+ * finish.
+ */
diff --git a/qt4/TelepathyQt4/avatar.cpp b/qt4/TelepathyQt4/avatar.cpp
new file mode 100644
index 000000000..7fb796f6e
--- /dev/null
+++ b/qt4/TelepathyQt4/avatar.cpp
@@ -0,0 +1,172 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/AvatarSpec>
+
+namespace Tp
+{
+
+/**
+ * \class AvatarData
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/avatar.h <TelepathyQt4/AvatarData>
+ *
+ * \brief The AvatarData class represents a Telepathy avatar.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT AvatarSpec::Private : public QSharedData
+{
+ Private(const QStringList &supportedMimeTypes,
+ uint minHeight, uint maxHeight, uint recommendedHeight,
+ uint minWidth, uint maxWidth, uint recommendedWidth,
+ uint maxBytes)
+ : supportedMimeTypes(supportedMimeTypes),
+ minHeight(minHeight),
+ maxHeight(maxHeight),
+ recommendedHeight(recommendedHeight),
+ minWidth(minWidth),
+ maxWidth(maxWidth),
+ recommendedWidth(recommendedWidth),
+ maxBytes(maxBytes)
+ {
+ }
+
+ QStringList supportedMimeTypes;
+ uint minHeight;
+ uint maxHeight;
+ uint recommendedHeight;
+ uint minWidth;
+ uint maxWidth;
+ uint recommendedWidth;
+ uint maxBytes;
+};
+
+/**
+ * \class AvatarSpec
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/avatar.h <TelepathyQt4/AvatarSpec>
+ *
+ * \brief The AvatarSpec class represents a Telepathy avatar information
+ * supported by a protocol.
+ */
+
+AvatarSpec::AvatarSpec()
+{
+}
+
+AvatarSpec::AvatarSpec(const QStringList &supportedMimeTypes,
+ uint minHeight, uint maxHeight, uint recommendedHeight,
+ uint minWidth, uint maxWidth, uint recommendedWidth,
+ uint maxBytes)
+ : mPriv(new Private(supportedMimeTypes, minHeight, maxHeight, recommendedHeight,
+ minWidth, maxWidth, recommendedWidth, maxBytes))
+{
+}
+
+AvatarSpec::AvatarSpec(const AvatarSpec &other)
+ : mPriv(other.mPriv)
+{
+}
+
+AvatarSpec::~AvatarSpec()
+{
+}
+
+AvatarSpec &AvatarSpec::operator=(const AvatarSpec &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+QStringList AvatarSpec::supportedMimeTypes() const
+{
+ if (!isValid()) {
+ return QStringList();
+ }
+
+ return mPriv->supportedMimeTypes;
+}
+
+uint AvatarSpec::minimumHeight() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->minHeight;
+}
+
+uint AvatarSpec::maximumHeight() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->maxHeight;
+}
+
+uint AvatarSpec::recommendedHeight() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->recommendedHeight;
+}
+
+uint AvatarSpec::minimumWidth() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->minWidth;
+}
+
+uint AvatarSpec::maximumWidth() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->maxWidth;
+}
+
+uint AvatarSpec::recommendedWidth() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->recommendedWidth;
+}
+
+uint AvatarSpec::maximumBytes() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->maxBytes;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/avatar.h b/qt4/TelepathyQt4/avatar.h
new file mode 100644
index 000000000..a229ae68a
--- /dev/null
+++ b/qt4/TelepathyQt4/avatar.h
@@ -0,0 +1,86 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010-2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_avatar_h_HEADER_GUARD_
+#define _TelepathyQt4_avatar_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QSharedDataPointer>
+#include <QString>
+#include <QStringList>
+#include <QMetaType>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_EXPORT AvatarData
+{
+public:
+ inline AvatarData(const QString &fileName, const QString &mimeType)
+ : fileName(fileName), mimeType(mimeType) {}
+ inline AvatarData() {}
+
+ QString fileName;
+ QString mimeType;
+};
+
+class TELEPATHY_QT4_EXPORT AvatarSpec
+{
+public:
+ AvatarSpec();
+ AvatarSpec(const QStringList &supportedMimeTypes,
+ uint minHeight, uint maxHeight, uint recommendedHeight,
+ uint minWidth, uint maxWidth, uint recommendedWidth,
+ uint maxBytes);
+ AvatarSpec(const AvatarSpec &other);
+ ~AvatarSpec();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ AvatarSpec &operator=(const AvatarSpec &other);
+
+ QStringList supportedMimeTypes() const;
+ uint minimumHeight() const;
+ uint maximumHeight() const;
+ uint recommendedHeight() const;
+ uint minimumWidth() const;
+ uint maximumWidth() const;
+ uint recommendedWidth() const;
+ uint maximumBytes() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::AvatarData);
+Q_DECLARE_METATYPE(Tp::AvatarSpec);
+
+#endif
diff --git a/qt4/TelepathyQt4/capabilities-base.cpp b/qt4/TelepathyQt4/capabilities-base.cpp
new file mode 100644
index 000000000..470c38655
--- /dev/null
+++ b/qt4/TelepathyQt4/capabilities-base.cpp
@@ -0,0 +1,348 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/CapabilitiesBase>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT CapabilitiesBase::Private : public QSharedData
+{
+ Private(bool specificToContact);
+ Private(const RequestableChannelClassSpecList &rccSpecs, bool specificToContact);
+
+ RequestableChannelClassSpecList rccSpecs;
+ bool specificToContact;
+};
+
+CapabilitiesBase::Private::Private(bool specificToContact)
+ : specificToContact(specificToContact)
+{
+}
+
+CapabilitiesBase::Private::Private(const RequestableChannelClassSpecList &rccSpecs,
+ bool specificToContact)
+ : rccSpecs(rccSpecs),
+ specificToContact(specificToContact)
+{
+}
+
+/**
+ * \class CapabilitiesBase
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/capabilities-base.h <TelepathyQt4/CapabilitiesBase>
+ *
+ * \brief The CapabilitiesBase class represents the capabilities a Connection
+ * or a Contact supports.
+ */
+
+/**
+ * Construct a new CapabilitiesBase object.
+ */
+CapabilitiesBase::CapabilitiesBase()
+ : mPriv(new Private(false))
+{
+}
+
+/**
+ * Construct a new CapabilitiesBase object.
+ *
+ * \param specificToContact Whether this object describes the capabilities of a
+ * particular contact.
+ */
+CapabilitiesBase::CapabilitiesBase(bool specificToContact)
+ : mPriv(new Private(specificToContact))
+{
+}
+
+/**
+ * Construct a new CapabilitiesBase object using the given \a rccs.
+ *
+ * \param rccs RequestableChannelClassList representing the capabilities of a
+ * connection or contact.
+ * \param specificToContact Whether this object describes the capabilities of a
+ * particular contact.
+ */
+CapabilitiesBase::CapabilitiesBase(const RequestableChannelClassList &rccs,
+ bool specificToContact)
+ : mPriv(new Private(RequestableChannelClassSpecList(rccs), specificToContact))
+{
+}
+
+/**
+ * Construct a new CapabilitiesBase object using the given \a rccSpecs.
+ *
+ * \param rccSpecs RequestableChannelClassSpecList representing the capabilities of a
+ * connection or contact.
+ * \param specificToContact Whether this object describes the capabilities of a
+ * particular contact.
+ */
+CapabilitiesBase::CapabilitiesBase(const RequestableChannelClassSpecList &rccSpecs,
+ bool specificToContact)
+ : mPriv(new Private(rccSpecs, specificToContact))
+{
+}
+
+CapabilitiesBase::CapabilitiesBase(const CapabilitiesBase &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+CapabilitiesBase::~CapabilitiesBase()
+{
+}
+
+CapabilitiesBase &CapabilitiesBase::operator=(const CapabilitiesBase &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+/**
+ * Return the list of requestable channel class spec representing the requests that can succeed.
+ *
+ * This can be used by advanced clients to determine whether an unusually
+ * complex request would succeed. See the \telepathy_spec
+ * for details of how to interpret the returned list.
+ *
+ * The higher-level methods like textChats() are likely to be more
+ * useful to the majority of clients.
+ *
+ * \return A RequestableChannelClassSpecList indicating the parameters to
+ * Account::createChannel, Account::ensureChannel,
+ * Connection::createChannel and Connection::ensureChannel
+ * that can be expected to work.
+ */
+RequestableChannelClassSpecList CapabilitiesBase::allClassSpecs() const
+{
+ return mPriv->rccSpecs;
+}
+
+void CapabilitiesBase::updateRequestableChannelClasses(
+ const RequestableChannelClassList &rccs)
+{
+ mPriv->rccSpecs = RequestableChannelClassSpecList(rccs);
+}
+
+/**
+ * Return whether this object accurately describes the capabilities of a
+ * particular contact, or if it's only a guess based on the
+ * capabilities of the underlying connection.
+ *
+ * In protocols like XMPP where each contact advertises their capabilities
+ * to others, Contact::capabilities() will generally return an object where
+ * this method returns true.
+ *
+ * In protocols like SIP where contacts' capabilities are not known,
+ * Contact::capabilities() will return an object where this method returns
+ * false, whose methods textChats() etc. are based on what the
+ * underlying connection supports.
+ *
+ * This reflects the fact that the best assumption an application can make is
+ * that every contact supports every channel type supported by the connection,
+ * while indicating that requests to communicate might fail if the contact
+ * does not actually have the necessary functionality.
+ *
+ * \return \c true if this object describes the capabilities of a particular
+ * contact, \c false otherwise.
+ */
+bool CapabilitiesBase::isSpecificToContact() const
+{
+ return mPriv->specificToContact;
+}
+
+/**
+ * Return whether private text channels can be established by providing
+ * a contact identifier.
+ *
+ * If the protocol is such that text chats can be established, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * \return \c true if Account::ensureTextChat() can be expected to work,
+ * \c false otherwise.
+ */
+bool CapabilitiesBase::textChats() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::textChat())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether private audio and/or video calls can be established by
+ * providing a contact identifier.
+ *
+ * If the protocol is such that these calls can be established, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * \return \c true if Account::ensureStreamedMediaCall() can be expected to work,
+ * \c false otherwise.
+ * \sa streamedMediaAudioCalls(), streamedMediaVideoCalls(),
+ * streamedMediaVideoCallsWithAudio()
+ */
+bool CapabilitiesBase::streamedMediaCalls() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaCall())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether private audio calls can be established by providing a
+ * contact identifier.
+ *
+ * Call upgradingCalls() to determine whether such calls are
+ * likely to be upgradable to have a video stream later.
+ *
+ * If the protocol is such that these calls can be established, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * In some older connection managers, streamedMediaAudioCalls() and
+ * streamedMediaVideoCalls() might both return false, even though streamedMediaCalls() returns
+ * true. This indicates that only an older API is supported - clients of these connection managers
+ * must call Account::ensureStreamedMediaCall() to get an empty call, then add audio and/or
+ * video streams to it.
+ *
+ * \return \c true if Account::ensureStreamedMediaAudioCall() can be expected to work,
+ * \c false otherwise.
+ * \sa streamedMediaCalls(), streamedMediaVideoCalls(), streamedMediaVideoCallsWithAudio()
+ */
+bool CapabilitiesBase::streamedMediaAudioCalls() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaAudioCall())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether private video calls can be established by providing a
+ * contact identifier.
+ *
+ * The same comments as for streamedMediaAudioCalls() apply to this method.
+ *
+ * \return \c true if Account::ensureStreamedMediaVideoCall() can be expected to work,
+ * if given \c false as \a withAudio parameter, \c false otherwise.
+ * \sa streamedMediaCalls(), streamedMediaAudioCalls(), streamedMediaVideoCallsWithAudio()
+ */
+bool CapabilitiesBase::streamedMediaVideoCalls() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaVideoCall())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether private video calls with audio can be established by providing a
+ * contact identifier.
+ *
+ * The same comments as for streamedMediaAudioCalls() apply to this method.
+ *
+ * \return \c true if Account::ensureStreamedMediaVideoCall() can be expected to work,
+ * if given \c true as \a withAudio parameter, \c false otherwise.
+ * \sa streamedMediaCalls(), streamedMediaAudioCalls(), streamedMediaVideoCalls()
+ */
+bool CapabilitiesBase::streamedMediaVideoCallsWithAudio() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether the protocol supports adding streams of a different type
+ * to ongoing media calls.
+ *
+ * In some protocols and clients (such as XMPP Jingle), all calls potentially
+ * support both audio and video. This is indicated by returning true.
+ *
+ * In other protocols and clients (such as MSN, and the variant of XMPP Jingle
+ * used by Google clients), the streams are fixed at the time the call is
+ * started, so if you will ever want video, you have to ask for it at the
+ * beginning, for instance with ensureStreamedMediaVideoCall(). This is indicated by
+ * returning false.
+ *
+ * User interfaces can use this method as a UI hint. If it returns false,
+ * then a UI wishing to support both audio and video calls will have to
+ * provide separate "audio call" and "video call" buttons or menu items;
+ * if it returns true, a single button that makes an audio call is sufficient,
+ * because video can be added later.
+ *
+ * (The underlying Telepathy feature is the ImmutableStreams property; if this
+ * method returns true, then ImmutableStreams is false, and vice versa).
+ *
+ * \return \c true if audio calls can be upgraded to audio + video,
+ * \c false otherwise.
+ */
+bool CapabilitiesBase::upgradingStreamedMediaCalls() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.channelType() == TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA &&
+ !rccSpec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".ImmutableStreams"))) {
+ // TODO should we test all classes that have channelType
+ // StreamedMedia or just one is fine?
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether file transfer can be established by providing a contact identifier
+ *
+ * \return \c true if file transfers can be expected to work,
+ * \c false otherwise.
+ */
+bool CapabilitiesBase::fileTransfers() const
+{
+ foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::fileTransfer())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/capabilities-base.h b/qt4/TelepathyQt4/capabilities-base.h
new file mode 100644
index 000000000..e36b6750e
--- /dev/null
+++ b/qt4/TelepathyQt4/capabilities-base.h
@@ -0,0 +1,85 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_capabilities_base_h_HEADER_GUARD_
+#define _TelepathyQt4_capabilities_base_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/RequestableChannelClassSpec>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT CapabilitiesBase
+{
+public:
+ CapabilitiesBase();
+ CapabilitiesBase(const CapabilitiesBase &other);
+ virtual ~CapabilitiesBase();
+
+ CapabilitiesBase &operator=(const CapabilitiesBase &other);
+
+ RequestableChannelClassSpecList allClassSpecs() const;
+
+ bool isSpecificToContact() const;
+
+ bool textChats() const;
+
+ bool streamedMediaCalls() const;
+ bool streamedMediaAudioCalls() const;
+ bool streamedMediaVideoCalls() const;
+ bool streamedMediaVideoCallsWithAudio() const;
+ bool upgradingStreamedMediaCalls() const;
+
+ bool fileTransfers() const;
+
+ // later: FIXME TODO why not now?
+ // QList<FileHashType> fileTransfersRequireHash() const;
+
+protected:
+ CapabilitiesBase(bool specificToContact);
+ CapabilitiesBase(const RequestableChannelClassList &rccs,
+ bool specificToContact);
+ CapabilitiesBase(const RequestableChannelClassSpecList &rccSpecs,
+ bool specificToContact);
+
+ virtual void updateRequestableChannelClasses(
+ const RequestableChannelClassList &rccs);
+
+private:
+ friend class Connection;
+ friend class Contact;
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::CapabilitiesBase);
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-class-features.h b/qt4/TelepathyQt4/channel-class-features.h
new file mode 100644
index 000000000..ecbdd74a1
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-class-features.h
@@ -0,0 +1,45 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_class_features_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_class_features_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/Features>
+
+#include <QMetaType>
+#include <QPair>
+
+namespace Tp
+{
+
+typedef QPair<ChannelClassSpec, Features> ChannelClassFeatures;
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ChannelClassFeatures);
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-class-spec.cpp b/qt4/TelepathyQt4/channel-class-spec.cpp
new file mode 100644
index 000000000..9e91c8390
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-class-spec.cpp
@@ -0,0 +1,555 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ChannelClassSpec>
+
+#include "TelepathyQt4/_gen/future-constants.h"
+
+#include "TelepathyQt4/debug-internal.h"
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ChannelClassSpec::Private : public QSharedData
+{
+ QVariantMap props;
+};
+
+/**
+ * \class ChannelClassSpec
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/channel-class-spec.h <TelepathyQt4/ChannelClassSpec>
+ *
+ * \brief The ChannelClassSpec class represents a Telepathy channel class.
+ */
+
+ChannelClassSpec::ChannelClassSpec()
+{
+}
+
+ChannelClassSpec::ChannelClassSpec(const ChannelClass &cc)
+ : mPriv(new Private)
+{
+ foreach (QString key, cc.keys()) {
+ setProperty(key, cc.value(key).variant());
+ }
+}
+
+ChannelClassSpec::ChannelClassSpec(const QVariantMap &props)
+ : mPriv(new Private)
+{
+ setChannelType(qdbus_cast<QString>(
+ props.value(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"))));
+ setTargetHandleType((HandleType) qdbus_cast<uint>(
+ props.value(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"))));
+
+ foreach (QString propName, props.keys()) {
+ setProperty(propName, props.value(propName));
+ }
+}
+
+ChannelClassSpec::ChannelClassSpec(const QString &channelType, HandleType targetHandleType,
+ const QVariantMap &otherProperties)
+ : mPriv(new Private)
+{
+ setChannelType(channelType);
+ setTargetHandleType(targetHandleType);
+ foreach (QString key, otherProperties.keys()) {
+ setProperty(key, otherProperties.value(key));
+ }
+}
+
+ChannelClassSpec::ChannelClassSpec(const QString &channelType, HandleType targetHandleType,
+ bool requested, const QVariantMap &otherProperties)
+ : mPriv(new Private)
+{
+ setChannelType(channelType);
+ setTargetHandleType(targetHandleType);
+ setRequested(requested);
+ foreach (QString key, otherProperties.keys()) {
+ setProperty(key, otherProperties.value(key));
+ }
+}
+
+ChannelClassSpec::ChannelClassSpec(const ChannelClassSpec &other,
+ const QVariantMap &additionalProperties)
+ : mPriv(other.mPriv)
+{
+ if (!additionalProperties.isEmpty()) {
+ foreach (QString key, additionalProperties.keys()) {
+ setProperty(key, additionalProperties.value(key));
+ }
+ }
+}
+
+ChannelClassSpec::~ChannelClassSpec()
+{
+}
+
+bool ChannelClassSpec::isValid() const
+{
+ return mPriv.constData() != 0 &&
+ !(qdbus_cast<QString>(
+ mPriv->props.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")))
+ .isEmpty()) &&
+ mPriv->props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"));
+}
+
+ChannelClassSpec &ChannelClassSpec::operator=(const ChannelClassSpec &other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool ChannelClassSpec::isSubsetOf(const ChannelClassSpec &other) const
+{
+ if (!mPriv) {
+ // Invalid instances have no properties - hence they're subset of anything
+ return true;
+ }
+
+ foreach (QString propName, mPriv->props.keys()) {
+ if (!other.hasProperty(propName)) {
+ return false;
+ } else if (property(propName) != other.property(propName)) {
+ return false;
+ }
+ }
+
+ // other had all of the properties we have and they all had the same values
+
+ return true;
+}
+
+bool ChannelClassSpec::matches(const QVariantMap &immutableProperties) const
+{
+ // We construct a ChannelClassSpec for comparison so the StreamedMedia props are normalized
+ // consistently etc
+ return this->isSubsetOf(ChannelClassSpec(immutableProperties));
+}
+
+bool ChannelClassSpec::hasProperty(const QString &qualifiedName) const
+{
+ return mPriv.constData() != 0 ? mPriv->props.contains(qualifiedName) : false;
+}
+
+QVariant ChannelClassSpec::property(const QString &qualifiedName) const
+{
+ return mPriv.constData() != 0 ? mPriv->props.value(qualifiedName) : QVariant();
+}
+
+void ChannelClassSpec::setProperty(const QString &qualifiedName, const QVariant &value)
+{
+ if (mPriv.constData() == 0) {
+ mPriv = new Private;
+ }
+
+ mPriv->props.insert(qualifiedName, value);
+}
+
+void ChannelClassSpec::unsetProperty(const QString &qualifiedName)
+{
+ if (mPriv.constData() == 0) {
+ // No properties set for sure, so don't have to unset any
+ return;
+ }
+
+ mPriv->props.remove(qualifiedName);
+}
+
+QVariantMap ChannelClassSpec::allProperties() const
+{
+ return mPriv.constData() != 0 ? mPriv->props : QVariantMap();
+}
+
+ChannelClass ChannelClassSpec::bareClass() const
+{
+ ChannelClass cc;
+
+ if (!isValid()) {
+ warning() << "Tried to convert an invalid ChannelClassSpec to a ChannelClass";
+ return ChannelClass();
+ }
+
+ QVariantMap props = mPriv->props;
+ foreach (QString propName, props.keys()) {
+ QVariant value = props.value(propName);
+
+ cc.insert(propName, QDBusVariant(value));
+ }
+
+ return cc;
+}
+
+ChannelClassSpec ChannelClassSpec::textChat(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT),
+ HandleTypeContact);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::textChatroom(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT),
+ HandleTypeRoom);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::unnamedTextChat(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT),
+ HandleTypeNone);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::streamedMediaCall(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeContact);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::streamedMediaAudioCall(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeContact);
+ spec.setStreamedMediaInitialAudioFlag();
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::streamedMediaVideoCall(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeContact);
+ spec.setStreamedMediaInitialVideoFlag();
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::streamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeContact);
+ spec.setStreamedMediaInitialAudioFlag();
+ spec.setStreamedMediaInitialVideoFlag();
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaCall(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeNone);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaAudioCall(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeNone);
+ spec.setStreamedMediaInitialAudioFlag();
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaVideoCall(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeNone);
+ spec.setStreamedMediaInitialVideoFlag();
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA),
+ HandleTypeNone);
+ spec.setStreamedMediaInitialAudioFlag();
+ spec.setStreamedMediaInitialVideoFlag();
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::roomList(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_ROOM_LIST),
+ HandleTypeNone);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::outgoingFileTransfer(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER),
+ HandleTypeContact, true);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::incomingFileTransfer(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER),
+ HandleTypeContact, false);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::outgoingStreamTube(const QString &service,
+ const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE),
+ HandleTypeContact, true);
+ }
+
+ QVariantMap props = additionalProperties;
+ if (!service.isEmpty()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"),
+ service);
+ }
+
+ if (props.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, props);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::incomingStreamTube(const QString &service,
+ const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE),
+ HandleTypeContact, false);
+ }
+
+ QVariantMap props = additionalProperties;
+ if (!service.isEmpty()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"),
+ service);
+ }
+
+ if (props.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, props);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::outgoingRoomStreamTube(const QString &service,
+ const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE),
+ HandleTypeRoom, true);
+ }
+
+ QVariantMap props = additionalProperties;
+ if (!service.isEmpty()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"),
+ service);
+ }
+
+ if (props.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, props);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::incomingRoomStreamTube(const QString &service,
+ const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE),
+ HandleTypeRoom, false);
+ }
+
+ QVariantMap props = additionalProperties;
+ if (!service.isEmpty()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"),
+ service);
+ }
+
+ if (props.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, props);
+ }
+}
+
+ChannelClassSpec ChannelClassSpec::contactSearch(const QVariantMap &additionalProperties)
+{
+ static ChannelClassSpec spec;
+
+ if (!spec.mPriv.constData()) {
+ spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH),
+ HandleTypeNone);
+ }
+
+ if (additionalProperties.isEmpty()) {
+ return spec;
+ } else {
+ return ChannelClassSpec(spec, additionalProperties);
+ }
+}
+
+/**
+ * \class ChannelClassSpecList
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/channel-class-spec.h <TelepathyQt4/ChannelClassSpecList>
+ *
+ * \brief The ChannelClassSpecList class represents a list of ChannelClassSpec.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/channel-class-spec.h b/qt4/TelepathyQt4/channel-class-spec.h
new file mode 100644
index 000000000..5a9595524
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-class-spec.h
@@ -0,0 +1,277 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_class_spec_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_class_spec_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/Types>
+
+#include <QSharedDataPointer>
+#include <QVariant>
+#include <QVariantMap>
+#include <QPair>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT ChannelClassSpec
+{
+public:
+ ChannelClassSpec();
+ ChannelClassSpec(const ChannelClass &cc);
+ ChannelClassSpec(const QVariantMap &props);
+ ChannelClassSpec(const QString &channelType, HandleType targetHandleType,
+ const QVariantMap &otherProperties = QVariantMap());
+ ChannelClassSpec(const QString &channelType, HandleType targetHandleType, bool requested,
+ const QVariantMap &otherProperties = QVariantMap());
+ ChannelClassSpec(const ChannelClassSpec &other,
+ const QVariantMap &additionalProperties = QVariantMap());
+ ~ChannelClassSpec();
+
+ bool isValid() const;
+
+ ChannelClassSpec &operator=(const ChannelClassSpec &other);
+
+ bool operator==(const ChannelClassSpec &other) const
+ {
+ return this->allProperties() == other.allProperties();
+ }
+
+ bool isSubsetOf(const ChannelClassSpec &other) const;
+ bool matches(const QVariantMap &immutableProperties) const;
+
+ // TODO: Use new TP_QT4_... constants
+ QString channelType() const
+ {
+ return qdbus_cast<QString>(
+ property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")));
+ }
+
+ void setChannelType(const QString &type)
+ {
+ setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QVariant::fromValue(type));
+ }
+
+ HandleType targetHandleType() const
+ {
+ return (HandleType) qdbus_cast<uint>(
+ property(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")));
+ }
+
+ void setTargetHandleType(HandleType type)
+ {
+ setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ QVariant::fromValue((uint) type));
+ }
+
+ bool hasRequested() const
+ {
+ return hasProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"));
+ }
+
+ bool isRequested() const
+ {
+ return qdbus_cast<bool>(
+ property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")));
+ }
+
+ void setRequested(bool requested)
+ {
+ setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"),
+ QVariant::fromValue(requested));
+ }
+
+ void unsetRequested()
+ {
+ unsetProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"));
+ }
+
+ bool hasStreamedMediaInitialAudioFlag() const
+ {
+ return qdbus_cast<bool>(
+ property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
+ ".InitialAudio")));
+ }
+
+ void setStreamedMediaInitialAudioFlag()
+ {
+ setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"),
+ QVariant::fromValue(true));
+ }
+
+ void unsetStreamedMediaInitialAudioFlag()
+ {
+ unsetProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
+ ".InitialAudio"));
+ }
+
+ bool hasStreamedMediaInitialVideoFlag() const
+ {
+ return qdbus_cast<bool>(
+ property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
+ ".InitialVideo")));
+ }
+
+ void setStreamedMediaInitialVideoFlag()
+ {
+ setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"),
+ QVariant::fromValue(true));
+ }
+
+ void unsetStreamedMediaInitialVideoFlag()
+ {
+ unsetProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
+ ".InitialVideo"));
+ }
+
+ bool hasProperty(const QString &qualifiedName) const;
+ QVariant property(const QString &qualifiedName) const;
+
+ void setProperty(const QString &qualifiedName, const QVariant &value);
+ void unsetProperty(const QString &qualifiedName);
+
+ QVariantMap allProperties() const;
+ ChannelClass bareClass() const;
+
+ static ChannelClassSpec textChat(const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec textChatroom(const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec unnamedTextChat(const QVariantMap &additionalProperties = QVariantMap());
+
+ static ChannelClassSpec streamedMediaCall(const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec streamedMediaAudioCall(const QVariantMap &additionalProperties =
+ QVariantMap());
+ static ChannelClassSpec streamedMediaVideoCall(const QVariantMap &additionalProperties =
+ QVariantMap());
+ static ChannelClassSpec streamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties =
+ QVariantMap());
+
+ static ChannelClassSpec unnamedStreamedMediaCall(const QVariantMap &additionalProperties =
+ QVariantMap());
+ static ChannelClassSpec unnamedStreamedMediaAudioCall(const QVariantMap &additionalProperties =
+ QVariantMap());
+ static ChannelClassSpec unnamedStreamedMediaVideoCall(const QVariantMap &additionalProperties =
+ QVariantMap());
+ static ChannelClassSpec unnamedStreamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties =
+ QVariantMap());
+
+ // TODO: add Call when it's undrafted
+ static ChannelClassSpec roomList(const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec outgoingFileTransfer(const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec incomingFileTransfer(const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec outgoingStreamTube(const QString &service = QString(),
+ const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec incomingStreamTube(const QString &service = QString(),
+ const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec outgoingRoomStreamTube(const QString &service = QString(),
+ const QVariantMap &additionalProperties = QVariantMap());
+ static ChannelClassSpec incomingRoomStreamTube(const QString &service = QString(),
+ const QVariantMap &additionalProperties = QVariantMap());
+ // TODO: add dbus tubes when they're implemented
+ static ChannelClassSpec contactSearch(const QVariantMap &additionalProperties = QVariantMap());
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT ChannelClassSpecList :
+ public QList<ChannelClassSpec>
+{
+public:
+ ChannelClassSpecList() { }
+
+ ChannelClassSpecList(const ChannelClassSpec &spec)
+ {
+ append(spec);
+ }
+
+ ChannelClassSpecList(const QList<ChannelClassSpec> &other)
+ : QList<ChannelClassSpec>(other)
+ {
+ }
+
+ ChannelClassSpecList(const ChannelClassList &classes)
+ {
+ // Why doesn't Qt have range constructors like STL... stupid, so stupid.
+ Q_FOREACH (const ChannelClass &cc, classes) {
+ append(cc);
+ }
+ }
+
+ ChannelClassList bareClasses() const
+ {
+ ChannelClassList list;
+ Q_FOREACH (const ChannelClassSpec &spec, *this) {
+ list.append(spec.bareClass());
+ }
+ return list;
+ }
+};
+
+inline uint qHash(const ChannelClassSpec &spec)
+{
+ uint ret = 0;
+ QVariantMap::const_iterator it = spec.allProperties().constBegin();
+ QVariantMap::const_iterator end = spec.allProperties().constEnd();
+ int i = spec.allProperties().size() + 1;
+ for (; it != end; ++it) {
+ // all D-Bus types should be convertible to QString
+ QPair<QString, QString> p(it.key(), it.value().toString());
+ int h = qHash(p);
+ ret ^= ((h << (2 << i)) | (h >> (2 >> i)));
+ i--;
+ }
+ return ret;
+}
+
+inline uint qHash(const QSet<ChannelClassSpec> &specSet)
+{
+ int ret = 0;
+ Q_FOREACH (const ChannelClassSpec &spec, specSet) {
+ int h = qHash(spec);
+ ret ^= h;
+ }
+ return ret;
+}
+
+inline uint qHash(const ChannelClassSpecList &specList)
+{
+ // Make it unique by converting to QSet
+ QSet<ChannelClassSpec> uniqueSet = specList.toSet();
+ return qHash(uniqueSet);
+}
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ChannelClassSpec);
+Q_DECLARE_METATYPE(Tp::ChannelClassSpecList);
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-dispatch-operation-internal.h b/qt4/TelepathyQt4/channel-dispatch-operation-internal.h
new file mode 100644
index 000000000..7b29a2b6b
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatch-operation-internal.h
@@ -0,0 +1,51 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_dispatch_operation_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_dispatch_operation_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/PendingOperation>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT ChannelDispatchOperation::PendingClaim : public PendingOperation
+{
+ Q_OBJECT
+
+public:
+ PendingClaim(const ChannelDispatchOperationPtr &op,
+ const AbstractClientHandlerPtr &handler = AbstractClientHandlerPtr());
+ ~PendingClaim();
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onClaimFinished(Tp::PendingOperation *op);
+
+private:
+ ChannelDispatchOperationPtr mDispatchOp;
+ AbstractClientHandlerPtr mHandler;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-dispatch-operation.cpp b/qt4/TelepathyQt4/channel-dispatch-operation.cpp
new file mode 100644
index 000000000..3ba17f3ed
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatch-operation.cpp
@@ -0,0 +1,623 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ChannelDispatchOperation>
+#include "TelepathyQt4/channel-dispatch-operation-internal.h"
+
+#include "TelepathyQt4/_gen/cli-channel-dispatch-operation-body.hpp"
+#include "TelepathyQt4/_gen/cli-channel-dispatch-operation.moc.hpp"
+#include "TelepathyQt4/_gen/channel-dispatch-operation.moc.hpp"
+#include "TelepathyQt4/_gen/channel-dispatch-operation-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/fake-handler-manager-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingVoid>
+
+#include <QPointer>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ChannelDispatchOperation::Private
+{
+ Private(ChannelDispatchOperation *parent);
+ ~Private();
+
+ static void introspectMain(Private *self);
+
+ void extractMainProps(const QVariantMap &props,
+ bool immutableProperties);
+
+ // Public object
+ ChannelDispatchOperation *parent;
+
+ // Context
+ AccountFactoryConstPtr accFactory;
+ ConnectionFactoryConstPtr connFactory;
+ ChannelFactoryConstPtr chanFactory;
+ ContactFactoryConstPtr contactFactory;
+
+ // Instance of generated interface class
+ Client::ChannelDispatchOperationInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ QVariantMap immutableProperties;
+ ConnectionPtr connection;
+ AccountPtr account;
+ QList<ChannelPtr> channels;
+ QStringList possibleHandlers;
+ bool gotPossibleHandlers;
+};
+
+ChannelDispatchOperation::Private::Private(ChannelDispatchOperation *parent)
+ : parent(parent),
+ baseInterface(new Client::ChannelDispatchOperationInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ gotPossibleHandlers(false)
+{
+ debug() << "Creating new ChannelDispatchOperation:" << parent->objectPath();
+
+ parent->connect(baseInterface,
+ SIGNAL(Finished()),
+ SLOT(onFinished()));
+
+ parent->connect(baseInterface,
+ SIGNAL(ChannelLost(QDBusObjectPath,QString,QString)),
+ SLOT(onChannelLost(QDBusObjectPath,QString,QString)));
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As ChannelDispatchOperation does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+ChannelDispatchOperation::Private::~Private()
+{
+}
+
+void ChannelDispatchOperation::Private::introspectMain(ChannelDispatchOperation::Private *self)
+{
+ QVariantMap mainProps;
+ foreach (QString key, self->immutableProperties.keys()) {
+ if (key.startsWith(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION "."))) {
+ QVariant value = self->immutableProperties.value(key);
+ mainProps.insert(
+ key.remove(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".")),
+ value);
+ }
+ }
+
+ if (!self->channels.isEmpty() && mainProps.contains(QLatin1String("Account"))
+ && mainProps.contains(QLatin1String("Connection"))
+ && mainProps.contains(QLatin1String("Interfaces"))
+ && mainProps.contains(QLatin1String("PossibleHandlers"))) {
+ debug() << "Supplied properties were sufficient, not introspecting"
+ << self->parent->objectPath();
+ self->extractMainProps(mainProps, true);
+ return;
+ }
+
+ debug() << "Calling Properties::GetAll(ChannelDispatchOperation)";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION)),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+}
+
+void ChannelDispatchOperation::Private::extractMainProps(const QVariantMap &props,
+ bool immutableProperties)
+{
+ parent->setInterfaces(qdbus_cast<QStringList>(props.value(QLatin1String("Interfaces"))));
+
+ QList<PendingOperation *> readyOps;
+
+ if (!connection && props.contains(QLatin1String("Connection"))) {
+ QDBusObjectPath connectionObjectPath =
+ qdbus_cast<QDBusObjectPath>(props.value(QLatin1String("Connection")));
+ QString connectionBusName =
+ connectionObjectPath.path().mid(1).replace(QLatin1String("/"),
+ QLatin1String("."));
+
+ PendingReady *readyOp =
+ connFactory->proxy(connectionBusName, connectionObjectPath.path(),
+ chanFactory, contactFactory);
+ connection = ConnectionPtr::qObjectCast(readyOp->proxy());
+ readyOps.append(readyOp);
+ }
+
+ if (!account && props.contains(QLatin1String("Account"))) {
+ QDBusObjectPath accountObjectPath =
+ qdbus_cast<QDBusObjectPath>(props.value(QLatin1String("Account")));
+
+ PendingReady *readyOp =
+ accFactory->proxy(QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME),
+ accountObjectPath.path(), connFactory, chanFactory, contactFactory);
+ account = AccountPtr::qObjectCast(readyOp->proxy());
+ readyOps.append(readyOp);
+ }
+
+ if (!immutableProperties) {
+ // If we're here, it means we had to introspect the object, and now for sure have the
+ // correct channels list, so let's overwrite the initial channels - but keep the refs around
+ // for a while as an optimization enabling the factory to still return the same ones instead
+ // of constructing everything anew. Note that this is not done at all in the case the
+ // immutable props and initial channels etc were sufficient.
+ QList<ChannelPtr> saveChannels = channels;
+ channels.clear();
+
+ ChannelDetailsList channelDetailsList =
+ qdbus_cast<ChannelDetailsList>(props.value(QLatin1String("Channels")));
+ ChannelPtr channel;
+ foreach (const ChannelDetails &channelDetails, channelDetailsList) {
+ PendingReady *readyOp =
+ chanFactory->proxy(connection,
+ channelDetails.channel.path(), channelDetails.properties);
+ channels.append(ChannelPtr::qObjectCast(readyOp->proxy()));
+ readyOps.append(readyOp);
+ }
+
+ // saveChannels goes out of scope now, so any initial channels which don't exist anymore are
+ // freed
+ }
+
+ if (props.contains(QLatin1String("PossibleHandlers"))) {
+ possibleHandlers = qdbus_cast<QStringList>(props.value(QLatin1String("PossibleHandlers")));
+ gotPossibleHandlers = true;
+ }
+
+ if (readyOps.isEmpty()) {
+ debug() << "No proxies to prepare for CDO" << parent->objectPath();
+ readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ parent->connect(new PendingComposite(readyOps, ChannelDispatchOperationPtr(parent)),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onProxiesPrepared(Tp::PendingOperation*)));
+ }
+}
+
+ChannelDispatchOperation::PendingClaim::PendingClaim(const ChannelDispatchOperationPtr &op,
+ const AbstractClientHandlerPtr &handler)
+ : PendingOperation(op),
+ mDispatchOp(op),
+ mHandler(handler)
+{
+ debug() << "Invoking CDO.Claim";
+ connect(new PendingVoid(op->baseInterface()->Claim(), op),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onClaimFinished(Tp::PendingOperation*)));
+}
+
+ChannelDispatchOperation::PendingClaim::~PendingClaim()
+{
+}
+
+void ChannelDispatchOperation::PendingClaim::onClaimFinished(
+ PendingOperation *op)
+{
+ if (!op->isError()) {
+ debug() << "CDO.Claim returned successfully, updating HandledChannels";
+ if (mHandler) {
+ // register the channels in HandledChannels
+ FakeHandlerManager::instance()->registerChannels(
+ mDispatchOp->channels());
+ }
+ setFinished();
+ } else {
+ warning() << "CDO.Claim failed with" << op->errorName() << "-" << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ }
+}
+
+/**
+ * \class ChannelDispatchOperation
+ * \ingroup clientchanneldispatchoperation
+ * \headerfile TelepathyQt4/channel-dispatch-operation.h <TelepathyQt4/ChannelDispatchOperation>
+ *
+ * \brief The ChannelDispatchOperation class represents a Telepathy channel
+ * dispatch operation.
+ *
+ * One of the channel dispatcher's functions is to offer incoming channels to
+ * Approver clients for approval. An approver should generally ask the user
+ * whether they want to participate in the requested communication channels
+ * (join the chat or chatroom, answer the call, accept the file transfer, or
+ * whatever is appropriate). A collection of channels offered in this way
+ * is represented by a ChannelDispatchOperation object.
+ *
+ * If the user wishes to accept the communication channels, the approver
+ * should call handleWith() to indicate the user's or approver's preferred
+ * handler for the channels (the empty string indicates no particular
+ * preference, and will cause any suitable handler to be used).
+ *
+ * If the user wishes to reject the communication channels, or if the user
+ * accepts the channels and the approver will handle them itself, the approver
+ * should call claim(). If the resulting PendingOperation succeeds, the approver
+ * immediately has control over the channels as their primary handler,
+ * and may do anything with them (in particular, it may close them in whatever
+ * way seems most appropriate).
+ *
+ * There are various situations in which the channel dispatch operation will
+ * be closed, causing the DBusProxy::invalidated() signal to be emitted. If this
+ * happens, the approver should stop prompting the user.
+ *
+ * Because all approvers are launched simultaneously, the user might respond
+ * to another approver; if this happens, the invalidated signal will be
+ * emitted with the error code #TP_QT4_ERROR_OBJECT_REMOVED.
+ *
+ * If a channel closes, the signal channelLost() is emitted. If all channels
+ * close, there is nothing more to dispatch, so the invalidated signal will be
+ * emitted with the error code #TP_QT4_ERROR_OBJECT_REMOVED.
+ *
+ * If the channel dispatcher crashes or exits, the invalidated
+ * signal will be emitted with the error code
+ * #TP_QT4_DBUS_ERROR_NAME_HAS_NO_OWNER. In a high-quality implementation,
+ * the dispatcher should be restarted, at which point it will create new
+ * channel dispatch operations for any undispatched channels, and the approver
+ * will be notified again.
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * ChannelDispatchOperation object usable.
+ *
+ * Note that this feature must be enabled in order to use most
+ * ChannelDispatchOperation methods.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature ChannelDispatchOperation::FeatureCore = Feature(QLatin1String(ChannelDispatchOperation::staticMetaObject.className()), 0, true);
+
+/**
+ * Create a new channel dispatch operation object using the given \a bus, the given factories and
+ * the given initial channels.
+ *
+ * \param bus QDBusConnection to use.
+ * \param objectPath The channel dispatch operation object path.
+ * \param immutableProperties The channel dispatch operation immutable properties.
+ * \param initialChannels The channels this CDO has initially (further tracking is done internally).
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ChannelDispatchOperationPtr object pointing to the newly created
+ * ChannelDispatchOperation object.
+ */
+ChannelDispatchOperationPtr ChannelDispatchOperation::create(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const QList<ChannelPtr> &initialChannels,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ChannelDispatchOperationPtr(new ChannelDispatchOperation(
+ bus, objectPath, immutableProperties, initialChannels, accountFactory,
+ connectionFactory, channelFactory, contactFactory));
+}
+
+/**
+ * Construct a new channel dispatch operation object using the given \a bus, the given factories and
+ * the given initial channels.
+ *
+ * \param bus QDBusConnection to use
+ * \param objectPath The channel dispatch operation object path.
+ * \param immutableProperties The channel dispatch operation immutable properties.
+ * \param initialChannels The channels this CDO has initially (further tracking is done internally).
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ */
+ChannelDispatchOperation::ChannelDispatchOperation(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const QList<ChannelPtr> &initialChannels,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : StatefulDBusProxy(bus,
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER),
+ objectPath, FeatureCore),
+ OptionalInterfaceFactory<ChannelDispatchOperation>(this),
+ mPriv(new Private(this))
+{
+ if (accountFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the account factory is not the proxy connection";
+ }
+
+ if (connectionFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the connection factory is not the proxy connection";
+ }
+
+ if (channelFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection";
+ }
+
+ mPriv->channels = initialChannels;
+
+ mPriv->accFactory = accountFactory;
+ mPriv->connFactory = connectionFactory;
+ mPriv->chanFactory = channelFactory;
+ mPriv->contactFactory = contactFactory;
+
+ mPriv->immutableProperties = immutableProperties;
+}
+
+/**
+ * Class destructor.
+ */
+ChannelDispatchOperation::~ChannelDispatchOperation()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection with which the channels for this dispatch
+ * operation are associated.
+ *
+ * This method requires ChannelDispatchOperation::FeatureCore to be ready.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr ChannelDispatchOperation::connection() const
+{
+ return mPriv->connection;
+}
+
+/**
+ * Return the account with which the connection and channels for this dispatch
+ * operation are associated.
+ *
+ * This method requires ChannelDispatchOperation::FeatureCore to be ready.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr ChannelDispatchOperation::account() const
+{
+ return mPriv->account;
+}
+
+/**
+ * Return the channels to be dispatched.
+ *
+ * This method requires ChannelDispatchOperation::FeatureCore to be ready.
+ *
+ * \return A list of pointers to Channel objects.
+ */
+QList<ChannelPtr> ChannelDispatchOperation::channels() const
+{
+ if (!isReady()) {
+ warning() << "ChannelDispatchOperation::channels called with channel "
+ "not ready";
+ }
+ return mPriv->channels;
+}
+
+/**
+ * Return the well known bus names (starting with
+ * org.freedesktop.Telepathy.Client.) of the possible Handlers for this
+ * dispatch operation channels with the preferred handlers first.
+ *
+ * As a result, approvers should use the first handler by default, unless they
+ * have a reason to do otherwise.
+ *
+ * This method requires ChannelDispatchOperation::FeatureCore to be ready.
+ *
+ * \return List of possible handlers names.
+ */
+QStringList ChannelDispatchOperation::possibleHandlers() const
+{
+ return mPriv->possibleHandlers;
+}
+
+/**
+ * Called by an approver to accept a channel bundle and request that the given
+ * handler be used to handle it.
+ *
+ * If successful, this method will cause the ChannelDispatchOperation object to
+ * disappear, emitting invalidated with error
+ * #TP_QT4_ERROR_OBJECT_REMOVED.
+ *
+ * However, this method may fail because the dispatch has already been completed
+ * and the object has already gone. If this occurs, it indicates that another
+ * approver has asked for the bundle to be handled by a particular handler. The
+ * approver must not attempt to interact with the channels further in this case,
+ * unless it is separately invoked as the handler.
+ *
+ * Approvers which are also channel handlers should use claim() instead of
+ * this method to request that they can handle a channel bundle themselves.
+ *
+ * \param handler The well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the channel handler that
+ * should handle the channel, or an empty string if
+ * the client has no preferred channel handler.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *ChannelDispatchOperation::handleWith(const QString &handler)
+{
+ return new PendingVoid(
+ mPriv->baseInterface->HandleWith(handler),
+ ChannelDispatchOperationPtr(this));
+}
+
+/**
+ * Called by an approver to claim channels for closing them.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *ChannelDispatchOperation::claim()
+{
+ return new PendingClaim(ChannelDispatchOperationPtr(this));
+}
+
+/**
+ * Called by an approver to claim channels for handling internally. If this
+ * method is called successfully, the \a handler becomes the
+ * handler for the channel, but does not have the
+ * AbstractClientHandler::handleChannels() method called on it.
+ *
+ * Approvers wishing to reject channels must call this method to claim ownership
+ * of them, and must not call requestClose() on the channels unless/until this
+ * method returns successfully.
+ *
+ * The channel dispatcher can't know how best to close arbitrary channel types,
+ * so it leaves it up to the approver to do so. For instance, for text channels
+ * it is necessary to acknowledge any messages that have already been displayed
+ * to the user first - ideally, the approver would display and then acknowledge
+ * the messages - or to call Channel::requestClose() if the destructive
+ * behaviour of that method is desired.
+ *
+ * Similarly, an approver for streamed media channels can close the channel with
+ * a reason (e.g. "busy") if desired. The channel dispatcher, which is designed
+ * to have no specific knowledge of particular channel types, can't do that.
+ *
+ * If successful, this method will cause the ChannelDispatchOperation object to
+ * disappear, emitting Finished, in the same way as for handleWith().
+ *
+ * This method may fail because the dispatch operation has already been
+ * completed. Again, see handleWith() for more details. The approver must not
+ * attempt to interact with the channels further in this case.
+ *
+ * \param handler The channel handler, that should remain registered during the
+ * lifetime of channels(), otherwise dispatching will fail if the
+ * channel dispatcher restarts.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa claim(), handleWith()
+ */
+PendingOperation *ChannelDispatchOperation::claim(const AbstractClientHandlerPtr &handler)
+{
+ if (!handler->isRegistered()) {
+ return new PendingFailure(TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Handler must be registered for using claim(handler)"),
+ ChannelDispatchOperationPtr(this));
+ }
+
+ return new PendingClaim(ChannelDispatchOperationPtr(this),
+ handler);
+}
+
+/**
+ * \fn void ChannelDispatchOperation::channelLost(const ChannelPtr &channel,
+ * const QString &errorName, const QString &errorMessage);
+ *
+ * Emitted when a channel has closed before it could be claimed or handled. If this is
+ * emitted for the last remaining channel in a channel dispatch operation, it
+ * will immediately be followed by invalidated() with error
+ * #TP_QT4_ERROR_OBJECT_REMOVED.
+ *
+ * \param channel The channel that was closed.
+ * \param error The name of a D-Bus error indicating why the channel closed.
+ * \param errorMessage The error message.
+ */
+
+/**
+ * Return the ChannelDispatchOperationInterface for this ChannelDispatchOperation
+ * class. This method is protected since the convenience methods provided by
+ * this class should always be used instead of the interface by users of the
+ * class.
+ *
+ * \return A pointer to the existing Client::ChannelDispatchOperationInterface object for this
+ * ChannelDispatchOperation object.
+ */
+Client::ChannelDispatchOperationInterface *ChannelDispatchOperation::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+void ChannelDispatchOperation::onFinished()
+{
+ debug() << "ChannelDispatchOperation finished and was removed";
+ invalidate(QLatin1String(TELEPATHY_QT4_ERROR_OBJECT_REMOVED),
+ QLatin1String("ChannelDispatchOperation finished and was removed"));
+}
+
+void ChannelDispatchOperation::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ // Watcher is NULL if we didn't have to introspect at all
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties::GetAll(ChannelDispatchOperation)";
+ mPriv->extractMainProps(reply.value(), false);
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore,
+ false, reply.error());
+ warning().nospace() << "Properties::GetAll(ChannelDispatchOperation) failed with "
+ << reply.error().name() << ": " << reply.error().message();
+ }
+}
+
+void ChannelDispatchOperation::onChannelLost(
+ const QDBusObjectPath &channelObjectPath,
+ const QString &errorName, const QString &errorMessage)
+{
+ foreach (const ChannelPtr &channel, mPriv->channels) {
+ if (channel->objectPath() == channelObjectPath.path()) {
+ emit channelLost(channel, errorName, errorMessage);
+ mPriv->channels.removeOne(channel);
+ return;
+ }
+ }
+}
+
+void ChannelDispatchOperation::onProxiesPrepared(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Preparing proxies for CDO" << objectPath() << "failed with"
+ << op->errorName() << ":" << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false);
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/channel-dispatch-operation.h b/qt4/TelepathyQt4/channel-dispatch-operation.h
new file mode 100644
index 000000000..249bff4d9
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatch-operation.h
@@ -0,0 +1,114 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_dispatch_operation_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_dispatch_operation_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-channel-dispatch-operation.h>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/SharedPtr>
+
+#include <QString>
+#include <QStringList>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT ChannelDispatchOperation : public StatefulDBusProxy,
+ public OptionalInterfaceFactory<ChannelDispatchOperation>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ChannelDispatchOperation)
+
+public:
+ static const Feature FeatureCore;
+
+ static ChannelDispatchOperationPtr create(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const QList<ChannelPtr> &initialChannels,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ virtual ~ChannelDispatchOperation();
+
+ ConnectionPtr connection() const;
+
+ AccountPtr account() const;
+
+ QList<ChannelPtr> channels() const;
+
+ QStringList possibleHandlers() const;
+
+ PendingOperation *handleWith(const QString &handler);
+
+ PendingOperation *claim();
+ PendingOperation *claim(const AbstractClientHandlerPtr &handler);
+
+Q_SIGNALS:
+ void channelLost(const Tp::ChannelPtr &channel, const QString &errorName,
+ const QString &errorMessage);
+
+protected:
+ ChannelDispatchOperation(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const QList<ChannelPtr> &initialChannels,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+
+ Client::ChannelDispatchOperationInterface *baseInterface() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onFinished();
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onChannelLost(const QDBusObjectPath &channelObjectPath,
+ const QString &errorName, const QString &errorMessage);
+ TELEPATHY_QT4_NO_EXPORT void onProxiesPrepared(Tp::PendingOperation *op);
+
+private:
+ class PendingClaim;
+ friend class PendingClaim;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-dispatch-operation.xml b/qt4/TelepathyQt4/channel-dispatch-operation.xml
new file mode 100644
index 000000000..d7a6cdda1
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatch-operation.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel Dispatch Operation interface</tp:title>
+
+<xi:include href="../spec/Channel_Dispatch_Operation.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/channel-dispatcher.cpp b/qt4/TelepathyQt4/channel-dispatcher.cpp
new file mode 100644
index 000000000..6e034410d
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatcher.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ChannelDispatcher>
+
+#include "TelepathyQt4/_gen/cli-channel-dispatcher-body.hpp"
+#include "TelepathyQt4/_gen/cli-channel-dispatcher.moc.hpp"
diff --git a/qt4/TelepathyQt4/channel-dispatcher.h b/qt4/TelepathyQt4/channel-dispatcher.h
new file mode 100644
index 000000000..2afa78bd8
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatcher.h
@@ -0,0 +1,32 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_dispatcher_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_dispatcher_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-channel-dispatcher.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-dispatcher.xml b/qt4/TelepathyQt4/channel-dispatcher.xml
new file mode 100644
index 000000000..a1588a426
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-dispatcher.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel Dispatcher interface</tp:title>
+
+<xi:include href="../spec/Channel_Dispatcher.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/channel-factory.cpp b/qt4/TelepathyQt4/channel-factory.cpp
new file mode 100644
index 000000000..2c163f6d6
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-factory.cpp
@@ -0,0 +1,529 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ChannelFactory>
+
+#include "TelepathyQt4/_gen/channel-factory.moc.hpp"
+
+#include "TelepathyQt4/_gen/future-constants.h"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassFeatures>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/ContactSearchChannel>
+#include <TelepathyQt4/FileTransferChannel>
+#include <TelepathyQt4/IncomingFileTransferChannel>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/RoomListChannel>
+#include <TelepathyQt4/StreamTubeChannel>
+#include <TelepathyQt4/StreamedMediaChannel>
+#include <TelepathyQt4/TextChannel>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ChannelFactory::Private
+{
+ Private();
+
+ QList<ChannelClassFeatures> features;
+
+ typedef QPair<ChannelClassSpec, ConstructorConstPtr> CtorPair;
+ QList<CtorPair> ctors;
+};
+
+ChannelFactory::Private::Private()
+{
+}
+
+/**
+ * \class ChannelFactory
+ * \ingroup utils
+ * \headerfile TelepathyQt4/channel-factory.h <TelepathyQt4/ChannelFactory>
+ *
+ * \brief The ChannelFactory class is responsible for constructing Channel
+ * objects according to application-defined settings.
+ */
+
+/**
+ * Create a new ChannelFactory object.
+ *
+ * The returned factory will construct channel subclasses provided by TelepathyQt4 as appropriate
+ * for the channel immutable properties, but not make any features ready.
+ *
+ * \param bus The QDBusConnection the proxies constructed using this factory should use.
+ * \return An ChannelFactoryPtr pointing to the newly created factory.
+ */
+ChannelFactoryPtr ChannelFactory::create(const QDBusConnection &bus)
+{
+ return ChannelFactoryPtr(new ChannelFactory(bus));
+}
+
+/**
+ * Construct a new ChannelFactory object.
+ *
+ * The constructed factory will construct channel subclasses provided by TelepathyQt4 as appropriate
+ * for the channel immutable properties, but not make any features ready.
+ *
+ * \param bus The QDBusConnection the proxies constructed using this factory should use.
+ */
+ChannelFactory::ChannelFactory(const QDBusConnection &bus)
+ : DBusProxyFactory(bus), mPriv(new Private)
+{
+ setSubclassForTextChats<TextChannel>();
+ setSubclassForTextChatrooms<TextChannel>();
+ setSubclassForStreamedMediaCalls<StreamedMediaChannel>();
+ setSubclassForRoomLists<RoomListChannel>();
+ setSubclassForIncomingFileTransfers<IncomingFileTransferChannel>();
+ setSubclassForOutgoingFileTransfers<OutgoingFileTransferChannel>();
+ setSubclassForIncomingStreamTubes<IncomingStreamTubeChannel>();
+ setSubclassForOutgoingStreamTubes<OutgoingStreamTubeChannel>();
+ setSubclassForIncomingRoomStreamTubes<IncomingStreamTubeChannel>();
+ setSubclassForOutgoingRoomStreamTubes<OutgoingStreamTubeChannel>();
+ setSubclassForContactSearches<ContactSearchChannel>();
+ setFallbackSubclass<Channel>();
+}
+
+/**
+ * Class destructor.
+ */
+ChannelFactory::~ChannelFactory()
+{
+ delete mPriv;
+}
+
+Features ChannelFactory::featuresForTextChats(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::textChat(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForTextChats(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::textChat(additionalProps), features);
+ addFeaturesFor(ChannelClassSpec::unnamedTextChat(additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForTextChats(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::textChat(additionalProps));
+}
+
+void ChannelFactory::setConstructorForTextChats(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::textChat(additionalProps), ctor);
+ setConstructorFor(ChannelClassSpec::unnamedTextChat(additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForTextChatrooms(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::textChatroom(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForTextChatrooms(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::textChatroom(additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForTextChatrooms(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::textChatroom(additionalProps));
+}
+
+void ChannelFactory::setConstructorForTextChatrooms(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::textChatroom(additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForStreamedMediaCalls(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::streamedMediaCall(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForStreamedMediaCalls(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ ChannelClassSpec smSpec = ChannelClassSpec::streamedMediaCall(additionalProps);
+ ChannelClassSpec unnamedSMSpec = ChannelClassSpec::unnamedStreamedMediaCall(additionalProps);
+
+ addFeaturesFor(smSpec, features);
+ addFeaturesFor(unnamedSMSpec, features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForStreamedMediaCalls(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::streamedMediaCall(additionalProps));
+}
+
+void ChannelFactory::setConstructorForStreamedMediaCalls(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ ChannelClassSpec smSpec = ChannelClassSpec::streamedMediaCall(additionalProps);
+ ChannelClassSpec unnamedSMSpec = ChannelClassSpec::unnamedStreamedMediaCall(additionalProps);
+
+ setConstructorFor(smSpec, ctor);
+ setConstructorFor(unnamedSMSpec, ctor);
+}
+
+Features ChannelFactory::featuresForRoomLists(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::roomList(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForRoomLists(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::roomList(additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForRoomLists(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::roomList(additionalProps));
+}
+
+void ChannelFactory::setConstructorForRoomLists(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::roomList(additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForOutgoingFileTransfers(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::outgoingFileTransfer(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForOutgoingFileTransfers(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::outgoingFileTransfer(additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForOutgoingFileTransfers(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::outgoingFileTransfer(additionalProps));
+}
+
+void ChannelFactory::setConstructorForOutgoingFileTransfers(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::outgoingFileTransfer(additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForIncomingFileTransfers(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::incomingFileTransfer(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForIncomingFileTransfers(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::incomingFileTransfer(additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForIncomingFileTransfers(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::incomingFileTransfer(additionalProps));
+}
+
+void ChannelFactory::setConstructorForIncomingFileTransfers(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::incomingFileTransfer(additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForOutgoingStreamTubes(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::addFeaturesForOutgoingStreamTubes(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForOutgoingStreamTubes(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::setConstructorForOutgoingStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForIncomingStreamTubes(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::addFeaturesForIncomingStreamTubes(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForIncomingStreamTubes(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::setConstructorForIncomingStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForOutgoingRoomStreamTubes(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::addFeaturesForOutgoingRoomStreamTubes(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForOutgoingRoomStreamTubes(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::setConstructorForOutgoingRoomStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForIncomingRoomStreamTubes(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::addFeaturesForIncomingRoomStreamTubes(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForIncomingRoomStreamTubes(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps));
+}
+
+void ChannelFactory::setConstructorForIncomingRoomStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps), ctor);
+}
+
+Features ChannelFactory::featuresForContactSearches(const QVariantMap &additionalProps) const
+{
+ return featuresFor(ChannelClassSpec::contactSearch(additionalProps));
+}
+
+void ChannelFactory::addFeaturesForContactSearches(const Features &features,
+ const QVariantMap &additionalProps)
+{
+ addFeaturesFor(ChannelClassSpec::contactSearch(additionalProps), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForContactSearches(
+ const QVariantMap &additionalProps) const
+{
+ return constructorFor(ChannelClassSpec::contactSearch(additionalProps));
+}
+
+void ChannelFactory::setConstructorForContactSearches(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps)
+{
+ setConstructorFor(ChannelClassSpec::contactSearch(additionalProps), ctor);
+}
+
+Features ChannelFactory::commonFeatures() const
+{
+ return featuresFor(ChannelClassSpec());
+}
+
+void ChannelFactory::addCommonFeatures(const Features &features)
+{
+ addFeaturesFor(ChannelClassSpec(), features);
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::fallbackConstructor() const
+{
+ return constructorFor(ChannelClassSpec());
+}
+
+void ChannelFactory::setFallbackConstructor(const ConstructorConstPtr &ctor)
+{
+ setConstructorFor(ChannelClassSpec(), ctor);
+}
+
+Features ChannelFactory::featuresFor(const ChannelClassSpec &channelClass) const
+{
+ Features features;
+
+ foreach (const ChannelClassFeatures &pair, mPriv->features) {
+ if (pair.first.isSubsetOf(channelClass)) {
+ features.unite(pair.second);
+ }
+ }
+
+ return features;
+}
+
+void ChannelFactory::addFeaturesFor(const ChannelClassSpec &channelClass, const Features &features)
+{
+ QList<ChannelClassFeatures>::iterator i;
+ for (i = mPriv->features.begin(); i != mPriv->features.end(); ++i) {
+ if (channelClass.allProperties().size() > i->first.allProperties().size()) {
+ break;
+ }
+
+ if (i->first == channelClass) {
+ i->second.unite(features);
+ return;
+ }
+ }
+
+ // We ran out of feature specifications (for the given size/specificity of a channel class)
+ // before finding a matching one, so let's create a new entry
+ mPriv->features.insert(i, qMakePair(channelClass, features));
+}
+
+ChannelFactory::ConstructorConstPtr ChannelFactory::constructorFor(const ChannelClassSpec &cc) const
+{
+ QList<Private::CtorPair>::iterator i;
+ for (i = mPriv->ctors.begin(); i != mPriv->ctors.end(); ++i) {
+ if (i->first.isSubsetOf(cc)) {
+ return i->second;
+ }
+ }
+
+ // If this is reached, we didn't have a proper fallback constructor
+ Q_ASSERT(false);
+ return ConstructorConstPtr();
+}
+
+void ChannelFactory::setConstructorFor(const ChannelClassSpec &channelClass,
+ const ConstructorConstPtr &ctor)
+{
+ if (ctor.isNull()) {
+ warning().nospace() << "Tried to set a NULL ctor for ChannelClass("
+ << channelClass.channelType() << ", " << channelClass.targetHandleType() << ", "
+ << channelClass.allProperties().size() << "props in total)";
+ return;
+ }
+
+ QList<Private::CtorPair>::iterator i;
+ for (i = mPriv->ctors.begin(); i != mPriv->ctors.end(); ++i) {
+ if (channelClass.allProperties().size() > i->first.allProperties().size()) {
+ break;
+ }
+
+ if (i->first == channelClass) {
+ i->second = ctor;
+ return;
+ }
+ }
+
+ // We ran out of constructors (for the given size/specificity of a channel class)
+ // before finding a matching one, so let's create a new entry
+ mPriv->ctors.insert(i, qMakePair(channelClass, ctor));
+}
+
+/**
+ * Constructs a Channel proxy and begins making it ready.
+ *
+ * If a valid proxy already exists in the factory cache for the given combination of \a busName and
+ * \a objectPath, it is returned instead. All newly created proxies are automatically cached until
+ * they're either DBusProxy::invalidated() or the last reference to them outside the factory has
+ * been dropped.
+ *
+ * The proxy can be accessed immediately after this function returns using PendingReady::proxy().
+ *
+ * \param connection Proxy for the owning connection of the channel.
+ * \param channelPath The object path of the channel.
+ * \param immutableProperties The immutable properties of the channel.
+ * \return A PendingReady operation with the proxy in PendingReady::proxy().
+ */
+PendingReady *ChannelFactory::proxy(const ConnectionPtr &connection, const QString &channelPath,
+ const QVariantMap &immutableProperties) const
+{
+ DBusProxyPtr proxy = cachedProxy(connection->busName(), channelPath);
+ if (proxy.isNull()) {
+ proxy = constructorFor(ChannelClassSpec(immutableProperties))->construct(connection,
+ channelPath, immutableProperties);
+ }
+
+ return nowHaveProxy(proxy);
+}
+
+/**
+ * Transforms well-known names to the corresponding unique names, as is appropriate for Channel
+ *
+ * \param uniqueOrWellKnown The name to transform.
+ * \return The unique name corresponding to \a uniqueOrWellKnown (which may be it itself).
+ */
+QString ChannelFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const
+{
+ return StatefulDBusProxy::uniqueNameFrom(dbusConnection(), uniqueOrWellKnown);
+}
+
+/**
+ * Return features as configured for the channel class given by the Channel::immutableProperties()
+ * of \a proxy.
+ *
+ * \param proxy The Channel proxy to determine the features for.
+ * \return A list of Feature objects.
+ */
+Features ChannelFactory::featuresFor(const DBusProxyPtr &proxy) const
+{
+ ChannelPtr chan = ChannelPtr::qObjectCast(proxy);
+ Q_ASSERT(!chan.isNull());
+
+ return featuresFor(ChannelClassSpec(chan->immutableProperties()));
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/channel-factory.h b/qt4/TelepathyQt4/channel-factory.h
new file mode 100644
index 000000000..fa98dae87
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-factory.h
@@ -0,0 +1,305 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/DBusProxyFactory>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+// For Q_DISABLE_COPY
+#include <QtGlobal>
+#include <QString>
+#include <QVariantMap>
+
+class QDBusConnection;
+
+namespace Tp
+{
+
+class ChannelClassSpec;
+
+class TELEPATHY_QT4_EXPORT ChannelFactory : public DBusProxyFactory
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ChannelFactory)
+
+public:
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+ struct TELEPATHY_QT4_EXPORT Constructor : public RefCounted
+ {
+ virtual ~Constructor() {}
+
+ virtual ChannelPtr construct(const ConnectionPtr &conn, const QString &objectPath,
+ const QVariantMap &immutableProperties) const = 0;
+ };
+ typedef SharedPtr<Constructor> ConstructorPtr;
+ typedef SharedPtr<const Constructor> ConstructorConstPtr;
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+ static ChannelFactoryPtr create(const QDBusConnection &bus);
+
+ virtual ~ChannelFactory();
+
+ Features featuresForTextChats(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForTextChats(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForTextChats(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForTextChats(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForTextChats(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForTextChats(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForTextChatrooms(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForTextChatrooms(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForTextChatrooms(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForTextChatrooms(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForTextChatrooms(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForTextChatrooms(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForStreamedMediaCalls(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForStreamedMediaCalls(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForStreamedMediaCalls(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForStreamedMediaCalls(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForStreamedMediaCalls(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForStreamedMediaCalls(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForRoomLists(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForRoomLists(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForRoomLists(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForRoomLists(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForRoomLists(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForRoomLists(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForOutgoingFileTransfers(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForOutgoingFileTransfers(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForOutgoingFileTransfers(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForOutgoingFileTransfers(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForOutgoingFileTransfers(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForOutgoingFileTransfers(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForIncomingFileTransfers(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForIncomingFileTransfers(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForIncomingFileTransfers(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForIncomingFileTransfers(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForIncomingFileTransfers(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForIncomingFileTransfers(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForOutgoingStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForOutgoingStreamTubes(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForOutgoingStreamTubes(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForOutgoingStreamTubes(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForOutgoingStreamTubes(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForOutgoingStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForIncomingStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForIncomingStreamTubes(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForIncomingStreamTubes(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForIncomingStreamTubes(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForIncomingStreamTubes(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForIncomingStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForOutgoingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForOutgoingRoomStreamTubes(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForOutgoingRoomStreamTubes(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForOutgoingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForOutgoingRoomStreamTubes(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForOutgoingRoomStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForIncomingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForIncomingRoomStreamTubes(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForIncomingRoomStreamTubes(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForIncomingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForIncomingRoomStreamTubes(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForIncomingRoomStreamTubes(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features featuresForContactSearches(const QVariantMap &additionalProps = QVariantMap()) const;
+ void addFeaturesForContactSearches(const Features &features,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ ConstructorConstPtr constructorForContactSearches(
+ const QVariantMap &additionalProps = QVariantMap()) const;
+
+ template<typename Subclass>
+ void setSubclassForContactSearches(const QVariantMap &additionalProps = QVariantMap())
+ {
+ setConstructorForContactSearches(SubclassCtor<Subclass>::create(), additionalProps);
+ }
+
+ void setConstructorForContactSearches(const ConstructorConstPtr &ctor,
+ const QVariantMap &additionalProps = QVariantMap());
+
+ Features commonFeatures() const;
+ void addCommonFeatures(const Features &features);
+
+ ConstructorConstPtr fallbackConstructor() const;
+
+ template <typename Subclass>
+ void setFallbackSubclass()
+ {
+ setFallbackConstructor(SubclassCtor<Subclass>::create());
+ }
+
+ void setFallbackConstructor(const ConstructorConstPtr &ctor);
+
+ Features featuresFor(const ChannelClassSpec &channelClass) const;
+ void addFeaturesFor(const ChannelClassSpec &channelClass, const Features &features);
+
+ template <typename Subclass>
+ void setSubclassFor(const ChannelClassSpec &channelClass)
+ {
+ setConstructorFor(channelClass, SubclassCtor<Subclass>::create());
+ }
+
+ ConstructorConstPtr constructorFor(const ChannelClassSpec &channelClass) const;
+ void setConstructorFor(const ChannelClassSpec &channelClass, const ConstructorConstPtr &ctor);
+
+ PendingReady *proxy(const ConnectionPtr &connection, const QString &channelPath,
+ const QVariantMap &immutableProperties) const;
+
+protected:
+ ChannelFactory(const QDBusConnection &bus);
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+ template <typename Subclass>
+ struct SubclassCtor : public Constructor
+ {
+ static ConstructorPtr create()
+ {
+ return ConstructorPtr(new SubclassCtor<Subclass>());
+ }
+
+ virtual ~SubclassCtor() {}
+
+ ChannelPtr construct(const ConnectionPtr &conn, const QString &objectPath,
+ const QVariantMap &immutableProperties) const
+ {
+ return Subclass::create(conn, objectPath, immutableProperties);
+ }
+ };
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+ virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const;
+ // Nothing we'd like to prepare()
+ virtual Features featuresFor(const DBusProxyPtr &proxy) const;
+
+private:
+ struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-internal.h b/qt4/TelepathyQt4/channel-internal.h
new file mode 100644
index 000000000..bd8009d5a
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-internal.h
@@ -0,0 +1,50 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/PendingOperation>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT Channel::PendingLeave : public PendingOperation
+{
+ Q_OBJECT
+
+public:
+ PendingLeave(const ChannelPtr &channel, const QString &message,
+ ChannelGroupChangeReason reason);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onChanInvalidated(Tp::DBusProxy *proxy);
+ TELEPATHY_QT4_NO_EXPORT void onRemoveFinished(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &,
+ const Tp::Contacts &);
+ TELEPATHY_QT4_NO_EXPORT void onCloseFinished(Tp::PendingOperation *);
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-request.cpp b/qt4/TelepathyQt4/channel-request.cpp
new file mode 100644
index 000000000..ca1074cee
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-request.cpp
@@ -0,0 +1,803 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ChannelRequest>
+
+#include "TelepathyQt4/_gen/cli-channel-request-body.hpp"
+#include "TelepathyQt4/_gen/cli-channel-request.moc.hpp"
+#include "TelepathyQt4/_gen/channel-request.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingVoid>
+
+#include <QDateTime>
+#include <QSharedData>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ChannelRequest::Private
+{
+ Private(ChannelRequest *parent, const QVariantMap &immutableProperties,
+ const AccountFactoryConstPtr &, const ConnectionFactoryConstPtr &,
+ const ChannelFactoryConstPtr &, const ContactFactoryConstPtr &);
+ ~Private();
+
+ static void introspectMain(Private *self);
+
+ // \param lastCall Is this the last call to extractMainProps ie. should actions that only must
+ // be done once be done in this call
+ void extractMainProps(const QVariantMap &props, bool lastCall);
+
+ // Public object
+ ChannelRequest *parent;
+
+ // Context
+ AccountFactoryConstPtr accFact;
+ ConnectionFactoryConstPtr connFact;
+ ChannelFactoryConstPtr chanFact;
+ ContactFactoryConstPtr contactFact;
+
+ // Instance of generated interface class
+ Client::ChannelRequestInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ QVariantMap immutableProperties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ AccountPtr account;
+ QDateTime userActionTime;
+ QString preferredHandler;
+ QualifiedPropertyValueMapList requests;
+ ChannelRequestHints hints;
+ bool propertiesDone;
+
+ bool gotSWC;
+ ChannelPtr chan;
+};
+
+ChannelRequest::Private::Private(ChannelRequest *parent,
+ const QVariantMap &immutableProperties,
+ const AccountFactoryConstPtr &accFact,
+ const ConnectionFactoryConstPtr &connFact,
+ const ChannelFactoryConstPtr &chanFact,
+ const ContactFactoryConstPtr &contactFact)
+ : parent(parent),
+ accFact(accFact),
+ connFact(connFact),
+ chanFact(chanFact),
+ contactFact(contactFact),
+ baseInterface(new Client::ChannelRequestInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ immutableProperties(immutableProperties),
+ readinessHelper(parent->readinessHelper()),
+ propertiesDone(false),
+ gotSWC(false)
+{
+ debug() << "Creating new ChannelRequest:" << parent->objectPath();
+
+ parent->connect(baseInterface,
+ SIGNAL(Failed(QString,QString)),
+ SIGNAL(failed(QString,QString)));
+ parent->connect(baseInterface,
+ SIGNAL(Succeeded()),
+ SLOT(onLegacySucceeded()));
+ parent->connect(baseInterface,
+ SIGNAL(SucceededWithChannel(QDBusObjectPath,QVariantMap,QDBusObjectPath,QVariantMap)),
+ SLOT(onSucceededWithChannel(QDBusObjectPath,QVariantMap,QDBusObjectPath,QVariantMap)));
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As ChannelRequest does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+
+ // For early access to the immutable properties through the friendly getters - will be called
+ // again with lastCall = true eventually, if/when becomeReady is called, though
+ QVariantMap mainProps;
+ foreach (QString key, immutableProperties.keys()) {
+ // The key.count thing is so that we don't match "org.fdo.Tp.CR.OptionalInterface.Prop" too
+ if (key.startsWith(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST "."))
+ && key.count(QLatin1Char('.')) ==
+ QString::fromAscii(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".").count(QLatin1Char('.'))) {
+ QVariant value = immutableProperties.value(key);
+ mainProps.insert(key.remove(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".")), value);
+ }
+ }
+ extractMainProps(mainProps, false);
+}
+
+ChannelRequest::Private::~Private()
+{
+}
+
+void ChannelRequest::Private::introspectMain(ChannelRequest::Private *self)
+{
+ QVariantMap props;
+ QString key;
+ bool needIntrospectMainProps = false;
+ const char *propertiesNames[] = { "Account", "UserActionTime",
+ "PreferredHandler", "Requests", "Interfaces",
+ NULL };
+ for (unsigned i = 0; propertiesNames[i] != NULL; ++i) {
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".");
+ key += QLatin1String(propertiesNames[i]);
+ if (!self->immutableProperties.contains(key)) {
+ needIntrospectMainProps = true;
+ break;
+ }
+ props.insert(QLatin1String(propertiesNames[i]),
+ self->immutableProperties[key]);
+ }
+
+ if (needIntrospectMainProps) {
+ debug() << "Calling Properties::GetAll(ChannelRequest)";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST)),
+ self->parent);
+ // FIXME: This is a Qt bug fixed upstream, should be in the next Qt release.
+ // We should not need to check watcher->isFinished() here, remove the
+ // check when a fixed Qt version is released.
+ if (watcher->isFinished()) {
+ self->parent->gotMainProperties(watcher);
+ } else {
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+ }
+ } else {
+ self->extractMainProps(props, true);
+ }
+}
+
+void ChannelRequest::Private::extractMainProps(const QVariantMap &props, bool lastCall)
+{
+ PendingReady *readyOp = 0;
+
+ if (props.contains(QLatin1String("Account"))) {
+ QDBusObjectPath accountObjectPath =
+ qdbus_cast<QDBusObjectPath>(props.value(QLatin1String("Account")));
+
+ if (!account.isNull()) {
+ if (accountObjectPath.path() == account->objectPath()) {
+ // Most often a no-op, but we want this to guarantee the old behavior in all cases
+ readyOp = account->becomeReady();
+ } else {
+ warning() << "The account" << accountObjectPath.path() << "was not the expected"
+ << account->objectPath() << "for CR" << parent->objectPath();
+ // Construct a new one instead
+ account.reset();
+ }
+ }
+
+ // We need to check again because we might have dropped the expected account just a sec ago
+ if (account.isNull() && !accountObjectPath.path().isEmpty()) {
+ if (!accFact.isNull()) {
+ readyOp = accFact->proxy(
+ QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME), accountObjectPath.path(),
+ connFact, chanFact, contactFact);
+ account = AccountPtr::qObjectCast(readyOp->proxy());
+ } else {
+ account = Account::create(
+ QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME),
+ accountObjectPath.path(), connFact, chanFact, contactFact);
+ readyOp = account->becomeReady();
+ }
+ }
+ }
+
+ // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690
+ uint stamp = (uint) qdbus_cast<qlonglong>(props.value(QLatin1String("UserActionTime")));
+ if (stamp != 0) {
+ userActionTime = QDateTime::fromTime_t(stamp);
+ }
+
+ preferredHandler = qdbus_cast<QString>(props.value(QLatin1String("PreferredHandler")));
+ requests = qdbus_cast<QualifiedPropertyValueMapList>(props.value(QLatin1String("Requests")));
+
+ parent->setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")]));
+ readinessHelper->setInterfaces(parent->interfaces());
+
+ if (props.contains(QLatin1String("Hints"))) {
+ hints = qdbus_cast<QVariantMap>(props.value(QLatin1String("Hints")));
+ }
+
+ if (lastCall) {
+ propertiesDone = true;
+ }
+
+ if (account) {
+ parent->connect(readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAccountReady(Tp::PendingOperation*)));
+ } else if (lastCall) {
+ warning() << "No account for ChannelRequest" << parent->objectPath();
+ readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+}
+
+/**
+ * \class ChannelRequest
+ * \ingroup clientchannelrequest
+ * \headerfile TelepathyQt4/channel-request.h <TelepathyQt4/ChannelRequest>
+ *
+ * \brief The ChannelRequest class represents a Telepathy channel request.
+ *
+ * A channel request is an object in the channel dispatcher representing an
+ * ongoing request for some channels to be created or found. There can be any
+ * number of channel request objects at the same time.
+ *
+ * A channel request can be cancelled by any client (not just the one that
+ * requested it). This means that the channel dispatcher will close the
+ * resulting channel, or refrain from requesting it at all, rather than
+ * dispatching it to a handler.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * ChannelRequest object usable.
+ *
+ * Note that this feature must be enabled in order to use most
+ * ChannelRequest methods.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature ChannelRequest::FeatureCore = Feature(QLatin1String(ChannelRequest::staticMetaObject.className()), 0, true);
+
+/**
+ * Create a new channel request object using the given \a bus and the given factories.
+ *
+ * \param objectPath The channel request object path.
+ * \param immutableProperties The channel request immutable properties.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest object.
+ */
+ChannelRequestPtr ChannelRequest::create(const QDBusConnection &bus, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ChannelRequestPtr(new ChannelRequest(bus, objectPath, immutableProperties,
+ accountFactory, connectionFactory, channelFactory, contactFactory));
+}
+
+/**
+ * Create a new channel request object for the given \a account.
+ *
+ * The returned instance will use factories from the account.
+ *
+ * \param account The account that the request was made through.
+ * \param objectPath The channel request object path.
+ * \param immutableProperties The channel request immutable properties.
+ * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest object.
+ */
+ChannelRequestPtr ChannelRequest::create(const AccountPtr &account, const QString &objectPath,
+ const QVariantMap &immutableProperties)
+{
+ return ChannelRequestPtr(new ChannelRequest(account, objectPath, immutableProperties));
+}
+
+/**
+ * Construct a new channel request object using the given \a bus and the given factories.
+ *
+ * \param bus QDBusConnection to use.
+ * \param objectPath The channel request object path.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \param immutableProperties The immutable properties of the channel request.
+ * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest.
+ */
+ChannelRequest::ChannelRequest(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : StatefulDBusProxy(bus,
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER),
+ objectPath, FeatureCore),
+ OptionalInterfaceFactory<ChannelRequest>(this),
+ mPriv(new Private(this, immutableProperties, accountFactory, connectionFactory,
+ channelFactory, contactFactory))
+{
+ if (accountFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the account factory is not the proxy connection";
+ }
+
+ if (connectionFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the connection factory is not the proxy connection";
+ }
+
+ if (channelFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection";
+ }
+}
+
+/**
+ * Construct a new channel request object using the given \a account.
+ *
+ * The constructed instance will use the factories from the account.
+ *
+ * \param account Account to use.
+ * \param objectPath The channel request object path.
+ * \param immutableProperties The immutable properties of the channel request.
+ * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest.
+ */
+ChannelRequest::ChannelRequest(const AccountPtr &account,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+ : StatefulDBusProxy(account->dbusConnection(),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER),
+ objectPath, FeatureCore),
+ OptionalInterfaceFactory<ChannelRequest>(this),
+ mPriv(new Private(this, immutableProperties, AccountFactoryPtr(),
+ account->connectionFactory(),
+ account->channelFactory(),
+ account->contactFactory()))
+{
+ mPriv->account = account;
+}
+
+/**
+ * Class destructor.
+ */
+ChannelRequest::~ChannelRequest()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account on which this request was made.
+ *
+ * This method can be used even before the ChannelRequest is ready, in which case the account object
+ * corresponding to the immutable properties is returned. In this case, the Account object is not
+ * necessarily ready either. This is useful for eg. matching ChannelRequests from
+ * ClientHandlerInterface::addRequest() with existing accounts in the application: either by object
+ * path, or if account factories are in use, even by object identity.
+ *
+ * If the account is not provided in the immutable properties, this will only return a non-\c NULL
+ * AccountPtr once ChannelRequest::FeatureCore is ready on this object.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr ChannelRequest::account() const
+{
+ return mPriv->account;
+}
+
+/**
+ * Return the time at which the user action occurred, or 0 if this channel
+ * request is for some reason not involving user action.
+ *
+ * Unix developers: this corresponds to the _NET_WM_USER_TIME property in EWMH.
+ *
+ * This property is set when the channel request is created, and can never
+ * change.
+ *
+ * This method can be used even before the ChannelRequest is ready: in this case, the user action
+ * time from the immutable properties, if any, is returned.
+ *
+ * \return The time at which the user action occurred as QDateTime.
+ */
+QDateTime ChannelRequest::userActionTime() const
+{
+ return mPriv->userActionTime;
+}
+
+/**
+ * Return either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred handler for this channel,
+ * or an empty string to indicate that any handler would be acceptable.
+ *
+ * This property is set when the channel request is created, and can never
+ * change.
+ *
+ * This method can be used even before the ChannelRequest is ready: in this case, the preferred
+ * handler from the immutable properties, if any, is returned.
+ *
+ * \return The preferred handler, or an empty string if any handler would be
+ * acceptable.
+ */
+QString ChannelRequest::preferredHandler() const
+{
+ return mPriv->preferredHandler;
+}
+
+/**
+ * Return the desirable properties for the channel or channels to be created, as specified when
+ * placing the request in the first place.
+ *
+ * This property is set when the channel request is created, and can never
+ * change.
+ *
+ * This method can be used even before the ChannelRequest is ready: in this case, the requested
+ * channel properties from the immutable properties, if any, are returned. This is useful for e.g.
+ * matching ChannelRequests from ClientHandlerInterface::addRequest() with existing requests in the
+ * application (by the target ID or handle, most likely).
+ *
+ * \return The requested desirable channel properties as a list of
+ * QualifiedPropertyValueMap objects.
+ */
+QualifiedPropertyValueMapList ChannelRequest::requests() const
+{
+ return mPriv->requests;
+}
+
+/**
+ * Return the dictionary of metadata provided by the channel requester when requesting the channel.
+ *
+ * This property is set when the channel request is created, and can never change.
+ *
+ * This method can be used even before the ChannelRequest is ready: in this case, the requested
+ * channel properties from the immutable properties, if any, are returned. This is useful for e.g.
+ * matching ChannelRequests from ClientHandlerInterface::addRequest() with existing requests in the
+ * application (by the target ID or handle, most likely).
+ *
+ * \sa Account::supportsRequestHints()
+ * \return The hints in the request as a ChannelRequestHints object, if any.
+ */
+ChannelRequestHints ChannelRequest::hints() const
+{
+ return mPriv->hints;
+}
+
+/**
+ * Return all of the immutable properties passed to this object when created.
+ *
+ * This is useful for e.g. getting to domain-specific properties of channel requests.
+ *
+ * \return The immutable properties as QVariantMap.
+ */
+QVariantMap ChannelRequest::immutableProperties() const
+{
+ QVariantMap props = mPriv->immutableProperties;
+
+ if (!account().isNull()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Account"),
+ QVariant::fromValue(QDBusObjectPath(account()->objectPath())));
+ }
+
+ if (userActionTime().isValid()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".UserActionTime"),
+ QVariant::fromValue(userActionTime().toTime_t()));
+ }
+
+ if (!preferredHandler().isNull()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".PreferredHandler"),
+ preferredHandler());
+ }
+
+ if (!requests().isEmpty()) {
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Requests"),
+ QVariant::fromValue(requests()));
+ }
+
+ props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Interfaces"),
+ QVariant::fromValue(interfaces()));
+
+ return props;
+}
+
+/**
+ * Cancel the channel request.
+ *
+ * If failed() is emitted in response to this method, the error will be
+ * #TP_QT4_ERROR_CANCELLED.
+ *
+ * If the channel has already been dispatched to a handler, then it's too late
+ * to call this method, and the channel request will no longer exist.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *ChannelRequest::cancel()
+{
+ return new PendingVoid(mPriv->baseInterface->Cancel(),
+ ChannelRequestPtr(this));
+}
+
+/**
+ * Return the Channel which this request succeeded with, if any.
+ *
+ * This will only ever be populated if Account::requestsSucceedWithChannel() is \c true, and
+ * succeeded() has already been emitted on this ChannelRequest. Note that a PendingChannelRequest
+ * being successfully finished already implies succeeded() has been emitted.
+ *
+ * \return A pointer to the Channel object, or a null ChannelPtr if there isn't any.
+ */
+ChannelPtr ChannelRequest::channel() const
+{
+ return mPriv->chan;
+}
+
+/**
+ * Proceed with the channel request.
+ *
+ * The client that created this object calls this method when it has connected
+ * signal handlers for succeeded() and failed(). Note that this is done
+ * automatically when using PendingChannelRequest.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *ChannelRequest::proceed()
+{
+ return new PendingVoid(mPriv->baseInterface->Proceed(),
+ ChannelRequestPtr(this));
+}
+
+/**
+ * Return the ChannelRequestInterface for this ChannelRequest class. This method is
+ * protected since the convenience methods provided by this class should
+ * always be used instead of the interface by users of the class.
+ *
+ * \return A pointer to the existing Client::ChannelRequestInterface object for this
+ * ChannelRequest object.
+ */
+Client::ChannelRequestInterface *ChannelRequest::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+void ChannelRequest::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties::GetAll(ChannelRequest)";
+ props = reply.value();
+
+ mPriv->extractMainProps(props, true);
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore,
+ false, reply.error());
+ warning().nospace() << "Properties::GetAll(ChannelRequest) failed with "
+ << reply.error().name() << ": " << reply.error().message();
+ }
+
+ watcher->deleteLater();
+}
+
+void ChannelRequest::onAccountReady(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Unable to make ChannelRequest.Account ready";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ op->errorName(), op->errorMessage());
+ return;
+ }
+
+ if (mPriv->propertiesDone && !isReady()) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+}
+
+void ChannelRequest::onLegacySucceeded()
+{
+ if (mPriv->gotSWC) {
+ return;
+ }
+
+ emit succeeded(); // API/ABI break TODO: don't
+ emit succeeded(ChannelPtr());
+}
+
+void ChannelRequest::onSucceededWithChannel(
+ const QDBusObjectPath &connPath,
+ const QVariantMap &connProps,
+ const QDBusObjectPath &chanPath,
+ const QVariantMap &chanProps)
+{
+ if (mPriv->gotSWC) {
+ warning().nospace() << "Got SucceededWithChannel again for CR(" << objectPath() << ")!";
+ return;
+ }
+
+ mPriv->gotSWC = true;
+
+ QList<PendingOperation *> readyOps;
+
+ QString connBusName = connPath.path().mid(1).replace(
+ QLatin1String("/"), QLatin1String("."));
+ PendingReady *connReady = mPriv->connFact->proxy(connBusName, connPath.path(),
+ mPriv->chanFact, mPriv->contactFact);
+ ConnectionPtr conn = ConnectionPtr::qObjectCast(connReady->proxy());
+ readyOps.append(connReady);
+
+ PendingReady *chanReady = mPriv->chanFact->proxy(conn, chanPath.path(), chanProps);
+ mPriv->chan = ChannelPtr::qObjectCast(chanReady->proxy());
+ readyOps.append(chanReady);
+
+ connect(new PendingComposite(readyOps, ChannelRequestPtr(this)),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChanBuilt(Tp::PendingOperation*)));
+}
+
+void ChannelRequest::onChanBuilt(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Failed to build Channel which the ChannelRequest succeeded with,"
+ << "succeeding with NULL channel:" << op->errorName() << ',' << op->errorMessage();
+ mPriv->chan.reset();
+ }
+
+ emit succeeded(); // API/ABI break TODO: don't
+ emit succeeded(mPriv->chan);
+}
+
+/**
+ * \fn void ChannelRequest::failed(const QString &errorName,
+ * const QString &errorMessage)
+ *
+ * Emitted when the channel request has failed. No further
+ * methods must not be called on it.
+ *
+ * \param errorName The name of a D-Bus error.
+ * \param errorMessage The error message.
+ * \sa succeeded()
+ */
+
+/**
+ * \fn void ChannelRequest::succeeded()
+ *
+ * \deprecated Use ChannelRequest::succeeded(const ChannelPtr &) instead.
+ */
+
+/**
+ * \fn void ChannelRequest::succeeded(const Tp::ChannelPtr &channel)
+ *
+ * Emitted when the channel request has succeeded. No further
+ * methods must not be called on it.
+ *
+ * The \a channel parameter can be used to observe the channel resulting from the request (e.g. for
+ * it getting closed). The pointer may be NULL if the Channel Dispatcher implementation is too old.
+ * Whether a non-NULL channel can be expected can be checked with
+ * Account::requestsSucceedWithChannel().
+ *
+ * If there is a channel, it will be of the subclass determined by and made ready (or not) according
+ * to the settings of the ChannelFactory on the Account the request was made through.
+ *
+ * \param channel Pointer to a proxy for the resulting channel, if the Channel Dispatcher reported it.
+ * \sa failed()
+ */
+
+void ChannelRequest::connectNotify(const char *signalName)
+{
+ if (qstrcmp(signalName, SIGNAL(succeeded())) == 0) {
+ warning() << "Connecting to deprecated signal ChannelRequest::succeeded()";
+ }
+}
+
+/**
+ * \class ChannelRequestHints
+ * \ingroup clientchannelrequest
+ * \headerfile TelepathyQt4/channel-request.h <TelepathyQt4/ChannelRequestHints>
+ *
+ * \brief The ChannelRequestHints class represents a dictionary of metadata
+ * provided by the channel requester when requesting a channel.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT ChannelRequestHints::Private : public QSharedData
+{
+ Private() {}
+ Private(const QVariantMap &hints) : hints(hints) {}
+
+ QVariantMap hints;
+};
+
+ChannelRequestHints::ChannelRequestHints()
+{
+}
+
+ChannelRequestHints::ChannelRequestHints(const QVariantMap &hints)
+ : mPriv(new Private(hints))
+{
+}
+
+ChannelRequestHints::ChannelRequestHints(const ChannelRequestHints &crh)
+ : mPriv(crh.mPriv)
+{
+}
+
+ChannelRequestHints::~ChannelRequestHints()
+{
+}
+
+ChannelRequestHints &ChannelRequestHints::operator=(const ChannelRequestHints &other)
+{
+ if (this == &other) {
+ return *this;
+ }
+
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool ChannelRequestHints::isValid() const
+{
+ return mPriv.constData() != 0;
+}
+
+bool ChannelRequestHints::hasHint(const QString &reversedDomain, const QString &localName) const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ const QString qualifiedName = reversedDomain + QLatin1Char('.') + localName;
+ return mPriv->hints.contains(qualifiedName);
+}
+
+QVariant ChannelRequestHints::hint(const QString &reversedDomain, const QString &localName) const
+{
+ if (!isValid()) {
+ return QVariant();
+ }
+
+ const QString qualifiedName = reversedDomain + QLatin1Char('.') + localName;
+ return mPriv->hints.value(qualifiedName);
+}
+
+void ChannelRequestHints::setHint(const QString &reversedDomain, const QString &localName, const QVariant &value)
+{
+ const QString qualifiedName = reversedDomain + QLatin1Char('.') + localName;
+
+ if (!isValid()) {
+ mPriv = new Private();
+ }
+
+ mPriv->hints.insert(qualifiedName, value);
+}
+
+QVariantMap ChannelRequestHints::allHints() const
+{
+ return isValid() ? mPriv->hints : QVariantMap();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/channel-request.h b/qt4/TelepathyQt4/channel-request.h
new file mode 100644
index 000000000..7018803f6
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-request.h
@@ -0,0 +1,154 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_request_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_request_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-channel-request.h>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/SharedPtr>
+
+#include <QSharedDataPointer>
+#include <QString>
+#include <QStringList>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class ChannelRequestHints;
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT ChannelRequest : public StatefulDBusProxy,
+ public OptionalInterfaceFactory<ChannelRequest>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ChannelRequest)
+
+public:
+ static const Feature FeatureCore;
+
+ static ChannelRequestPtr create(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+
+ static ChannelRequestPtr create(const AccountPtr &account,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~ChannelRequest();
+
+ AccountPtr account() const;
+ QDateTime userActionTime() const;
+ QString preferredHandler() const;
+ QualifiedPropertyValueMapList requests() const;
+ ChannelRequestHints hints() const;
+
+ QVariantMap immutableProperties() const;
+
+ PendingOperation *cancel();
+
+ ChannelPtr channel() const;
+
+Q_SIGNALS:
+ void failed(const QString &errorName, const QString &errorMessage);
+ void succeeded(); // TODO API/ABI break: remove
+ void succeeded(const Tp::ChannelPtr &channel);
+
+protected:
+ ChannelRequest(const QDBusConnection &bus,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+
+ ChannelRequest(const AccountPtr &account,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ Client::ChannelRequestInterface *baseInterface() const;
+
+protected:
+ // TODO: (API/ABI break) Remove connectNotify
+ void connectNotify(const char *);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onAccountReady(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onLegacySucceeded();
+ TELEPATHY_QT4_NO_EXPORT void onSucceededWithChannel(const QDBusObjectPath &connPath, const QVariantMap &connProps,
+ const QDBusObjectPath &chanPath, const QVariantMap &chanProps);
+ TELEPATHY_QT4_NO_EXPORT void onChanBuilt(Tp::PendingOperation *op);
+
+private:
+ friend class PendingChannelRequest;
+
+ PendingOperation *proceed();
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT ChannelRequestHints
+{
+public:
+ ChannelRequestHints();
+ ChannelRequestHints(const QVariantMap &hints);
+ ChannelRequestHints(const ChannelRequestHints &other);
+ ~ChannelRequestHints();
+
+ ChannelRequestHints &operator=(const ChannelRequestHints &other);
+
+ bool isValid() const;
+
+ bool hasHint(const QString &reversedDomain, const QString &localName) const;
+ QVariant hint(const QString &reversedDomain, const QString &localName) const;
+ void setHint(const QString &reversedDomain, const QString &localName, const QVariant &value);
+
+ QVariantMap allHints() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ChannelRequestHints);
+
+#endif
diff --git a/qt4/TelepathyQt4/channel-request.xml b/qt4/TelepathyQt4/channel-request.xml
new file mode 100644
index 000000000..92fc88172
--- /dev/null
+++ b/qt4/TelepathyQt4/channel-request.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel Request interface</tp:title>
+
+<xi:include href="../spec/Channel_Request.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/channel.cpp b/qt4/TelepathyQt4/channel.cpp
new file mode 100644
index 000000000..a35483613
--- /dev/null
+++ b/qt4/TelepathyQt4/channel.cpp
@@ -0,0 +1,3596 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Channel>
+#include "TelepathyQt4/channel-internal.h"
+
+#include "TelepathyQt4/_gen/cli-channel-body.hpp"
+#include "TelepathyQt4/_gen/cli-channel.moc.hpp"
+#include "TelepathyQt4/_gen/channel.moc.hpp"
+#include "TelepathyQt4/_gen/channel-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include "TelepathyQt4/future-internal.h"
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingSuccess>
+#include <TelepathyQt4/StreamTubeChannel>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/Constants>
+
+#include <QHash>
+#include <QQueue>
+#include <QSharedData>
+#include <QTimer>
+
+namespace Tp
+{
+
+using TpFuture::Client::ChannelInterfaceMergeableConferenceInterface;
+using TpFuture::Client::ChannelInterfaceSplittableInterface;
+
+struct TELEPATHY_QT4_NO_EXPORT Channel::Private
+{
+ Private(Channel *parent, const ConnectionPtr &connection,
+ const QVariantMap &immutableProperties);
+ ~Private();
+
+ static void introspectMain(Private *self);
+ void introspectMainProperties();
+ void introspectMainFallbackChannelType();
+ void introspectMainFallbackHandle();
+ void introspectMainFallbackInterfaces();
+ void introspectGroup();
+ void introspectGroupFallbackFlags();
+ void introspectGroupFallbackMembers();
+ void introspectGroupFallbackLocalPendingWithInfo();
+ void introspectGroupFallbackSelfHandle();
+ void introspectConference();
+
+ static void introspectConferenceInitialInviteeContacts(Private *self);
+
+ void continueIntrospection();
+
+ void extractMainProps(const QVariantMap &props);
+ void extract0176GroupProps(const QVariantMap &props);
+
+ void nowHaveInterfaces();
+ void nowHaveInitialMembers();
+
+ bool setGroupFlags(uint groupFlags);
+
+ void buildContacts();
+ void doMembersChangedDetailed(const UIntList &, const UIntList &, const UIntList &,
+ const UIntList &, const QVariantMap &);
+ void processMembersChanged();
+ void updateContacts(const QList<ContactPtr> &contacts =
+ QList<ContactPtr>());
+ bool fakeGroupInterfaceIfNeeded();
+ void setReady();
+
+ QString groupMemberChangeDetailsTelepathyError(
+ const GroupMemberChangeDetails &details);
+
+ inline ChannelInterfaceMergeableConferenceInterface *mergeableConferenceInterface(
+ InterfaceSupportedChecking check = CheckInterfaceSupported) const
+ {
+ return parent->optionalInterface<ChannelInterfaceMergeableConferenceInterface>(check);
+ }
+
+ inline ChannelInterfaceSplittableInterface *splittableInterface(
+ InterfaceSupportedChecking check = CheckInterfaceSupported) const
+ {
+ return parent->optionalInterface<ChannelInterfaceSplittableInterface>(check);
+ }
+
+ void processConferenceChannelRemoved();
+
+ struct GroupMembersChangedInfo;
+ struct ConferenceChannelRemovedInfo;
+
+ // Public object
+ Channel *parent;
+
+ // Instance of generated interface class
+ Client::ChannelInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ // Owning connection - it can be a SharedPtr as Connection does not cache
+ // channels
+ ConnectionPtr connection;
+
+ QVariantMap immutableProperties;
+
+ // Optional interface proxies
+ Client::ChannelInterfaceGroupInterface *group;
+ Client::ChannelInterfaceConferenceInterface *conference;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ QQueue<void (Private::*)()> introspectQueue;
+
+ // Introspected properties
+
+ // Main interface
+ QString channelType;
+ uint targetHandleType;
+ uint targetHandle;
+ QString targetId;
+ ContactPtr targetContact;
+ bool requested;
+ uint initiatorHandle;
+ ContactPtr initiatorContact;
+
+ // Group flags
+ uint groupFlags;
+ bool usingMembersChangedDetailed;
+
+ // Group member introspection
+ bool groupHaveMembers;
+ bool buildingContacts;
+
+ // Queue of received MCD signals to process
+ QQueue<GroupMembersChangedInfo *> groupMembersChangedQueue;
+ GroupMembersChangedInfo *currentGroupMembersChangedInfo;
+
+ // Pending from the MCD signal currently processed, but contacts not yet built
+ QSet<uint> pendingGroupMembers;
+ QSet<uint> pendingGroupLocalPendingMembers;
+ QSet<uint> pendingGroupRemotePendingMembers;
+ UIntList groupMembersToRemove;
+ UIntList groupLocalPendingMembersToRemove;
+ UIntList groupRemotePendingMembersToRemove;
+
+ // Initial members
+ UIntList groupInitialMembers;
+ LocalPendingInfoList groupInitialLP;
+ UIntList groupInitialRP;
+
+ // Current members
+ QHash<uint, ContactPtr> groupContacts;
+ QHash<uint, ContactPtr> groupLocalPendingContacts;
+ QHash<uint, ContactPtr> groupRemotePendingContacts;
+
+ // Stored change info
+ QHash<uint, GroupMemberChangeDetails> groupLocalPendingContactsChangeInfo;
+ GroupMemberChangeDetails groupSelfContactRemoveInfo;
+
+ // Group handle owners
+ bool groupAreHandleOwnersAvailable;
+ HandleOwnerMap groupHandleOwners;
+
+ // Group self identity
+ bool pendingRetrieveGroupSelfContact;
+ bool groupIsSelfHandleTracked;
+ uint groupSelfHandle;
+ ContactPtr groupSelfContact;
+
+ // Conference
+ bool introspectingConference;
+ QHash<QString, ChannelPtr> conferenceChannels;
+ QHash<QString, ChannelPtr> conferenceInitialChannels;
+ QString conferenceInvitationMessage;
+ QHash<uint, ChannelPtr> conferenceOriginalChannels;
+ UIntList conferenceInitialInviteeHandles;
+ Contacts conferenceInitialInviteeContacts;
+ QQueue<ConferenceChannelRemovedInfo *> conferenceChannelRemovedQueue;
+ bool buildingConferenceChannelRemovedActorContact;
+
+ static const QString keyActor;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT Channel::Private::GroupMembersChangedInfo
+{
+ GroupMembersChangedInfo(const UIntList &added, const UIntList &removed,
+ const UIntList &localPending, const UIntList &remotePending,
+ const QVariantMap &details)
+ : added(added),
+ removed(removed),
+ localPending(localPending),
+ remotePending(remotePending),
+ details(details),
+ // TODO most of these probably should be removed once the rest of the code using them is sanitized
+ actor(qdbus_cast<uint>(details.value(keyActor))),
+ reason(qdbus_cast<uint>(details.value(keyChangeReason))),
+ message(qdbus_cast<QString>(details.value(keyMessage)))
+ {
+ }
+
+ UIntList added;
+ UIntList removed;
+ UIntList localPending;
+ UIntList remotePending;
+ QVariantMap details;
+ uint actor;
+ uint reason;
+ QString message;
+
+ static const QString keyChangeReason;
+ static const QString keyMessage;
+ static const QString keyContactIds;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT Channel::Private::ConferenceChannelRemovedInfo
+{
+ ConferenceChannelRemovedInfo(const QDBusObjectPath &channelPath, const QVariantMap &details)
+ : channelPath(channelPath),
+ details(details)
+ {
+ }
+
+ QDBusObjectPath channelPath;
+ QVariantMap details;
+};
+
+const QString Channel::Private::keyActor(QLatin1String("actor"));
+const QString Channel::Private::GroupMembersChangedInfo::keyChangeReason(
+ QLatin1String("change-reason"));
+const QString Channel::Private::GroupMembersChangedInfo::keyMessage(QLatin1String("message"));
+const QString Channel::Private::GroupMembersChangedInfo::keyContactIds(QLatin1String("contact-ids"));
+
+Channel::Private::Private(Channel *parent, const ConnectionPtr &connection,
+ const QVariantMap &immutableProperties)
+ : parent(parent),
+ baseInterface(new Client::ChannelInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ connection(connection),
+ immutableProperties(immutableProperties),
+ group(0),
+ conference(0),
+ readinessHelper(parent->readinessHelper()),
+ targetHandleType(0),
+ targetHandle(0),
+ requested(false),
+ initiatorHandle(0),
+ groupFlags(0),
+ usingMembersChangedDetailed(false),
+ groupHaveMembers(false),
+ buildingContacts(false),
+ currentGroupMembersChangedInfo(0),
+ groupAreHandleOwnersAvailable(false),
+ pendingRetrieveGroupSelfContact(false),
+ groupIsSelfHandleTracked(false),
+ groupSelfHandle(0),
+ introspectingConference(false),
+ buildingConferenceChannelRemovedActorContact(false)
+{
+ debug() << "Creating new Channel:" << parent->objectPath();
+
+ if (connection->isValid()) {
+ debug() << " Connecting to Channel::Closed() signal";
+ parent->connect(baseInterface,
+ SIGNAL(Closed()),
+ SLOT(onClosed()));
+
+ debug() << " Connection to owning connection's lifetime signals";
+ parent->connect(connection.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onConnectionInvalidated()));
+ }
+ else {
+ warning() << "Connection given as the owner for a Channel was "
+ "invalid! Channel will be stillborn.";
+ parent->invalidate(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Connection given as the owner of this channel was invalid"));
+ }
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As Channel does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ // As Channel does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableConferenceInitialInviteeContacts(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures
+ QStringList() << TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE, // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectConferenceInitialInviteeContacts,
+ this);
+ introspectables[FeatureConferenceInitialInviteeContacts] =
+ introspectableConferenceInitialInviteeContacts;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+Channel::Private::~Private()
+{
+ delete currentGroupMembersChangedInfo;
+ foreach (GroupMembersChangedInfo *info, groupMembersChangedQueue) {
+ delete info;
+ }
+ foreach (ConferenceChannelRemovedInfo *info, conferenceChannelRemovedQueue) {
+ delete info;
+ }
+}
+
+void Channel::Private::introspectMain(Channel::Private *self)
+{
+ // Make sure connection object is ready, as we need to use some methods that
+ // are only available after connection object gets ready.
+ debug() << "Calling Connection::becomeReady()";
+ self->parent->connect(self->connection->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onConnectionReady(Tp::PendingOperation*)));
+}
+
+void Channel::Private::introspectMainProperties()
+{
+ QVariantMap props;
+ QString key;
+ bool needIntrospectMainProps = false;
+ const unsigned numNames = 8;
+ const static QString names[numNames] = {
+ QLatin1String("ChannelType"),
+ QLatin1String("Interfaces"),
+ QLatin1String("TargetHandleType"),
+ QLatin1String("TargetHandle"),
+ QLatin1String("TargetID"),
+ QLatin1String("Requested"),
+ QLatin1String("InitiatorHandle"),
+ QLatin1String("InitiatorID")
+ };
+ const static QString qualifiedNames[numNames] = {
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Interfaces"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorID")
+ };
+ for (unsigned i = 0; i < numNames; ++i) {
+ const QString &qualified = qualifiedNames[i];
+ if (!immutableProperties.contains(qualified)) {
+ needIntrospectMainProps = true;
+ break;
+ }
+ props.insert(names[i], immutableProperties.value(qualified));
+ }
+
+ // Save Requested and InitiatorHandle here, so even if the GetAll return doesn't have them but
+ // the given immutable props do (eg. due to the PendingChannel fallback guesses) we use them
+ requested = qdbus_cast<bool>(props[QLatin1String("Requested")]);
+ initiatorHandle = qdbus_cast<uint>(props[QLatin1String("InitiatorHandle")]);
+
+ if (props.contains(QLatin1String("InitiatorID"))) {
+ QString initiatorId = qdbus_cast<QString>(props[QLatin1String("InitiatorID")]);
+ connection->lowlevel()->injectContactId(initiatorHandle, initiatorId);
+ }
+
+ if (needIntrospectMainProps) {
+ debug() << "Calling Properties::GetAll(Channel)";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL)),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+ } else {
+ extractMainProps(props);
+ continueIntrospection();
+ }
+}
+
+void Channel::Private::introspectMainFallbackChannelType()
+{
+ debug() << "Calling Channel::GetChannelType()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(baseInterface->GetChannelType(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotChannelType(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectMainFallbackHandle()
+{
+ debug() << "Calling Channel::GetHandle()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(baseInterface->GetHandle(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotHandle(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectMainFallbackInterfaces()
+{
+ debug() << "Calling Channel::GetInterfaces()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(baseInterface->GetInterfaces(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotInterfaces(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectGroup()
+{
+ Q_ASSERT(properties != 0);
+
+ if (!group) {
+ group = parent->interface<Client::ChannelInterfaceGroupInterface>();
+ Q_ASSERT(group != 0);
+ }
+
+ debug() << "Introspecting Channel.Interface.Group for" << parent->objectPath();
+
+ parent->connect(group,
+ SIGNAL(GroupFlagsChanged(uint,uint)),
+ SLOT(onGroupFlagsChanged(uint,uint)));
+
+ parent->connect(group,
+ SIGNAL(MembersChanged(QString,Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,uint,uint)),
+ SLOT(onMembersChanged(QString,Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,uint,uint)));
+ parent->connect(group,
+ SIGNAL(MembersChangedDetailed(Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,QVariantMap)),
+ SLOT(onMembersChangedDetailed(Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,QVariantMap)));
+
+ parent->connect(group,
+ SIGNAL(HandleOwnersChanged(Tp::HandleOwnerMap,
+ Tp::UIntList)),
+ SLOT(onHandleOwnersChanged(Tp::HandleOwnerMap,
+ Tp::UIntList)));
+
+ parent->connect(group,
+ SIGNAL(SelfHandleChanged(uint)),
+ SLOT(onSelfHandleChanged(uint)));
+
+ debug() << "Calling Properties::GetAll(Channel.Interface.Group)";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP)),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotGroupProperties(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectGroupFallbackFlags()
+{
+ Q_ASSERT(group != 0);
+
+ debug() << "Calling Channel.Interface.Group::GetGroupFlags()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(group->GetGroupFlags(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotGroupFlags(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectGroupFallbackMembers()
+{
+ Q_ASSERT(group != 0);
+
+ debug() << "Calling Channel.Interface.Group::GetAllMembers()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(group->GetAllMembers(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotAllMembers(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectGroupFallbackLocalPendingWithInfo()
+{
+ Q_ASSERT(group != 0);
+
+ debug() << "Calling Channel.Interface.Group::GetLocalPendingMembersWithInfo()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(group->GetLocalPendingMembersWithInfo(),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotLocalPendingMembersWithInfo(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectGroupFallbackSelfHandle()
+{
+ Q_ASSERT(group != 0);
+
+ debug() << "Calling Channel.Interface.Group::GetSelfHandle()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(group->GetSelfHandle(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotSelfHandle(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectConference()
+{
+ Q_ASSERT(properties != 0);
+ Q_ASSERT(conference == 0);
+
+ debug() << "Introspecting Conference interface";
+ conference = parent->interface<Client::ChannelInterfaceConferenceInterface>();
+ Q_ASSERT(conference != 0);
+
+ introspectingConference = true;
+
+ debug() << "Connecting to Channel.Interface.Conference.ChannelMerged/Removed";
+ parent->connect(conference,
+ SIGNAL(ChannelMerged(QDBusObjectPath,uint,QVariantMap)),
+ SLOT(onConferenceChannelMerged(QDBusObjectPath,uint,QVariantMap)));
+ parent->connect(conference,
+ SIGNAL(ChannelRemoved(QDBusObjectPath,QVariantMap)),
+ SLOT(onConferenceChannelRemoved(QDBusObjectPath,QVariantMap)));
+
+ debug() << "Calling Properties::GetAll(Channel.Interface.Conference)";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CONFERENCE)),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotConferenceProperties(QDBusPendingCallWatcher*)));
+}
+
+void Channel::Private::introspectConferenceInitialInviteeContacts(Private *self)
+{
+ if (!self->conferenceInitialInviteeHandles.isEmpty()) {
+ ContactManagerPtr manager = self->connection->contactManager();
+ PendingContacts *pendingContacts = manager->contactsForHandles(
+ self->conferenceInitialInviteeHandles);
+ self->parent->connect(pendingContacts,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(gotConferenceInitialInviteeContacts(Tp::PendingOperation *)));
+ } else {
+ self->readinessHelper->setIntrospectCompleted(
+ FeatureConferenceInitialInviteeContacts, true);
+ }
+}
+
+void Channel::Private::continueIntrospection()
+{
+ if (introspectQueue.isEmpty()) {
+ // this should always be true, but let's make sure
+ if (!parent->isReady(Channel::FeatureCore)) {
+ if (groupMembersChangedQueue.isEmpty() && !buildingContacts &&
+ !introspectingConference) {
+ debug() << "Both the IS and the MCD queue empty for the first time. Ready.";
+ setReady();
+ } else {
+ debug() << "Introspection done before contacts done - contacts sets ready";
+ }
+ }
+ } else {
+ (this->*(introspectQueue.dequeue()))();
+ }
+}
+
+void Channel::Private::extractMainProps(const QVariantMap &props)
+{
+ const static QString keyChannelType(QLatin1String("ChannelType"));
+ const static QString keyInterfaces(QLatin1String("Interfaces"));
+ const static QString keyTargetHandle(QLatin1String("TargetHandle"));
+ const static QString keyTargetHandleType(QLatin1String("TargetHandleType"));
+
+ bool haveProps = props.size() >= 4
+ && props.contains(keyChannelType)
+ && !qdbus_cast<QString>(props[keyChannelType]).isEmpty()
+ && props.contains(keyInterfaces)
+ && props.contains(keyTargetHandle)
+ && props.contains(keyTargetHandleType);
+
+ if (!haveProps) {
+ warning() << "Channel properties specified in 0.17.7 not found";
+
+ introspectQueue.enqueue(&Private::introspectMainFallbackChannelType);
+ introspectQueue.enqueue(&Private::introspectMainFallbackHandle);
+ introspectQueue.enqueue(&Private::introspectMainFallbackInterfaces);
+ } else {
+ parent->setInterfaces(qdbus_cast<QStringList>(props[keyInterfaces]));
+ readinessHelper->setInterfaces(parent->interfaces());
+ channelType = qdbus_cast<QString>(props[keyChannelType]);
+ targetHandle = qdbus_cast<uint>(props[keyTargetHandle]);
+ targetHandleType = qdbus_cast<uint>(props[keyTargetHandleType]);
+
+ const static QString keyTargetId(QLatin1String("TargetID"));
+ const static QString keyRequested(QLatin1String("Requested"));
+ const static QString keyInitiatorHandle(QLatin1String("InitiatorHandle"));
+ const static QString keyInitiatorId(QLatin1String("InitiatorID"));
+
+ if (props.contains(keyTargetId)) {
+ targetId = qdbus_cast<QString>(props[keyTargetId]);
+
+ if (targetHandleType == HandleTypeContact) {
+ connection->lowlevel()->injectContactId(targetHandle, targetId);
+ }
+ }
+
+ if (props.contains(keyRequested)) {
+ requested = qdbus_cast<uint>(props[keyRequested]);
+ }
+
+ if (props.contains(keyInitiatorHandle)) {
+ initiatorHandle = qdbus_cast<uint>(props[keyInitiatorHandle]);
+ }
+
+ if (props.contains(keyInitiatorId)) {
+ QString initiatorId = qdbus_cast<QString>(props[keyInitiatorId]);
+ connection->lowlevel()->injectContactId(initiatorHandle, initiatorId);
+ }
+
+ if (!fakeGroupInterfaceIfNeeded() &&
+ !parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP)) &&
+ initiatorHandle) {
+ // No group interface, so nobody will build the poor fellow for us. Will do it ourselves
+ // out of pity for him.
+ // TODO: needs testing. I would imagine some of the elaborate updateContacts logic
+ // tripping over with just this.
+ buildContacts();
+ }
+
+ nowHaveInterfaces();
+ }
+
+ debug() << "Have initiator handle:" << (initiatorHandle ? "yes" : "no");
+}
+
+void Channel::Private::extract0176GroupProps(const QVariantMap &props)
+{
+ const static QString keyGroupFlags(QLatin1String("GroupFlags"));
+ const static QString keyHandleOwners(QLatin1String("HandleOwners"));
+ const static QString keyLPMembers(QLatin1String("LocalPendingMembers"));
+ const static QString keyMembers(QLatin1String("Members"));
+ const static QString keyRPMembers(QLatin1String("RemotePendingMembers"));
+ const static QString keySelfHandle(QLatin1String("SelfHandle"));
+
+ bool haveProps = props.size() >= 6
+ && (props.contains(keyGroupFlags)
+ && (qdbus_cast<uint>(props[keyGroupFlags]) &
+ ChannelGroupFlagProperties))
+ && props.contains(keyHandleOwners)
+ && props.contains(keyLPMembers)
+ && props.contains(keyMembers)
+ && props.contains(keyRPMembers)
+ && props.contains(keySelfHandle);
+
+ if (!haveProps) {
+ warning() << " Properties specified in 0.17.6 not found";
+ warning() << " Handle owners and self handle tracking disabled";
+
+ introspectQueue.enqueue(&Private::introspectGroupFallbackFlags);
+ introspectQueue.enqueue(&Private::introspectGroupFallbackMembers);
+ introspectQueue.enqueue(&Private::introspectGroupFallbackLocalPendingWithInfo);
+ introspectQueue.enqueue(&Private::introspectGroupFallbackSelfHandle);
+ } else {
+ debug() << " Found properties specified in 0.17.6";
+
+ groupAreHandleOwnersAvailable = true;
+ groupIsSelfHandleTracked = true;
+
+ setGroupFlags(qdbus_cast<uint>(props[keyGroupFlags]));
+ groupHandleOwners = qdbus_cast<HandleOwnerMap>(props[keyHandleOwners]);
+
+ groupInitialMembers = qdbus_cast<UIntList>(props[keyMembers]);
+ groupInitialLP = qdbus_cast<LocalPendingInfoList>(props[keyLPMembers]);
+ groupInitialRP = qdbus_cast<UIntList>(props[keyRPMembers]);
+
+ uint propSelfHandle = qdbus_cast<uint>(props[keySelfHandle]);
+ // Don't overwrite the self handle we got from the Connection with 0
+ if (propSelfHandle) {
+ groupSelfHandle = propSelfHandle;
+ }
+
+ nowHaveInitialMembers();
+ }
+}
+
+void Channel::Private::nowHaveInterfaces()
+{
+ debug() << "Channel has" << parent->interfaces().size() <<
+ "optional interfaces:" << parent->interfaces();
+
+ QStringList interfaces = parent->interfaces();
+
+ if (interfaces.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ introspectQueue.enqueue(&Private::introspectGroup);
+ }
+
+ if (interfaces.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CONFERENCE))) {
+ introspectQueue.enqueue(&Private::introspectConference);
+ }
+}
+
+void Channel::Private::nowHaveInitialMembers()
+{
+ // Must be called with no contacts anywhere in the first place
+ Q_ASSERT(!parent->isReady(Channel::FeatureCore));
+ Q_ASSERT(!buildingContacts);
+
+ Q_ASSERT(pendingGroupMembers.isEmpty());
+ Q_ASSERT(pendingGroupLocalPendingMembers.isEmpty());
+ Q_ASSERT(pendingGroupRemotePendingMembers.isEmpty());
+
+ Q_ASSERT(groupContacts.isEmpty());
+ Q_ASSERT(groupLocalPendingContacts.isEmpty());
+ Q_ASSERT(groupRemotePendingContacts.isEmpty());
+
+ // Set groupHaveMembers so we start queueing fresh MCD signals
+ Q_ASSERT(!groupHaveMembers);
+ groupHaveMembers = true;
+
+ // Synthesize MCD for current + RP
+ groupMembersChangedQueue.enqueue(new GroupMembersChangedInfo(
+ groupInitialMembers, // Members
+ UIntList(), // Removed - obviously, none
+ UIntList(), // LP - will be handled separately, see below
+ groupInitialRP, // Remote pending
+ QVariantMap())); // No details for members + RP
+
+ // Synthesize one MCD for each initial LP member - they might have different details
+ foreach (const LocalPendingInfo &info, groupInitialLP) {
+ QVariantMap details;
+
+ if (info.actor != 0) {
+ details.insert(QLatin1String("actor"), info.actor);
+ }
+
+ if (info.reason != ChannelGroupChangeReasonNone) {
+ details.insert(QLatin1String("change-reason"), info.reason);
+ }
+
+ if (!info.message.isEmpty()) {
+ details.insert(QLatin1String("message"), info.message);
+ }
+
+ groupMembersChangedQueue.enqueue(new GroupMembersChangedInfo(UIntList(), UIntList(),
+ UIntList() << info.toBeAdded, UIntList(), details));
+ }
+
+ // At least our added MCD event to process
+ processMembersChanged();
+}
+
+bool Channel::Private::setGroupFlags(uint newGroupFlags)
+{
+ if (groupFlags == newGroupFlags) {
+ return false;
+ }
+
+ groupFlags = newGroupFlags;
+
+ // this shouldn't happen but let's make sure
+ if (!parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ return false;
+ }
+
+ if ((groupFlags & ChannelGroupFlagMembersChangedDetailed) &&
+ !usingMembersChangedDetailed) {
+ usingMembersChangedDetailed = true;
+ debug() << "Starting to exclusively listen to MembersChangedDetailed for" <<
+ parent->objectPath();
+ parent->disconnect(group,
+ SIGNAL(MembersChanged(QString,Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,uint,uint)),
+ parent,
+ SLOT(onMembersChanged(QString,Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,uint,uint)));
+ } else if (!(groupFlags & ChannelGroupFlagMembersChangedDetailed) &&
+ usingMembersChangedDetailed) {
+ warning() << " Channel service did spec-incompliant removal of MCD from GroupFlags";
+ usingMembersChangedDetailed = false;
+ parent->connect(group,
+ SIGNAL(MembersChanged(QString,Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,uint,uint)),
+ parent,
+ SLOT(onMembersChanged(QString,Tp::UIntList,
+ Tp::UIntList,Tp::UIntList,
+ Tp::UIntList,uint,uint)));
+ }
+
+ return true;
+}
+
+void Channel::Private::buildContacts()
+{
+ buildingContacts = true;
+
+ ContactManagerPtr manager = connection->contactManager();
+ UIntList toBuild = QSet<uint>(pendingGroupMembers +
+ pendingGroupLocalPendingMembers +
+ pendingGroupRemotePendingMembers).toList();
+
+ if (currentGroupMembersChangedInfo &&
+ currentGroupMembersChangedInfo->actor != 0) {
+ toBuild.append(currentGroupMembersChangedInfo->actor);
+ }
+
+ if (!initiatorContact && initiatorHandle) {
+ // No initiator contact, but Yes initiator handle - might do something about it with just
+ // that information
+ toBuild.append(initiatorHandle);
+ }
+
+ if (!targetContact && targetHandleType == HandleTypeContact && targetHandle != 0) {
+ toBuild.append(targetHandle);
+ }
+
+ // always try to retrieve selfContact and check if it changed on
+ // updateContacts or on gotContacts, in case we were not able to retrieve it
+ if (groupSelfHandle) {
+ toBuild.append(groupSelfHandle);
+ }
+
+ // group self handle changed to 0 <- strange but it may happen, and contacts
+ // were being built at the time, so check now
+ if (toBuild.isEmpty()) {
+ if (!groupSelfHandle && groupSelfContact) {
+ groupSelfContact.reset();
+ if (parent->isReady(Channel::FeatureCore)) {
+ emit parent->groupSelfContactChanged();
+ }
+ }
+
+ buildingContacts = false;
+ return;
+ }
+
+ PendingContacts *pendingContacts = manager->contactsForHandles(
+ toBuild);
+ parent->connect(pendingContacts,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContacts(Tp::PendingOperation*)));
+}
+
+void Channel::Private::processMembersChanged()
+{
+ Q_ASSERT(!buildingContacts);
+
+ if (groupMembersChangedQueue.isEmpty()) {
+ if (pendingRetrieveGroupSelfContact) {
+ pendingRetrieveGroupSelfContact = false;
+ // nothing queued but selfContact changed
+ buildContacts();
+ return;
+ }
+
+ if (!parent->isReady(Channel::FeatureCore)) {
+ if (introspectQueue.isEmpty()) {
+ debug() << "Both the MCD and the introspect queue empty for the first time. Ready!";
+
+ if (initiatorHandle && !initiatorContact) {
+ warning() << " Unable to create contact object for initiator with handle" <<
+ initiatorHandle;
+ }
+
+ if (targetHandleType == HandleTypeContact && targetHandle != 0 && !targetContact) {
+ warning() << " Unable to create contact object for target with handle" <<
+ targetHandle;
+ }
+
+ if (groupSelfHandle && !groupSelfContact) {
+ warning() << " Unable to create contact object for self handle" <<
+ groupSelfHandle;
+ }
+
+ continueIntrospection();
+ } else {
+ debug() << "Contact queue empty but introspect queue isn't. IS will set ready.";
+ }
+ }
+
+ return;
+ }
+
+ Q_ASSERT(pendingGroupMembers.isEmpty());
+ Q_ASSERT(pendingGroupLocalPendingMembers.isEmpty());
+ Q_ASSERT(pendingGroupRemotePendingMembers.isEmpty());
+
+ // always set this to false here, as buildContacts will always try to
+ // retrieve the selfContact and updateContacts will check if the built
+ // contact is the same as the current contact.
+ pendingRetrieveGroupSelfContact = false;
+
+ currentGroupMembersChangedInfo = groupMembersChangedQueue.dequeue();
+
+ foreach (uint handle, currentGroupMembersChangedInfo->added) {
+ if (!groupContacts.contains(handle)) {
+ pendingGroupMembers.insert(handle);
+ }
+
+ // the member was added to current members, check if it was in the
+ // local/pending lists and if true, schedule for removal from that list
+ if (groupLocalPendingContacts.contains(handle)) {
+ groupLocalPendingMembersToRemove.append(handle);
+ } else if(groupRemotePendingContacts.contains(handle)) {
+ groupRemotePendingMembersToRemove.append(handle);
+ }
+ }
+
+ foreach (uint handle, currentGroupMembersChangedInfo->localPending) {
+ if (!groupLocalPendingContacts.contains(handle)) {
+ pendingGroupLocalPendingMembers.insert(handle);
+ }
+ }
+
+ foreach (uint handle, currentGroupMembersChangedInfo->remotePending) {
+ if (!groupRemotePendingContacts.contains(handle)) {
+ pendingGroupRemotePendingMembers.insert(handle);
+ }
+ }
+
+ foreach (uint handle, currentGroupMembersChangedInfo->removed) {
+ groupMembersToRemove.append(handle);
+ }
+
+ // Always go through buildContacts - we might have a self/initiator/whatever handle to build
+ buildContacts();
+}
+
+void Channel::Private::updateContacts(const QList<ContactPtr> &contacts)
+{
+ Contacts groupContactsAdded;
+ Contacts groupLocalPendingContactsAdded;
+ Contacts groupRemotePendingContactsAdded;
+ ContactPtr actorContact;
+ bool selfContactUpdated = false;
+
+ debug() << "Entering Chan::Priv::updateContacts() with" << contacts.size() << "contacts";
+
+ // FIXME: simplify. Some duplication of logic present.
+ foreach (ContactPtr contact, contacts) {
+ uint handle = contact->handle()[0];
+ if (pendingGroupMembers.contains(handle)) {
+ groupContactsAdded.insert(contact);
+ groupContacts[handle] = contact;
+ } else if (pendingGroupLocalPendingMembers.contains(handle)) {
+ groupLocalPendingContactsAdded.insert(contact);
+ groupLocalPendingContacts[handle] = contact;
+ // FIXME: should set the details and actor here too
+ groupLocalPendingContactsChangeInfo[handle] = GroupMemberChangeDetails();
+ } else if (pendingGroupRemotePendingMembers.contains(handle)) {
+ groupRemotePendingContactsAdded.insert(contact);
+ groupRemotePendingContacts[handle] = contact;
+ }
+
+ if (groupSelfHandle == handle && groupSelfContact != contact) {
+ groupSelfContact = contact;
+ selfContactUpdated = true;
+ }
+
+ if (!initiatorContact && initiatorHandle == handle) {
+ // No initiator contact stored, but there's a contact for the initiator handle
+ // We can use that!
+ initiatorContact = contact;
+ }
+
+ if (!targetContact && targetHandleType == HandleTypeContact && targetHandle == handle) {
+ targetContact = contact;
+
+ if (targetId.isEmpty()) {
+ // For some reason, TargetID was missing from the property map. We can initialize it
+ // here in that case.
+ targetId = targetContact->id();
+ }
+ }
+
+ if (currentGroupMembersChangedInfo &&
+ currentGroupMembersChangedInfo->actor == contact->handle()[0]) {
+ actorContact = contact;
+ }
+ }
+
+ if (!groupSelfHandle && groupSelfContact) {
+ groupSelfContact.reset();
+ selfContactUpdated = true;
+ }
+
+ pendingGroupMembers.clear();
+ pendingGroupLocalPendingMembers.clear();
+ pendingGroupRemotePendingMembers.clear();
+
+ // FIXME: This shouldn't be needed. Clearer would be to first scan for the actor being present
+ // in the contacts supplied.
+ foreach (ContactPtr contact, contacts) {
+ uint handle = contact->handle()[0];
+ if (groupLocalPendingContactsChangeInfo.contains(handle)) {
+ groupLocalPendingContactsChangeInfo[handle] =
+ GroupMemberChangeDetails(actorContact,
+ currentGroupMembersChangedInfo ? currentGroupMembersChangedInfo->details : QVariantMap());
+ }
+ }
+
+ Contacts groupContactsRemoved;
+ ContactPtr contactToRemove;
+ foreach (uint handle, groupMembersToRemove) {
+ if (groupContacts.contains(handle)) {
+ contactToRemove = groupContacts[handle];
+ groupContacts.remove(handle);
+ } else if (groupLocalPendingContacts.contains(handle)) {
+ contactToRemove = groupLocalPendingContacts[handle];
+ groupLocalPendingContacts.remove(handle);
+ } else if (groupRemotePendingContacts.contains(handle)) {
+ contactToRemove = groupRemotePendingContacts[handle];
+ groupRemotePendingContacts.remove(handle);
+ }
+
+ if (groupLocalPendingContactsChangeInfo.contains(handle)) {
+ groupLocalPendingContactsChangeInfo.remove(handle);
+ }
+
+ if (contactToRemove) {
+ groupContactsRemoved.insert(contactToRemove);
+ }
+ }
+ groupMembersToRemove.clear();
+
+ // FIXME: drop the LPToRemove and RPToRemove sets - they're redundant
+ foreach (uint handle, groupLocalPendingMembersToRemove) {
+ groupLocalPendingContacts.remove(handle);
+ }
+ groupLocalPendingMembersToRemove.clear();
+
+ foreach (uint handle, groupRemotePendingMembersToRemove) {
+ groupRemotePendingContacts.remove(handle);
+ }
+ groupRemotePendingMembersToRemove.clear();
+
+ if (!groupContactsAdded.isEmpty() ||
+ !groupLocalPendingContactsAdded.isEmpty() ||
+ !groupRemotePendingContactsAdded.isEmpty() ||
+ !groupContactsRemoved.isEmpty()) {
+ GroupMemberChangeDetails details(
+ actorContact,
+ currentGroupMembersChangedInfo ? currentGroupMembersChangedInfo->details : QVariantMap());
+
+ if (currentGroupMembersChangedInfo
+ && currentGroupMembersChangedInfo->removed.contains(groupSelfHandle)) {
+ // Update groupSelfContactRemoveInfo with the proper actor in case
+ // the actor was not available by the time onMembersChangedDetailed
+ // was called.
+ groupSelfContactRemoveInfo = details;
+ }
+
+ if (parent->isReady(Channel::FeatureCore)) {
+ // Channel is ready, we can signal membership changes to the outside world without
+ // confusing anyone's fragile logic.
+ emit parent->groupMembersChanged(
+ groupContactsAdded,
+ groupLocalPendingContactsAdded,
+ groupRemotePendingContactsAdded,
+ groupContactsRemoved,
+ details);
+ }
+ }
+ delete currentGroupMembersChangedInfo;
+ currentGroupMembersChangedInfo = 0;
+
+ if (selfContactUpdated && parent->isReady(Channel::FeatureCore)) {
+ emit parent->groupSelfContactChanged();
+ }
+
+ processMembersChanged();
+}
+
+bool Channel::Private::fakeGroupInterfaceIfNeeded()
+{
+ if (parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ return false;
+ } else if (targetHandleType != HandleTypeContact) {
+ return false;
+ }
+
+ // fake group interface
+ if (connection->selfHandle() && targetHandle) {
+ // Fake groupSelfHandle and initial members, let the MCD handling take care of the rest
+ // TODO connect to Connection::selfHandleChanged
+ groupSelfHandle = connection->selfHandle();
+ groupInitialMembers = UIntList() << groupSelfHandle << targetHandle;
+
+ debug().nospace() << "Faking a group on channel with self handle=" <<
+ groupSelfHandle << " and other handle=" << targetHandle;
+
+ nowHaveInitialMembers();
+ } else {
+ warning() << "Connection::selfHandle is 0 or targetHandle is 0, "
+ "not faking a group on channel";
+ }
+
+ return true;
+}
+
+void Channel::Private::setReady()
+{
+ Q_ASSERT(!parent->isReady(Channel::FeatureCore));
+
+ debug() << "Channel fully ready";
+ debug() << " Channel type" << channelType;
+ debug() << " Target handle" << targetHandle;
+ debug() << " Target handle type" << targetHandleType;
+
+ if (parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ debug() << " Group: flags" << groupFlags;
+ if (groupAreHandleOwnersAvailable) {
+ debug() << " Group: Number of handle owner mappings" <<
+ groupHandleOwners.size();
+ }
+ else {
+ debug() << " Group: No handle owners property present";
+ }
+ debug() << " Group: Number of current members" <<
+ groupContacts.size();
+ debug() << " Group: Number of local pending members" <<
+ groupLocalPendingContacts.size();
+ debug() << " Group: Number of remote pending members" <<
+ groupRemotePendingContacts.size();
+ debug() << " Group: Self handle" << groupSelfHandle <<
+ "tracked:" << (groupIsSelfHandleTracked ? "yes" : "no");
+ }
+
+ readinessHelper->setIntrospectCompleted(FeatureCore, true);
+}
+
+QString Channel::Private::groupMemberChangeDetailsTelepathyError(
+ const GroupMemberChangeDetails &details)
+{
+ QString error;
+ uint reason = details.reason();
+ switch (reason) {
+ case ChannelGroupChangeReasonOffline:
+ error = QLatin1String(TELEPATHY_ERROR_OFFLINE);
+ break;
+ case ChannelGroupChangeReasonKicked:
+ error = QLatin1String(TELEPATHY_ERROR_CHANNEL_KICKED);
+ break;
+ case ChannelGroupChangeReasonBanned:
+ error = QLatin1String(TELEPATHY_ERROR_CHANNEL_BANNED);
+ break;
+ case ChannelGroupChangeReasonBusy:
+ error = QLatin1String(TELEPATHY_ERROR_BUSY);
+ break;
+ case ChannelGroupChangeReasonNoAnswer:
+ error = QLatin1String(TELEPATHY_ERROR_NO_ANSWER);
+ break;
+ case ChannelGroupChangeReasonPermissionDenied:
+ error = QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED);
+ break;
+ case ChannelGroupChangeReasonInvalidContact:
+ error = QLatin1String(TELEPATHY_ERROR_DOES_NOT_EXIST);
+ break;
+ // The following change reason are being mapped to default
+ // case ChannelGroupChangeReasonNone:
+ // case ChannelGroupChangeReasonInvited: // shouldn't happen
+ // case ChannelGroupChangeReasonError:
+ // case ChannelGroupChangeReasonRenamed:
+ // case ChannelGroupChangeReasonSeparated: // shouldn't happen
+ default:
+ // let's use the actor handle and selfHandle here instead of the
+ // contacts, as the contacts may not be ready.
+ error = ((qdbus_cast<uint>(details.allDetails().value(QLatin1String("actor"))) == groupSelfHandle) ?
+ QLatin1String(TELEPATHY_ERROR_CANCELLED) :
+ QLatin1String(TELEPATHY_ERROR_TERMINATED));
+ break;
+ }
+
+ return error;
+}
+
+void Channel::Private::processConferenceChannelRemoved()
+{
+ if (buildingConferenceChannelRemovedActorContact ||
+ conferenceChannelRemovedQueue.isEmpty()) {
+ return;
+ }
+
+ ConferenceChannelRemovedInfo *info = conferenceChannelRemovedQueue.first();
+ if (!conferenceChannels.contains(info->channelPath.path())) {
+ info = conferenceChannelRemovedQueue.dequeue();
+ delete info;
+ processConferenceChannelRemoved();
+ return;
+ }
+
+ buildingConferenceChannelRemovedActorContact = true;
+
+ if (info->details.contains(keyActor)) {
+ ContactManagerPtr manager = connection->contactManager();
+ PendingContacts *pendingContacts = manager->contactsForHandles(
+ UIntList() << qdbus_cast<uint>(info->details.value(keyActor)));
+ parent->connect(pendingContacts,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotConferenceChannelRemovedActorContact(Tp::PendingOperation*)));
+ } else {
+ parent->gotConferenceChannelRemovedActorContact(0);
+ }
+}
+
+struct TELEPATHY_QT4_NO_EXPORT Channel::GroupMemberChangeDetails::Private : public QSharedData
+{
+ Private(const ContactPtr &actor, const QVariantMap &details)
+ : actor(actor), details(details) {}
+
+ ContactPtr actor;
+ QVariantMap details;
+};
+
+/**
+ * \class Channel::GroupMemberChangeDetails
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/channel.h <TelepathyQt4/Channel>
+ *
+ * \brief The Channel::GroupMemberChangeDetails class represents the details of a group membership
+ * change.
+ *
+ * Extended information is not always available; this will be reflected by
+ * the return value of isValid().
+ */
+
+/**
+ * Constructs a new invalid GroupMemberChangeDetails instance.
+ */
+Channel::GroupMemberChangeDetails::GroupMemberChangeDetails()
+{
+}
+
+/**
+ * Copy constructor.
+ */
+Channel::GroupMemberChangeDetails::GroupMemberChangeDetails(const GroupMemberChangeDetails &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+Channel::GroupMemberChangeDetails::~GroupMemberChangeDetails()
+{
+}
+
+/**
+ * Assigns all information (validity, details) from other to this.
+ */
+Channel::GroupMemberChangeDetails &Channel::GroupMemberChangeDetails::operator=(
+ const GroupMemberChangeDetails &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+/**
+ * \fn bool Channel::GroupMemberChangeDetails::isValid() const
+ *
+ * Return whether the details are valid (have actually been received from the service).
+ *
+ * \return \c true if valid, \c false otherwise.
+ */
+
+/**
+ * Return whether the details specify an actor.
+ *
+ * If present, actor() will return the contact object representing the person who made the change.
+ *
+ * \return \c true if the actor is known, \c false otherwise.
+ * \sa actor()
+ */
+bool Channel::GroupMemberChangeDetails::hasActor() const
+{
+ return isValid() && !mPriv->actor.isNull();
+}
+
+/**
+ * Return the contact object representing the person who made the change (actor), if known.
+ *
+ * \return A pointer to the Contact object, or a null ContactPtr if the actor is unknown.
+ * \sa hasActor()
+ */
+ContactPtr Channel::GroupMemberChangeDetails::actor() const
+{
+ return isValid() ? mPriv->actor : ContactPtr();
+}
+
+/**
+ * \fn bool Channel::GroupMemberChangeDetails::hasReason() const
+ *
+ * Return whether the details specify the reason for the change.
+ *
+ * \return \c true if the reason is known, \c false otherwise.
+ * \sa reason()
+ */
+
+/**
+ * \fn ChannelGroupChangeReason Channel::GroupMemberChangeDetails::reason() const
+ *
+ * Return the reason for the change, if known.
+ *
+ * \return The change reason as #ChannelGroupChangeReason, or #ChannelGroupChangeReasonNone
+ * if the reason is unknown.
+ * \sa hasReason()
+ */
+
+/**
+ * \fn bool Channel::GroupMemberChangeDetails::hasMessage() const
+ *
+ * Return whether the details specify a human-readable message from the contact represented by
+ * actor() pertaining to the change.
+ *
+ * \return \c true if the message is known, \c false otherwise.
+ * \sa message()
+ */
+
+/**
+ * \fn QString Channel::GroupMemberChangeDetails::message() const
+ *
+ * Return a human-readable message from the contact represented by actor() pertaining to the change,
+ * if known.
+ *
+ * \return The message, or an empty string if the message is unknown.
+ * \sa hasMessage()
+ */
+
+/**
+ * \fn bool Channel::GroupMemberChangeDetails::hasError() const
+ *
+ * Return whether the details specify a D-Bus error describing the change.
+ *
+ * \return \c true if the error is known, \c false otherwise.
+ * \sa error()
+ */
+
+/**
+ * \fn QString Channel::GroupMemberChangeDetails::error() const
+ *
+ * Return the D-Bus error describing the change, if known.
+ *
+ * The D-Bus error provides more specific information than the reason() and should be used if
+ * applicable.
+ *
+ * \return A D-Bus error describing the change, or an empty string if the error is unknown.
+ * \sa hasError()
+ */
+
+/**
+ * \fn bool Channel::GroupMemberChangeDetails::hasDebugMessage() const
+ *
+ * Return whether the details specify a debug message.
+ *
+ * \return \c true if debug message is present, \c false otherwise.
+ * \sa debugMessage()
+ */
+
+/**
+ * \fn QString Channel::GroupMemberChangeDetails::debugMessage() const
+ *
+ * Return the debug message specified by the details, if any.
+ *
+ * The debug message is purely informational, offered for display for bug reporting purposes, and
+ * should not be attempted to be parsed.
+ *
+ * \return The debug message, or an empty string if there is none.
+ * \sa hasDebugMessage()
+ */
+
+/**
+ * Return a map containing all details of the group members change.
+ *
+ * This is useful for accessing domain-specific additional details.
+ *
+ * \return The details of the group members change as QVariantMap.
+ */
+QVariantMap Channel::GroupMemberChangeDetails::allDetails() const
+{
+ return isValid() ? mPriv->details : QVariantMap();
+}
+
+Channel::GroupMemberChangeDetails::GroupMemberChangeDetails(const ContactPtr &actor,
+ const QVariantMap &details)
+ : mPriv(new Private(actor, details))
+{
+}
+
+/**
+ * \class Channel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/channel.h <TelepathyQt4/Channel>
+ *
+ * \brief The Channel class represents a Telepathy channel.
+ *
+ * All communication in the Telepathy framework is carried out via channel
+ * objects. Specialized classes for some specific channel types such as
+ * StreamedMediaChannel, TextChannel, FileTransferChannel are provided.
+ *
+ * The remote object accessor functions on this object (channelType(), targetHandleType(),
+ * and so on) don't make any D-Bus calls; instead, they return/use
+ * values cached from a previous introspection run. The introspection process
+ * populates their values in the most efficient way possible based on what the
+ * service implements.
+ *
+ * To avoid unnecessary D-Bus traffic, some accessors only return valid
+ * information after specific features have been enabled.
+ * For instance, to retrieve the initial invitee contacts in a conference channel,
+ * it is necessary to enable the feature Channel::FeatureConferenceInitialInviteeContacts.
+ * See the individual methods descriptions for more details.
+ *
+ * Channel features can be enabled by constructing a ChannelFactory and enabling
+ * the desired features, and passing it to AccountManager, Account or ClientRegistrar
+ * when creating them as appropriate. However, if a particular
+ * feature is only ever used in a specific circumstance, such as an user opening
+ * some settings dialog separate from the general view of the application,
+ * features can be later enabled as needed by calling becomeReady() with the additional
+ * features, and waiting for the resulting PendingOperation to finish.
+ *
+ * Each channel is owned by a connection. If the Connection object becomes invalidated
+ * the Channel object will also get invalidated.
+ *
+ * \section chan_usage_sec Usage
+ *
+ * \subsection chan_create_sec Creating a channel object
+ *
+ * Channel objects can be created in various ways, but the preferred way is
+ * trough Account channel creation methods such as Account::ensureTextChat(),
+ * Account::createFileTransfer(), which uses the channel dispatcher.
+ *
+ * If you already know the object path, you can just call create().
+ * For example:
+ *
+ * \code
+ *
+ * ChannelPtr chan = Channel::create(connection, objectPath,
+ * immutableProperties);
+ *
+ * \endcode
+ *
+ * \subsection chan_ready_sec Making channel ready to use
+ *
+ * A Channel object needs to become ready before usage, meaning that the
+ * introspection process finished and the object accessors can be used.
+ *
+ * To make the object ready, use becomeReady() and wait for the
+ * PendingOperation::finished() signal to be emitted.
+ *
+ * \code
+ *
+ * class MyClass : public QObject
+ * {
+ * QOBJECT
+ *
+ * public:
+ * MyClass(QObject *parent = 0);
+ * ~MyClass() { }
+ *
+ * private Q_SLOTS:
+ * void onChannelReady(Tp::PendingOperation*);
+ *
+ * private:
+ * ChannelPtr chan;
+ * };
+ *
+ * MyClass::MyClass(const ConnectionPtr &connection,
+ * const QString &objectPath, const QVariantMap &immutableProperties)
+ * : QObject(parent)
+ * chan(Channel::create(connection, objectPath, immutableProperties))
+ * {
+ * connect(chan->becomeReady(),
+ * SIGNAL(finished(Tp::PendingOperation*)),
+ * SLOT(onChannelReady(Tp::PendingOperation*)));
+ * }
+ *
+ * void MyClass::onChannelReady(Tp::PendingOperation *op)
+ * {
+ * if (op->isError()) {
+ * qWarning() << "Channel cannot become ready:" <<
+ * op->errorName() << "-" << op->errorMessage();
+ * return;
+ * }
+ *
+ * // Channel is now ready
+ * }
+ *
+ * \endcode
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the Channel
+ * object usable.
+ *
+ * Note that this feature must be enabled in order to use most Channel methods.
+ * See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature Channel::FeatureCore = Feature(QLatin1String(Channel::staticMetaObject.className()), 0, true);
+
+/**
+ * Feature used in order to access the conference initial invitee contacts info.
+ *
+ * \sa conferenceInitialInviteeContacts()
+ */
+const Feature Channel::FeatureConferenceInitialInviteeContacts = Feature(QLatin1String(Channel::staticMetaObject.className()), 1, true);
+
+/**
+ * Create a new Channel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A ChannelPtr object pointing to the newly created Channel object.
+ *
+ * \todo \a immutableProperties should be used to populate the corresponding accessors (such as
+ * channelType()) already on construction, not only when making FeatureCore ready (fd.o #41654)
+ */
+ChannelPtr Channel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return ChannelPtr(new Channel(connection, objectPath, immutableProperties,
+ Channel::FeatureCore));
+}
+
+/**
+ * Construct a new Channel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type. The corresponding introspectable should
+ * depend on Channel::FeatureCore.
+ */
+Channel::Channel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : StatefulDBusProxy(connection->dbusConnection(), connection->busName(),
+ objectPath, coreFeature),
+ OptionalInterfaceFactory<Channel>(this),
+ mPriv(new Private(this, connection, immutableProperties))
+{
+}
+
+/**
+ * Class destructor.
+ */
+Channel::~Channel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection owning this channel.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr Channel::connection() const
+{
+ return mPriv->connection;
+}
+
+/**
+ * Return the immutable properties of the channel.
+ *
+ * If the channel is ready (isReady(Channel::FeatureCore) returns true), the following keys are
+ * guaranteed to be present:
+ * org.freedesktop.Telepathy.Channel.ChannelType,
+ * org.freedesktop.Telepathy.Channel.TargetHandleType,
+ * org.freedesktop.Telepathy.Channel.TargetHandle and
+ * org.freedesktop.Telepathy.Channel.Requested.
+ *
+ * The keys and values in this map are defined by the \telepathy_spec,
+ * or by third-party extensions to that specification.
+ * These are the properties that cannot change over the lifetime of the
+ * channel; they're announced in the result of the request, for efficiency.
+ *
+ * \return The immutable properties as QVariantMap.
+ */
+QVariantMap Channel::immutableProperties() const
+{
+ if (isReady(Channel::FeatureCore)) {
+ QString key;
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, mPriv->channelType);
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Interfaces");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, interfaces());
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, mPriv->targetHandleType);
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, mPriv->targetHandle);
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, mPriv->targetId);
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, mPriv->requested);
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle");
+ if (!mPriv->immutableProperties.contains(key)) {
+ mPriv->immutableProperties.insert(key, mPriv->initiatorHandle);
+ }
+
+ key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorID");
+ if (!mPriv->immutableProperties.contains(key) && !mPriv->initiatorContact.isNull()) {
+ mPriv->immutableProperties.insert(key, mPriv->initiatorContact->id());
+ }
+ }
+
+ return mPriv->immutableProperties;
+}
+
+/**
+ * Return the D-Bus interface name for the type of this channel.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return The D-Bus interface name for the type of the channel.
+ */
+QString Channel::channelType() const
+{
+ // Similarly, we don't want warnings triggered when using the type interface
+ // proxies internally.
+ if (!isReady(Channel::FeatureCore) && mPriv->channelType.isEmpty()) {
+ warning() << "Channel::channelType() before the channel type has "
+ "been received";
+ }
+ else if (!isValid()) {
+ warning() << "Channel::channelType() used with channel closed";
+ }
+
+ return mPriv->channelType;
+}
+
+/**
+ * Return the type of the handle returned by targetHandle() as specified in
+ * #HandleType.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return The target handle type as #HandleType.
+ * \sa targetHandle(), targetId()
+ */
+HandleType Channel::targetHandleType() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::targetHandleType() used channel not ready";
+ }
+
+ return (HandleType) mPriv->targetHandleType;
+}
+
+/**
+ * Return the handle of the remote party with which this channel
+ * communicates.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return An integer representing the target handle, which is of the type
+ * targetHandleType() indicates.
+ * \sa targetHandleType(), targetId()
+ */
+uint Channel::targetHandle() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::targetHandle() used channel not ready";
+ }
+
+ return mPriv->targetHandle;
+}
+
+/**
+ * Return the persistent unique ID of the remote party with which this channel communicates.
+ *
+ * If targetHandleType() is #HandleTypeContact, this will be the ID of the remote contact, and
+ * similarly the unique ID of the room when targetHandleType() is #HandleTypeRoom.
+ *
+ * This is not necessarily the best identifier to display to the user, though. In particular, for
+ * contacts, their alias should be displayed instead. It can be used for matching channels and UI
+ * elements for them across reconnects, though, at which point the old channels and contacts are
+ * invalidated.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return The target identifier.
+ * \sa targetHandle(), targetContact()
+ */
+QString Channel::targetId() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::targetId() used, but the channel is not ready";
+ }
+
+ return mPriv->targetId;
+}
+
+/**
+ * Return the contact with which this channel communicates for its lifetime, if applicable.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A pointer to the Contact object, or a null ContactPtr if targetHandleType() is not
+ * #HandleTypeContact.
+ * \sa targetHandle(), targetId()
+ */
+ContactPtr Channel::targetContact() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::targetContact() used, but the channel is not ready";
+ } else if (targetHandleType() != HandleTypeContact) {
+ warning() << "Channel::targetContact() used with targetHandleType() != Contact";
+ }
+
+ return mPriv->targetContact;
+}
+
+/**
+ * Return whether this channel was created in response to a
+ * local request.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if the channel was created in response to a local request,
+ * \c false otherwise.
+ */
+bool Channel::isRequested() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::isRequested() used channel not ready";
+ }
+
+ return mPriv->requested;
+}
+
+/**
+ * Return the contact who initiated this channel.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A pointer to the Contact object representing the contact who initiated the channel,
+ * or a null ContactPtr if it can't be retrieved.
+ */
+ContactPtr Channel::initiatorContact() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::initiatorContact() used channel not ready";
+ }
+
+ return mPriv->initiatorContact;
+}
+
+/**
+ * Start an asynchronous request that this channel be closed.
+ *
+ * The returned PendingOperation object will signal the success or failure
+ * of this request; under normal circumstances, it can be expected to
+ * succeed.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa requestLeave()
+ */
+PendingOperation *Channel::requestClose()
+{
+ // Closing a channel does not make sense if it is already closed,
+ // just silently Return.
+ if (!isValid()) {
+ return new PendingSuccess(ChannelPtr(this));
+ }
+
+ return new PendingVoid(mPriv->baseInterface->Close(), ChannelPtr(this));
+}
+
+Channel::PendingLeave::PendingLeave(const ChannelPtr &chan, const QString &message,
+ ChannelGroupChangeReason reason)
+ : PendingOperation(chan)
+{
+ Q_ASSERT(chan->mPriv->group != NULL);
+
+ QDBusPendingCall call =
+ chan->mPriv->group->RemoveMembersWithReason(
+ UIntList() << chan->mPriv->groupSelfHandle,
+ message,
+ reason);
+
+ connect(chan.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ this,
+ SLOT(onChanInvalidated(Tp::DBusProxy*)));
+
+ connect(new PendingVoid(call, chan),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ this,
+ SLOT(onRemoveFinished(Tp::PendingOperation*)));
+}
+
+void Channel::PendingLeave::onChanInvalidated(Tp::DBusProxy *proxy)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ debug() << "Finishing PendingLeave successfully as the channel was invalidated";
+
+ setFinished();
+}
+
+void Channel::PendingLeave::onRemoveFinished(Tp::PendingOperation *op)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ ChannelPtr chan = ChannelPtr::staticCast(_object());
+
+ if (op->isValid()) {
+ debug() << "We left the channel" << chan->objectPath();
+
+ ContactPtr c = chan->groupSelfContact();
+
+ if (chan->groupContacts().contains(c)
+ || chan->groupLocalPendingContacts().contains(c)
+ || chan->groupRemotePendingContacts().contains(c)) {
+ debug() << "Waiting for self remove to be picked up";
+ connect(chan.data(),
+ SIGNAL(groupMembersChanged(Tp::Contacts,Tp::Contacts,Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ this,
+ SLOT(onMembersChanged(Tp::Contacts,Tp::Contacts,Tp::Contacts,Tp::Contacts)));
+ } else {
+ setFinished();
+ }
+
+ return;
+ }
+
+ debug() << "Leave RemoveMembersWithReason failed with " << op->errorName() << op->errorMessage()
+ << "- falling back to Close";
+
+ // If the channel has been closed or otherwise invalidated already in this mainloop iteration,
+ // the requestClose() operation will early-succeed
+ connect(chan->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ this,
+ SLOT(onCloseFinished(Tp::PendingOperation*)));
+}
+
+void Channel::PendingLeave::onMembersChanged(const Tp::Contacts &, const Tp::Contacts &,
+ const Tp::Contacts &, const Tp::Contacts &removed)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ ChannelPtr chan = ChannelPtr::staticCast(_object());
+ ContactPtr c = chan->groupSelfContact();
+
+ if (removed.contains(c)) {
+ debug() << "Leave event picked up for" << chan->objectPath();
+ setFinished();
+ }
+}
+
+void Channel::PendingLeave::onCloseFinished(Tp::PendingOperation *op)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ ChannelPtr chan = ChannelPtr::staticCast(_object());
+
+ if (op->isError()) {
+ warning() << "Closing the channel" << chan->objectPath()
+ << "as a fallback for leaving it failed with"
+ << op->errorName() << op->errorMessage() << "- so didn't leave";
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ } else {
+ debug() << "We left (by closing) the channel" << chan->objectPath();
+ setFinished();
+ }
+}
+
+/**
+ * Start an asynchronous request to leave this channel as gracefully as possible.
+ *
+ * If leaving any more gracefully is not possible, this will revert to the same as requestClose().
+ * In particular, this will be the case for channels with no group interface
+ * (#TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP not in the list returned by interfaces()).
+ *
+ * The returned PendingOperation object will signal the success or failure
+ * of this request; under normal circumstances, it can be expected to
+ * succeed.
+ *
+ * A message and a reason may be provided along with the request, which will be sent to the server
+ * if supported, which is indicated by #ChannelGroupFlagMessageDepart and/or
+ * #ChannelGroupFlagMessageReject.
+ *
+ * Attempting to leave again when we have already left, either by our request or forcibly, will be a
+ * no-op, with the returned PendingOperation immediately finishing successfully.
+ *
+ * \param message The message, which can be blank if desired.
+ * \param reason A reason for leaving.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *Channel::requestLeave(const QString &message, ChannelGroupChangeReason reason)
+{
+ // Leaving a channel does not make sense if it is already closed,
+ // just silently Return.
+ if (!isValid()) {
+ return new PendingSuccess(ChannelPtr(this));
+ }
+
+ if (!isReady(Channel::FeatureCore)) {
+ return new PendingFailure(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("Channel::FeatureCore must be ready to leave a channel"),
+ ChannelPtr(this));
+ }
+
+ if (!interfaces().contains(TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP)) {
+ return requestClose();
+ }
+
+ ContactPtr self = groupSelfContact();
+
+ if (!groupContacts().contains(self) && !groupLocalPendingContacts().contains(self)
+ && !groupRemotePendingContacts().contains(self)) {
+ debug() << "Channel::requestLeave() called for " << objectPath() <<
+ "which we aren't a member of";
+ return new PendingSuccess(ChannelPtr(this));
+ }
+
+ return new PendingLeave(ChannelPtr(this), message, reason);
+}
+
+/**
+ * \name Group interface
+ *
+ * Cached access to state of the group interface on the associated remote
+ * object, if the interface is present.
+ *
+ * Some methods can be used when targetHandleType() == #HandleTypeContact, such
+ * as groupFlags(), groupCanAddContacts(), groupCanRemoveContacts(),
+ * groupSelfContact() and groupContacts().
+ *
+ * As the group interface state can change freely during the lifetime of the
+ * channel due to events like new contacts joining the group, the cached state
+ * is automatically kept in sync with the remote object's state by hooking
+ * to the change notification signals present in the D-Bus interface.
+ *
+ * As the cached value changes, change notification signals are emitted.
+ *
+ * Signals such as groupMembersChanged(), groupSelfContactChanged(), etc., are emitted to
+ * indicate that properties have changed.
+ *
+ * Check the individual signals' descriptions for details.
+ */
+
+//@{
+
+/**
+ * Return a set of flags indicating the capabilities and behaviour of the
+ * group on this channel.
+ *
+ * Change notification is via the groupFlagsChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return The bitfield combination of flags as #ChannelGroupFlags.
+ * \sa groupFlagsChanged()
+ */
+ChannelGroupFlags Channel::groupFlags() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupFlags() used channel not ready";
+ }
+
+ return (ChannelGroupFlags) mPriv->groupFlags;
+}
+
+/**
+ * Return whether contacts can be added or invited to this channel.
+ *
+ * Change notification is via the groupCanAddContactsChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if contacts can be added or invited to the channel,
+ * \c false otherwise.
+ * \sa groupFlags(), groupAddContacts()
+ */
+bool Channel::groupCanAddContacts() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanAddContacts() used channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagCanAdd;
+}
+
+/**
+ * Return whether a message is expected when adding/inviting contacts, who
+ * are not already members, to this channel.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if a message is expected, \c false otherwise.
+ * \sa groupFlags(), groupAddContacts()
+ */
+bool Channel::groupCanAddContactsWithMessage() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanAddContactsWithMessage() used when channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagMessageAdd;
+}
+
+/**
+ * Return whether a message is expected when accepting contacts' requests to
+ * join this channel.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if a message is expected, \c false otherwise.
+ * \sa groupFlags(), groupAddContacts()
+ */
+bool Channel::groupCanAcceptContactsWithMessage() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanAcceptContactsWithMessage() used when channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagMessageAccept;
+}
+
+/**
+ * Add contacts to this channel.
+ *
+ * Contacts on the local pending list (those waiting for permission to join
+ * the channel) can always be added. If groupCanAcceptContactsWithMessage()
+ * returns \c true, an optional message is expected when doing this; if not,
+ * the message parameter is likely to be ignored (so the user should not be
+ * asked for a message, and the message parameter should be left empty).
+ *
+ * Other contacts can only be added if groupCanAddContacts() returns \c true.
+ * If groupCanAddContactsWithMessage() returns \c true, an optional message is
+ * expected when doing this, and if not, the message parameter is likely to be
+ * ignored.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \param contacts Contacts to be added.
+ * \param message A string message, which can be blank if desired.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa groupCanAddContacts(), groupCanAddContactsWithMessage(), groupCanAcceptContactsWithMessage()
+ */
+PendingOperation *Channel::groupAddContacts(const QList<ContactPtr> &contacts,
+ const QString &message)
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupAddContacts() used channel not ready";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ ChannelPtr(this));
+ } else if (contacts.isEmpty()) {
+ warning() << "Channel::groupAddContacts() used with empty contacts param";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("contacts cannot be an empty list"),
+ ChannelPtr(this));
+ }
+
+ foreach (const ContactPtr &contact, contacts) {
+ if (!contact) {
+ warning() << "Channel::groupAddContacts() used but contacts param contains "
+ "invalid contact";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Unable to add invalid contacts"),
+ ChannelPtr(this));
+ }
+ }
+
+ if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupAddContacts() used with no group interface";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Channel does not support group interface"),
+ ChannelPtr(this));
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+ return new PendingVoid(mPriv->group->AddMembers(handles, message), ChannelPtr(this));
+}
+
+/**
+ * Return whether contacts in groupRemotePendingContacts() can be removed from
+ * this channel (i.e. whether an invitation can be rescinded).
+ *
+ * Change notification is via the groupCanRescindContactsChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if contacts can be removed, \c false otherwise.
+ * \sa groupFlags(), groupRemoveContacts()
+ */
+bool Channel::groupCanRescindContacts() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanRescindContacts() used channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagCanRescind;
+}
+
+/**
+ * Return whether a message is expected when removing contacts who are in
+ * groupRemotePendingContacts() from this channel (i.e. rescinding an
+ * invitation).
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if a message is expected, \c false otherwise.
+ * \sa groupFlags(), groupRemoveContacts()
+ */
+bool Channel::groupCanRescindContactsWithMessage() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanRescindContactsWithMessage() used when channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagMessageRescind;
+}
+
+/**
+ * Return if contacts in groupContacts() can be removed from this channel.
+ *
+ * Note that contacts in local pending lists, and the groupSelfContact(), can
+ * always be removed from the channel.
+ *
+ * Change notification is via the groupCanRemoveContactsChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if contacts can be removed, \c false otherwise.
+ * \sa groupFlags(), groupRemoveContacts()
+ */
+bool Channel::groupCanRemoveContacts() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanRemoveContacts() used channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagCanRemove;
+}
+
+/**
+ * Return whether a message is expected when removing contacts who are in
+ * groupContacts() from this channel.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if a message is expected, \c false otherwise.
+ * \sa groupFlags(), groupRemoveContacts()
+ */
+bool Channel::groupCanRemoveContactsWithMessage() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanRemoveContactsWithMessage() used when channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagMessageRemove;
+}
+
+/**
+ * Return whether a message is expected when removing contacts who are in
+ * groupLocalPendingContacts() from this channel (i.e. rejecting a request to
+ * join).
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if a message is expected, \c false otherwise.
+ * \sa groupFlags(), groupRemoveContacts()
+ */
+bool Channel::groupCanRejectContactsWithMessage() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanRejectContactsWithMessage() used when channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagMessageReject;
+}
+
+/**
+ * Return whether a message is expected when removing the groupSelfContact()
+ * from this channel (i.e. departing from the channel).
+ *
+ * \return \c true if a message is expected, \c false otherwise.
+ * \sa groupFlags(), groupRemoveContacts()
+ */
+bool Channel::groupCanDepartWithMessage() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupCanDepartWithMessage() used when channel not ready";
+ }
+
+ return mPriv->groupFlags & ChannelGroupFlagMessageDepart;
+}
+
+/**
+ * Remove contacts from this channel.
+ *
+ * Contacts on the local pending list (those waiting for permission to join
+ * the channel) can always be removed. If groupCanRejectContactsWithMessage()
+ * returns \c true, an optional message is expected when doing this; if not,
+ * the message parameter is likely to be ignored (so the user should not be
+ * asked for a message, and the message parameter should be left empty).
+ *
+ * The groupSelfContact() can also always be removed, as a way to leave the
+ * group with an optional departure message and/or departure reason indication.
+ * If groupCanDepartWithMessage() returns \c true, an optional message is
+ * expected when doing this, and if not, the message parameter is likely to
+ * be ignored.
+ *
+ * Contacts in the group can only be removed (e.g. kicked) if
+ * groupCanRemoveContacts() returns \c true. If
+ * groupCanRemoveContactsWithMessage() returns \c true, an optional message is
+ * expected when doing this, and if not, the message parameter is likely to be
+ * ignored.
+ *
+ * Contacts in the remote pending list (those who have been invited to the
+ * channel) can only be removed (have their invitations rescinded) if
+ * groupCanRescindContacts() returns \c true. If
+ * groupCanRescindContactsWithMessage() returns \c true, an optional message is
+ * expected when doing this, and if not, the message parameter is likely to be
+ * ignored.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \param contacts Contacts to be removed.
+ * \param message A string message, which can be blank if desired.
+ * \param reason Reason of the change, as specified in
+ * #ChannelGroupChangeReason
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa groupCanRemoveContacts(), groupCanRemoveContactsWithMessage(),
+ * groupCanRejectContactsWithMessage(), groupCanRescindContacts(),
+ * groupCanRescindContacts(), groupCanRescindContactsWithMessage(),
+ * groupCanDepartWithMessage()
+ */
+PendingOperation *Channel::groupRemoveContacts(const QList<ContactPtr> &contacts,
+ const QString &message, ChannelGroupChangeReason reason)
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupRemoveContacts() used channel not ready";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ ChannelPtr(this));
+ }
+
+ if (contacts.isEmpty()) {
+ warning() << "Channel::groupRemoveContacts() used with empty contacts param";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("contacts param cannot be an empty list"),
+ ChannelPtr(this));
+ }
+
+ foreach (const ContactPtr &contact, contacts) {
+ if (!contact) {
+ warning() << "Channel::groupRemoveContacts() used but contacts param contains "
+ "invalid contact:";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Unable to remove invalid contacts"),
+ ChannelPtr(this));
+ }
+ }
+
+ if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupRemoveContacts() used with no group interface";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Channel does not support group interface"),
+ ChannelPtr(this));
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+ return new PendingVoid(
+ mPriv->group->RemoveMembersWithReason(handles, message, reason),
+ ChannelPtr(this));
+}
+
+/**
+ * Return the current contacts of the group.
+ *
+ * Change notification is via the groupMembersChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A set of pointers to the Contact objects.
+ * \sa groupLocalPendingContacts(), groupRemotePendingContacts()
+ */
+Contacts Channel::groupContacts() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupMembers() used channel not ready";
+ }
+
+ return mPriv->groupContacts.values().toSet();
+}
+
+/**
+ * Return the contacts currently waiting for local approval to join the
+ * group.
+ *
+ * Change notification is via the groupMembersChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A set of pointers to the Contact objects.
+ * \sa groupContacts(), groupRemotePendingContacts()
+ */
+Contacts Channel::groupLocalPendingContacts() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupLocalPendingContacts() used channel not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupLocalPendingContacts() used with no group interface";
+ }
+
+ return mPriv->groupLocalPendingContacts.values().toSet();
+}
+
+/**
+ * Return the contacts currently waiting for remote approval to join the
+ * group.
+ *
+ * Change notification is via the groupMembersChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A set of pointers to the Contact objects.
+ * \sa groupContacts(), groupLocalPendingContacts()
+ */
+Contacts Channel::groupRemotePendingContacts() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupRemotePendingContacts() used channel not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupRemotePendingContacts() used with no "
+ "group interface";
+ }
+
+ return mPriv->groupRemotePendingContacts.values().toSet();
+}
+
+/**
+ * Return information of a local pending contact change. If
+ * no information is available, an object for which
+ * GroupMemberChangeDetails::isValid() returns <code>false</code> is returned.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \param contact A Contact object that is on the local pending contacts list.
+ * \return The change info as a GroupMemberChangeDetails object.
+ */
+Channel::GroupMemberChangeDetails Channel::groupLocalPendingContactChangeInfo(
+ const ContactPtr &contact) const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupLocalPendingContactChangeInfo() used channel not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupLocalPendingContactChangeInfo() used with no group interface";
+ } else if (!contact) {
+ warning() << "Channel::groupLocalPendingContactChangeInfo() used with null contact param";
+ return GroupMemberChangeDetails();
+ }
+
+ uint handle = contact->handle()[0];
+ return mPriv->groupLocalPendingContactsChangeInfo.value(handle);
+}
+
+/**
+ * Return information on the removal of the local user from the group. If
+ * the user hasn't been removed from the group, an object for which
+ * GroupMemberChangeDetails::isValid() returns <code>false</code> is returned.
+ *
+ * This method should be called only after you've left the channel.
+ * This is useful for getting the remove information after missing the
+ * corresponding groupMembersChanged() signal, as the local user being
+ * removed usually causes the channel to be closed.
+ *
+ * The returned information is not guaranteed to be correct if
+ * groupIsSelfHandleTracked() returns false and a self handle change has
+ * occurred on the remote object.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return The remove info as a GroupMemberChangeDetails object.
+ */
+Channel::GroupMemberChangeDetails Channel::groupSelfContactRemoveInfo() const
+{
+ // Oftentimes, the channel will be closed as a result from being left - so checking a channel's
+ // self remove info when it has been closed and hence invalidated is valid
+ if (isValid() && !isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupSelfContactRemoveInfo() used before Channel::FeatureCore is ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupSelfContactRemoveInfo() used with "
+ "no group interface";
+ }
+
+ return mPriv->groupSelfContactRemoveInfo;
+}
+
+/**
+ * Return whether globally valid handles can be looked up using the
+ * channel-specific handle on this channel using this object.
+ *
+ * Handle owner lookup is only available if:
+ * <ul>
+ * <li>The object is ready
+ * <li>The list returned by interfaces() contains #TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP</li>
+ * <li>The set of flags returned by groupFlags() contains
+ * #GroupFlagProperties and #GroupFlagChannelSpecificHandles</li>
+ * </ul>
+ *
+ * If this function returns \c false, the return value of
+ * groupHandleOwners() is undefined and groupHandleOwnersChanged() will
+ * never be emitted.
+ *
+ * The value returned by this function will stay fixed for the entire time
+ * the object is ready, so no change notification is provided.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if handle owner lookup functionality is available, \c false otherwise.
+ */
+bool Channel::groupAreHandleOwnersAvailable() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupAreHandleOwnersAvailable() used channel not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupAreHandleOwnersAvailable() used with "
+ "no group interface";
+ }
+
+ return mPriv->groupAreHandleOwnersAvailable;
+}
+
+/**
+ * Return a mapping of handles specific to this channel to globally valid
+ * handles.
+ *
+ * The mapping includes at least all of the channel-specific handles in this
+ * channel's members, local-pending and remote-pending sets as keys. Any
+ * handle not in the keys of this mapping is not channel-specific in this
+ * channel. Handles which are channel-specific, but for which the owner is
+ * unknown, appear in this mapping with 0 as owner.
+ *
+ * Change notification is via the groupHandleOwnersChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A mapping from group-specific handles to globally valid handles.
+ */
+HandleOwnerMap Channel::groupHandleOwners() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupHandleOwners() used channel not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupAreHandleOwnersAvailable() used with no "
+ "group interface";
+ }
+ else if (!groupAreHandleOwnersAvailable()) {
+ warning() << "Channel::groupAreHandleOwnersAvailable() used, but handle "
+ "owners not available";
+ }
+
+ return mPriv->groupHandleOwners;
+}
+
+/**
+ * Return whether the value returned by groupSelfContact() is guaranteed to
+ * accurately represent the local user even after nickname changes, etc.
+ *
+ * This should always be \c true for new services implementing the group interface.
+ *
+ * Older services not providing group properties don't necessarily
+ * emit the SelfHandleChanged signal either, so self contact changes can't be
+ * reliably tracked.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if changes to the self contact are tracked, \c false otherwise.
+ */
+bool Channel::groupIsSelfContactTracked() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupIsSelfHandleTracked() used channel not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) {
+ warning() << "Channel::groupIsSelfHandleTracked() used with "
+ "no group interface";
+ }
+
+ return mPriv->groupIsSelfHandleTracked;
+}
+
+/**
+ * Return a Contact object representing the user in the group if at all possible, otherwise a
+ * Contact object representing the user globally.
+ *
+ * Change notification is via the groupSelfContactChanged() signal.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A pointer to the Contact object.
+ */
+ContactPtr Channel::groupSelfContact() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupSelfContact() used channel not ready";
+ }
+
+ return mPriv->groupSelfContact;
+}
+
+/**
+ * Return whether the local user is in the "local pending" state. This
+ * indicates that the local user needs to take action to accept an invitation,
+ * an incoming call, etc.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if local user is in the channel's local-pending set, \c false otherwise.
+ */
+bool Channel::groupSelfHandleIsLocalPending() const
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupSelfHandleIsLocalPending() used when "
+ "channel not ready";
+ return false;
+ }
+
+ return mPriv->groupLocalPendingContacts.contains(mPriv->groupSelfHandle);
+}
+
+/**
+ * Attempt to add the local user to this channel. In some channel types,
+ * such as Text and StreamedMedia, this is used to accept an invitation or an
+ * incoming call.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *Channel::groupAddSelfHandle()
+{
+ if (!isReady(Channel::FeatureCore)) {
+ warning() << "Channel::groupAddSelfHandle() used when channel not "
+ "ready";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Channel object not ready"),
+ ChannelPtr(this));
+ }
+
+ UIntList handles;
+
+ if (mPriv->groupSelfHandle == 0) {
+ handles << mPriv->connection->selfHandle();
+ } else {
+ handles << mPriv->groupSelfHandle;
+ }
+
+ return new PendingVoid(
+ mPriv->group->AddMembers(handles, QLatin1String("")),
+ ChannelPtr(this));
+}
+
+//@}
+
+/**
+ * Return whether this channel implements the conference interface
+ * (#TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE is in the list returned by interfaces()).
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if the conference interface is supported, \c false otherwise.
+ */
+bool Channel::isConference() const
+{
+ return hasInterface(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE);
+}
+
+/**
+ * Return a list of contacts invited to this conference when it was created.
+ *
+ * This method requires Channel::FeatureConferenceInitialInviteeContacts to be ready.
+ *
+ * \return A set of pointers to the Contact objects.
+ */
+Contacts Channel::conferenceInitialInviteeContacts() const
+{
+ return mPriv->conferenceInitialInviteeContacts;
+}
+
+/**
+ * Return the individual channels that are part of this conference.
+ *
+ * Change notification is via the conferenceChannelMerged() and
+ * conferenceChannelRemoved() signals.
+ *
+ * Note that the returned channels are not guaranteed to be ready. Calling
+ * Channel::becomeReady() may be needed.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A list of pointers to Channel objects containing all channels in the conference.
+ * \sa conferenceInitialChannels(), conferenceOriginalChannels()
+ */
+QList<ChannelPtr> Channel::conferenceChannels() const
+{
+ return mPriv->conferenceChannels.values();
+}
+
+/**
+ * Return the initial value of conferenceChannels().
+ *
+ * Note that the returned channels are not guaranteed to be ready. Calling
+ * Channel::becomeReady() may be needed.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A list of pointers to Channel objects containing all channels that were initially
+ * part of the conference.
+ * \sa conferenceChannels(), conferenceOriginalChannels()
+ */
+QList<ChannelPtr> Channel::conferenceInitialChannels() const
+{
+ return mPriv->conferenceInitialChannels.values();
+}
+
+/**
+ * Return a map between channel specific handles and the corresponding channels of this conference.
+ *
+ * This method is only relevant on GSM conference calls where it is possible to have the same phone
+ * number in a conference twice; for instance, it could be the number of a corporate switchboard.
+ * This is represented using channel-specific handles; whether or not a channel uses
+ * channel-specific handles is reported in groupFlags(). The groupHandleOwners() specifies the
+ * mapping from opaque channel-specific handles to actual numbers; this property specifies the
+ * original 1-1 channel corresponding to each channel-specific handle in the conference.
+ *
+ * In protocols where this situation cannot arise, such as XMPP, this method will return an empty
+ * hash.
+ *
+ * Example, consider this situation:
+ * 1. Place a call (with path /call/to/simon) to the contact +441234567890 (which is assigned the
+ * handle h, say), and ask to be put through to Simon McVittie;
+ * 2. Put that call on hold;
+ * 3. Place another call (with path /call/to/jonny) to +441234567890, and ask to be put through to
+ * Jonny Lamb;
+ * 4. Request a new conference channel with initial channels: ['/call/to/simon', '/call/to/jonny'].
+ *
+ * The new channel will have the following properties, for some handles s and j:
+ *
+ * {
+ * groupFlags(): ChannelGroupFlagChannelSpecificHandles | (other flags),
+ * groupMembers(): [self handle, s, j],
+ * groupHandleOwners(): { s: h, j: h },
+ * conferenceInitialChannels(): ['/call/to/simon', '/call/to/jonny'],
+ * conferenceChannels(): ['/call/to/simon', '/call/to/jonny'],
+ * conferenceOriginalChannels(): { s: '/call/to/simon', j: '/call/to/jonny' },
+ * # ...
+ * }
+ *
+ * Note that the returned channels are not guaranteed to be ready. Calling
+ * Channel::becomeReady() may be needed.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A map of channel specific handles to pointers to Channel objects.
+ * \sa conferenceChannels(), conferenceInitialChannels()
+ */
+QHash<uint, ChannelPtr> Channel::conferenceOriginalChannels() const
+{
+ return mPriv->conferenceOriginalChannels;
+}
+
+/**
+ * Return whether this channel supports conference merging using conferenceMergeChannel().
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if the interface is supported, \c false otherwise.
+ * \sa conferenceMergeChannel()
+ */
+bool Channel::supportsConferenceMerging() const
+{
+ return interfaces().contains(QLatin1String(
+ TP_FUTURE_INTERFACE_CHANNEL_INTERFACE_MERGEABLE_CONFERENCE));
+}
+
+/**
+ * Request that the given channel be incorporated into this channel.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa supportsConferenceMerging()
+ */
+PendingOperation *Channel::conferenceMergeChannel(const ChannelPtr &channel)
+{
+ if (!supportsConferenceMerging()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Channel does not support MergeableConference interface"),
+ ChannelPtr(this));
+ }
+
+ return new PendingVoid(mPriv->mergeableConferenceInterface()->Merge(
+ QDBusObjectPath(channel->objectPath())),
+ ChannelPtr(this));
+}
+
+/**
+ * Return whether this channel supports splitting using conferenceSplitChannel().
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if the interface is supported, \c false otherwise.
+ * \sa conferenceSplitChannel()
+ */
+bool Channel::supportsConferenceSplitting() const
+{
+ return interfaces().contains(QLatin1String(
+ TP_FUTURE_INTERFACE_CHANNEL_INTERFACE_SPLITTABLE));
+}
+
+/**
+ * Request that this channel is removed from any conference of which it is
+ * a part.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa supportsConferenceSplitting()
+ */
+PendingOperation *Channel::conferenceSplitChannel()
+{
+ if (!supportsConferenceSplitting()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Channel does not support Splittable interface"),
+ ChannelPtr(this));
+ }
+
+ return new PendingVoid(mPriv->splittableInterface()->Split(), ChannelPtr(this));
+}
+
+/**
+ * Return the Client::ChannelInterface interface proxy object for this channel.
+ * This method is protected since the convenience methods provided by this
+ * class should generally be used instead of calling D-Bus methods
+ * directly.
+ *
+ * \return A pointer to the existing Client::ChannelInterface object for this
+ * Channel object.
+ */
+Client::ChannelInterface *Channel::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+void Channel::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties::GetAll(Channel)";
+ props = reply.value();
+ } else {
+ warning().nospace() << "Properties::GetAll(Channel) failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ }
+
+ mPriv->extractMainProps(props);
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotChannelType(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QString> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel::GetChannelType() failed with " <<
+ reply.error().name() << ": " << reply.error().message() <<
+ ", Channel officially dead";
+ invalidate(reply.error());
+ return;
+ }
+
+ debug() << "Got reply to fallback Channel::GetChannelType()";
+ mPriv->channelType = reply.value();
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotHandle(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<uint, uint> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel::GetHandle() failed with " <<
+ reply.error().name() << ": " << reply.error().message() <<
+ ", Channel officially dead";
+ invalidate(reply.error());
+ return;
+ }
+
+ debug() << "Got reply to fallback Channel::GetHandle()";
+ mPriv->targetHandleType = reply.argumentAt<0>();
+ mPriv->targetHandle = reply.argumentAt<1>();
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotInterfaces(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QStringList> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel::GetInterfaces() failed with " <<
+ reply.error().name() << ": " << reply.error().message() <<
+ ", Channel officially dead";
+ invalidate(reply.error());
+ return;
+ }
+
+ debug() << "Got reply to fallback Channel::GetInterfaces()";
+ setInterfaces(reply.value());
+ mPriv->readinessHelper->setInterfaces(interfaces());
+ mPriv->nowHaveInterfaces();
+
+ mPriv->fakeGroupInterfaceIfNeeded();
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::onClosed()
+{
+ debug() << "Got Channel::Closed";
+
+ QString error;
+ QString message;
+ if (mPriv->groupSelfContactRemoveInfo.isValid() &&
+ mPriv->groupSelfContactRemoveInfo.hasReason()) {
+ error = mPriv->groupMemberChangeDetailsTelepathyError(
+ mPriv->groupSelfContactRemoveInfo);
+ message = mPriv->groupSelfContactRemoveInfo.message();
+ } else {
+ error = TP_QT4_ERROR_CANCELLED;
+ message = QLatin1String("channel closed");
+ }
+
+ invalidate(error, message);
+}
+
+void Channel::onConnectionReady(PendingOperation *op)
+{
+ if (op->isError()) {
+ invalidate(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ // FIXME: should connect to selfHandleChanged and act accordingly, but that is a PITA for
+ // keeping the Contacts built and even if we don't do it, the new code is better than the
+ // old one anyway because earlier on we just wouldn't have had a self contact.
+ //
+ // besides, the only thing which breaks without connecting in the world likely is if you're
+ // using idle and decide to change your nick, which I don't think we necessarily even have API
+ // to do from tp-qt4 anyway (or did I make idle change the nick when setting your alias? can't
+ // remember)
+ //
+ // Simply put, I just don't care ATM.
+
+ // Will be overwritten by the group self handle, if we can discover any.
+ Q_ASSERT(!mPriv->groupSelfHandle);
+ mPriv->groupSelfHandle = mPriv->connection->selfHandle();
+
+ mPriv->introspectMainProperties();
+}
+
+void Channel::onConnectionInvalidated()
+{
+ debug() << "Owning connection died leaving an orphan Channel, "
+ "changing to closed";
+ invalidate(QLatin1String(TP_QT4_ERROR_ORPHANED),
+ QLatin1String("Connection given as the owner of this channel was invalidated"));
+}
+
+void Channel::gotGroupProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties::GetAll(Channel.Interface.Group)";
+ props = reply.value();
+ }
+ else {
+ warning().nospace() << "Properties::GetAll(Channel.Interface.Group) "
+ "failed with " << reply.error().name() << ": " <<
+ reply.error().message();
+ }
+
+ mPriv->extract0176GroupProps(props);
+ // Add extraction (and possible fallbacks) in similar functions, called from here
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotGroupFlags(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<uint> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel.Interface.Group::GetGroupFlags() failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ }
+ else {
+ debug() << "Got reply to fallback Channel.Interface.Group::GetGroupFlags()";
+ mPriv->setGroupFlags(reply.value());
+
+ if (mPriv->groupFlags & ChannelGroupFlagProperties) {
+ warning() << " Reply included ChannelGroupFlagProperties, even "
+ "though properties specified in 0.17.7 didn't work! - unsetting";
+ mPriv->groupFlags &= ~ChannelGroupFlagProperties;
+ }
+ }
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotAllMembers(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<UIntList, UIntList, UIntList> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel.Interface.Group::GetAllMembers() failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ } else {
+ debug() << "Got reply to fallback Channel.Interface.Group::GetAllMembers()";
+
+ mPriv->groupInitialMembers = reply.argumentAt<0>();
+ mPriv->groupInitialRP = reply.argumentAt<2>();
+
+ foreach (uint handle, reply.argumentAt<1>()) {
+ LocalPendingInfo info = {handle, 0, ChannelGroupChangeReasonNone,
+ QLatin1String("")};
+ mPriv->groupInitialLP.push_back(info);
+ }
+ }
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotLocalPendingMembersWithInfo(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<LocalPendingInfoList> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel.Interface.Group::GetLocalPendingMembersWithInfo() "
+ "failed with " << reply.error().name() << ": " << reply.error().message();
+ warning() << " Falling back to what GetAllMembers returned with no extended info";
+ }
+ else {
+ debug() << "Got reply to fallback "
+ "Channel.Interface.Group::GetLocalPendingMembersWithInfo()";
+ // Overrides the previous vague list provided by gotAllMembers
+ mPriv->groupInitialLP = reply.value();
+ }
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotSelfHandle(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<uint> reply = *watcher;
+
+ if (reply.isError()) {
+ warning().nospace() << "Channel.Interface.Group::GetSelfHandle() failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ } else {
+ debug() << "Got reply to fallback Channel.Interface.Group::GetSelfHandle()";
+ // Don't overwrite the self handle we got from the connection with 0
+ if (reply.value()) {
+ mPriv->groupSelfHandle = reply.value();
+ }
+ }
+
+ mPriv->nowHaveInitialMembers();
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotContacts(PendingOperation *op)
+{
+ PendingContacts *pending = qobject_cast<PendingContacts *>(op);
+
+ mPriv->buildingContacts = false;
+
+ QList<ContactPtr> contacts;
+ if (pending->isValid()) {
+ contacts = pending->contacts();
+
+ if (!pending->invalidHandles().isEmpty()) {
+ warning() << "Unable to construct Contact objects for handles:" <<
+ pending->invalidHandles();
+
+ if (mPriv->groupSelfHandle &&
+ pending->invalidHandles().contains(mPriv->groupSelfHandle)) {
+ warning() << "Unable to retrieve self contact";
+ mPriv->groupSelfContact.reset();
+ emit groupSelfContactChanged();
+ }
+ }
+ } else {
+ warning().nospace() << "Getting contacts failed with " <<
+ pending->errorName() << ":" << pending->errorMessage();
+ }
+
+ mPriv->updateContacts(contacts);
+}
+
+void Channel::onGroupFlagsChanged(uint added, uint removed)
+{
+ debug().nospace() << "Got Channel.Interface.Group::GroupFlagsChanged(" <<
+ hex << added << ", " << removed << ")";
+
+ added &= ~(mPriv->groupFlags);
+ removed &= mPriv->groupFlags;
+
+ debug().nospace() << "Arguments after filtering (" << hex << added <<
+ ", " << removed << ")";
+
+ uint groupFlags = mPriv->groupFlags;
+ groupFlags |= added;
+ groupFlags &= ~removed;
+ // just emit groupFlagsChanged and related signals if the flags really
+ // changed and we are ready
+ if (mPriv->setGroupFlags(groupFlags) && isReady(Channel::FeatureCore)) {
+ debug() << "Emitting groupFlagsChanged with" << mPriv->groupFlags <<
+ "value" << added << "added" << removed << "removed";
+ emit groupFlagsChanged((ChannelGroupFlags) mPriv->groupFlags,
+ (ChannelGroupFlags) added, (ChannelGroupFlags) removed);
+
+ if (added & ChannelGroupFlagCanAdd ||
+ removed & ChannelGroupFlagCanAdd) {
+ debug() << "Emitting groupCanAddContactsChanged";
+ emit groupCanAddContactsChanged(groupCanAddContacts());
+ }
+
+ if (added & ChannelGroupFlagCanRemove ||
+ removed & ChannelGroupFlagCanRemove) {
+ debug() << "Emitting groupCanRemoveContactsChanged";
+ emit groupCanRemoveContactsChanged(groupCanRemoveContacts());
+ }
+
+ if (added & ChannelGroupFlagCanRescind ||
+ removed & ChannelGroupFlagCanRescind) {
+ debug() << "Emitting groupCanRescindContactsChanged";
+ emit groupCanRescindContactsChanged(groupCanRescindContacts());
+ }
+ }
+}
+
+void Channel::onMembersChanged(const QString &message,
+ const UIntList &added, const UIntList &removed,
+ const UIntList &localPending, const UIntList &remotePending,
+ uint actor, uint reason)
+{
+ // Ignore the signal if we're using the MCD signal to not duplicate events
+ if (mPriv->usingMembersChangedDetailed) {
+ return;
+ }
+
+ debug() << "Got Channel.Interface.Group::MembersChanged with" << added.size() <<
+ "added," << removed.size() << "removed," << localPending.size() <<
+ "moved to LP," << remotePending.size() << "moved to RP," << actor <<
+ "being the actor," << reason << "the reason and" << message << "the message";
+ debug() << " synthesizing a corresponding MembersChangedDetailed signal";
+
+ QVariantMap details;
+
+ if (!message.isEmpty()) {
+ details.insert(QLatin1String("message"), message);
+ }
+
+ if (actor != 0) {
+ details.insert(QLatin1String("actor"), actor);
+ }
+
+ details.insert(QLatin1String("change-reason"), reason);
+
+ mPriv->doMembersChangedDetailed(added, removed, localPending, remotePending, details);
+}
+
+void Channel::onMembersChangedDetailed(
+ const UIntList &added, const UIntList &removed,
+ const UIntList &localPending, const UIntList &remotePending,
+ const QVariantMap &details)
+{
+ // Ignore the signal if we aren't (yet) using MCD to not duplicate events
+ if (!mPriv->usingMembersChangedDetailed) {
+ return;
+ }
+
+ debug() << "Got Channel.Interface.Group::MembersChangedDetailed with" << added.size() <<
+ "added," << removed.size() << "removed," << localPending.size() <<
+ "moved to LP," << remotePending.size() << "moved to RP and with" << details.size() <<
+ "details";
+
+ mPriv->doMembersChangedDetailed(added, removed, localPending, remotePending, details);
+}
+
+void Channel::Private::doMembersChangedDetailed(
+ const UIntList &added, const UIntList &removed,
+ const UIntList &localPending, const UIntList &remotePending,
+ const QVariantMap &details)
+{
+ if (!groupHaveMembers) {
+ debug() << "Still waiting for initial group members, "
+ "so ignoring delta signal...";
+ return;
+ }
+
+ if (added.isEmpty() && removed.isEmpty() &&
+ localPending.isEmpty() && remotePending.isEmpty()) {
+ debug() << "Nothing really changed, so skipping membersChanged";
+ return;
+ }
+
+ // let's store groupSelfContactRemoveInfo here as we may not have time
+ // to build the contacts in case self contact is removed,
+ // as Closed will be emitted right after
+ if (removed.contains(groupSelfHandle)) {
+ if (qdbus_cast<uint>(details.value(QLatin1String("change-reason"))) ==
+ ChannelGroupChangeReasonRenamed) {
+ if (removed.size() != 1 ||
+ (added.size() + localPending.size() + remotePending.size()) != 1) {
+ // spec-incompliant CM, ignoring members changed
+ warning() << "Received MembersChangedDetailed with reason "
+ "Renamed and removed.size != 1 or added.size + "
+ "localPending.size + remotePending.size != 1. Ignoring";
+ return;
+ }
+ uint newHandle = 0;
+ if (!added.isEmpty()) {
+ newHandle = added.first();
+ } else if (!localPending.isEmpty()) {
+ newHandle = localPending.first();
+ } else if (!remotePending.isEmpty()) {
+ newHandle = remotePending.first();
+ }
+ parent->onSelfHandleChanged(newHandle);
+ return;
+ }
+
+ // let's try to get the actor contact from contact manager if available
+ groupSelfContactRemoveInfo = GroupMemberChangeDetails(
+ connection->contactManager()->lookupContactByHandle(
+ qdbus_cast<uint>(details.value(QLatin1String("actor")))),
+ details);
+ }
+
+ HandleIdentifierMap contactIds = qdbus_cast<HandleIdentifierMap>(
+ details.value(GroupMembersChangedInfo::keyContactIds));
+ connection->lowlevel()->injectContactIds(contactIds);
+
+ groupMembersChangedQueue.enqueue(
+ new Private::GroupMembersChangedInfo(
+ added, removed,
+ localPending, remotePending,
+ details));
+
+ if (!buildingContacts) {
+ // if we are building contacts, we should wait it to finish so we don't
+ // present the user with wrong information
+ processMembersChanged();
+ }
+}
+
+void Channel::onHandleOwnersChanged(const HandleOwnerMap &added,
+ const UIntList &removed)
+{
+ debug() << "Got Channel.Interface.Group::HandleOwnersChanged with" <<
+ added.size() << "added," << removed.size() << "removed";
+
+ if (!mPriv->groupAreHandleOwnersAvailable) {
+ debug() << "Still waiting for initial handle owners, so ignoring "
+ "delta signal...";
+ return;
+ }
+
+ UIntList emitAdded;
+ UIntList emitRemoved;
+
+ for (HandleOwnerMap::const_iterator i = added.begin();
+ i != added.end();
+ ++i) {
+ uint handle = i.key();
+ uint global = i.value();
+
+ if (!mPriv->groupHandleOwners.contains(handle)
+ || mPriv->groupHandleOwners[handle] != global) {
+ debug() << " +++/changed" << handle << "->" << global;
+ mPriv->groupHandleOwners[handle] = global;
+ emitAdded.append(handle);
+ }
+ }
+
+ foreach (uint handle, removed) {
+ if (mPriv->groupHandleOwners.contains(handle)) {
+ debug() << " ---" << handle;
+ mPriv->groupHandleOwners.remove(handle);
+ emitRemoved.append(handle);
+ }
+ }
+
+ // just emit groupHandleOwnersChanged if it really changed and
+ // we are ready
+ if ((emitAdded.size() || emitRemoved.size()) && isReady(Channel::FeatureCore)) {
+ debug() << "Emitting groupHandleOwnersChanged with" << emitAdded.size() <<
+ "added" << emitRemoved.size() << "removed";
+ emit groupHandleOwnersChanged(mPriv->groupHandleOwners,
+ emitAdded, emitRemoved);
+ }
+}
+
+void Channel::onSelfHandleChanged(uint selfHandle)
+{
+ debug().nospace() << "Got Channel.Interface.Group::SelfHandleChanged";
+
+ if (selfHandle != mPriv->groupSelfHandle) {
+ mPriv->groupSelfHandle = selfHandle;
+ debug() << " Emitting groupSelfHandleChanged with new self handle" <<
+ selfHandle;
+
+ // FIXME: fix self contact building with no group
+ mPriv->pendingRetrieveGroupSelfContact = true;
+ }
+}
+
+void Channel::gotConferenceProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ mPriv->introspectingConference = false;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties::GetAll(Channel.Interface.Conference)";
+ props = reply.value();
+
+ ConnectionPtr conn = connection();
+ ChannelFactoryConstPtr chanFactory = conn->channelFactory();
+
+ ObjectPathList channels =
+ qdbus_cast<ObjectPathList>(props[QLatin1String("Channels")]);
+ foreach (const QDBusObjectPath &channelPath, channels) {
+ if (mPriv->conferenceChannels.contains(channelPath.path())) {
+ continue;
+ }
+
+ PendingReady *readyOp = chanFactory->proxy(conn,
+ channelPath.path(), QVariantMap());
+ ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy()));
+ Q_ASSERT(!channel.isNull());
+
+ mPriv->conferenceChannels.insert(channelPath.path(), channel);
+ }
+
+ ObjectPathList initialChannels =
+ qdbus_cast<ObjectPathList>(props[QLatin1String("InitialChannels")]);
+ foreach (const QDBusObjectPath &channelPath, initialChannels) {
+ if (mPriv->conferenceInitialChannels.contains(channelPath.path())) {
+ continue;
+ }
+
+ PendingReady *readyOp = chanFactory->proxy(conn,
+ channelPath.path(), QVariantMap());
+ ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy()));
+ Q_ASSERT(!channel.isNull());
+
+ mPriv->conferenceInitialChannels.insert(channelPath.path(), channel);
+ }
+
+ mPriv->conferenceInitialInviteeHandles =
+ qdbus_cast<UIntList>(props[QLatin1String("InitialInviteeHandles")]);
+ QStringList conferenceInitialInviteeIds =
+ qdbus_cast<QStringList>(props[QLatin1String("InitialInviteeIDs")]);
+ if (mPriv->conferenceInitialInviteeHandles.size() == conferenceInitialInviteeIds.size()) {
+ HandleIdentifierMap contactIds;
+ int i = 0;
+ foreach (uint handle, mPriv->conferenceInitialInviteeHandles) {
+ contactIds.insert(handle, conferenceInitialInviteeIds.at(i++));
+ }
+ mPriv->connection->lowlevel()->injectContactIds(contactIds);
+ }
+
+ mPriv->conferenceInvitationMessage =
+ qdbus_cast<QString>(props[QLatin1String("InvitationMessage")]);
+
+ ChannelOriginatorMap originalChannels = qdbus_cast<ChannelOriginatorMap>(
+ props[QLatin1String("OriginalChannels")]);
+ for (ChannelOriginatorMap::const_iterator i = originalChannels.constBegin();
+ i != originalChannels.constEnd(); ++i) {
+ PendingReady *readyOp = chanFactory->proxy(conn,
+ i.value().path(), QVariantMap());
+ ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy()));
+ Q_ASSERT(!channel.isNull());
+
+ mPriv->conferenceOriginalChannels.insert(i.key(), channel);
+ }
+ } else {
+ warning().nospace() << "Properties::GetAll(Channel.Interface.Conference) "
+ "failed with " << reply.error().name() << ": " <<
+ reply.error().message();
+ }
+
+ mPriv->continueIntrospection();
+}
+
+void Channel::gotConferenceInitialInviteeContacts(PendingOperation *op)
+{
+ PendingContacts *pending = qobject_cast<PendingContacts *>(op);
+
+ if (pending->isValid()) {
+ mPriv->conferenceInitialInviteeContacts = pending->contacts().toSet();
+ } else {
+ warning().nospace() << "Getting conference initial invitee contacts "
+ "failed with " << pending->errorName() << ":" <<
+ pending->errorMessage();
+ }
+
+ mPriv->readinessHelper->setIntrospectCompleted(
+ FeatureConferenceInitialInviteeContacts, true);
+}
+
+void Channel::onConferenceChannelMerged(const QDBusObjectPath &channelPath,
+ uint channelSpecificHandle, const QVariantMap &properties)
+{
+ if (mPriv->conferenceChannels.contains(channelPath.path())) {
+ return;
+ }
+
+ ConnectionPtr conn = connection();
+ ChannelFactoryConstPtr chanFactory = conn->channelFactory();
+ PendingReady *readyOp = chanFactory->proxy(conn,
+ channelPath.path(), properties);
+ ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy()));
+ Q_ASSERT(!channel.isNull());
+
+ mPriv->conferenceChannels.insert(channelPath.path(), channel);
+ emit conferenceChannelMerged(channel);
+
+ if (channelSpecificHandle != 0) {
+ mPriv->conferenceOriginalChannels.insert(channelSpecificHandle, channel);
+ }
+}
+
+void Channel::onConferenceChannelMerged(const QDBusObjectPath &channelPath)
+{
+ onConferenceChannelMerged(channelPath, 0, QVariantMap());
+}
+
+void Channel::onConferenceChannelRemoved(const QDBusObjectPath &channelPath,
+ const QVariantMap &details)
+{
+ if (!mPriv->conferenceChannels.contains(channelPath.path())) {
+ return;
+ }
+
+ HandleIdentifierMap contactIds = qdbus_cast<HandleIdentifierMap>(
+ details.value(Private::GroupMembersChangedInfo::keyContactIds));
+ mPriv->connection->lowlevel()->injectContactIds(contactIds);
+
+ mPriv->conferenceChannelRemovedQueue.enqueue(
+ new Private::ConferenceChannelRemovedInfo(channelPath, details));
+ mPriv->processConferenceChannelRemoved();
+}
+
+void Channel::onConferenceChannelRemoved(const QDBusObjectPath &channelPath)
+{
+ onConferenceChannelRemoved(channelPath, QVariantMap());
+}
+
+void Channel::gotConferenceChannelRemovedActorContact(PendingOperation *op)
+{
+ ContactPtr actorContact;
+
+ if (op) {
+ PendingContacts *pc = qobject_cast<PendingContacts *>(op);
+
+ if (pc->isValid()) {
+ Q_ASSERT(pc->contacts().size() == 1);
+ actorContact = pc->contacts().first();
+ } else {
+ warning().nospace() << "Getting conference channel removed actor "
+ "failed with " << pc->errorName() << ":" <<
+ pc->errorMessage();
+ }
+ }
+
+ Private::ConferenceChannelRemovedInfo *info = mPriv->conferenceChannelRemovedQueue.dequeue();
+
+ ChannelPtr channel = mPriv->conferenceChannels[info->channelPath.path()];
+ mPriv->conferenceChannels.remove(info->channelPath.path());
+ emit conferenceChannelRemoved(channel, GroupMemberChangeDetails(actorContact,
+ info->details));
+
+ for (QHash<uint, ChannelPtr>::iterator i = mPriv->conferenceOriginalChannels.begin();
+ i != mPriv->conferenceOriginalChannels.end();) {
+ if (i.value() == channel) {
+ i = mPriv->conferenceOriginalChannels.erase(i);
+ } else {
+ ++i;
+ }
+ }
+
+ delete info;
+
+ mPriv->buildingConferenceChannelRemovedActorContact = false;
+ mPriv->processConferenceChannelRemoved();
+}
+
+/**
+ * \fn void Channel::groupFlagsChanged(uint flags, uint added, uint removed)
+ *
+ * Emitted when the value of groupFlags() changes.
+ *
+ * \param flags The value which would now be returned by groupFlags().
+ * \param added Flags added compared to the previous value.
+ * \param removed Flags removed compared to the previous value.
+ */
+
+/**
+ * \fn void Channel::groupCanAddContactsChanged(bool canAddContacts)
+ *
+ * Emitted when the value of groupCanAddContacts() changes.
+ *
+ * \param canAddContacts Whether a contact can be added to this channel.
+ * \sa groupCanAddContacts()
+ */
+
+/**
+ * \fn void Channel::groupCanRemoveContactsChanged(bool canRemoveContacts)
+ *
+ * Emitted when the value of groupCanRemoveContacts() changes.
+ *
+ * \param canRemoveContacts Whether a contact can be removed from this channel.
+ * \sa groupCanRemoveContacts()
+ */
+
+/**
+ * \fn void Channel::groupCanRescindContactsChanged(bool canRescindContacts)
+ *
+ * Emitted when the value of groupCanRescindContacts() changes.
+ *
+ * \param canRescindContacts Whether contact invitations can be rescinded.
+ * \sa groupCanRescindContacts()
+ */
+
+/**
+ * \fn void Channel::groupMembersChanged(
+ * const Tp::Contacts &groupMembersAdded,
+ * const Tp::Contacts &groupLocalPendingMembersAdded,
+ * const Tp::Contacts &groupRemotePendingMembersAdded,
+ * const Tp::Contacts &groupMembersRemoved,
+ * const Channel::GroupMemberChangeDetails &details)
+ *
+ * Emitted when the value returned by groupContacts(), groupLocalPendingContacts() or
+ * groupRemotePendingContacts() changes.
+ *
+ * \param groupMembersAdded The contacts that were added to this channel.
+ * \param groupLocalPendingMembersAdded The local pending contacts that were
+ * added to this channel.
+ * \param groupRemotePendingMembersAdded The remote pending contacts that were
+ * added to this channel.
+ * \param groupMembersRemoved The contacts removed from this channel.
+ * \param details Additional details such as the contact requesting or causing
+ * the change.
+ */
+
+/**
+ * \fn void Channel::groupHandleOwnersChanged(const HandleOwnerMap &owners,
+ * const Tp::UIntList &added, const Tp::UIntList &removed)
+ *
+ * Emitted when the value returned by groupHandleOwners() changes.
+ *
+ * \param owners The value which would now be returned by
+ * groupHandleOwners().
+ * \param added Handles which have been added to the mapping as keys, or
+ * existing handle keys for which the mapped-to value has changed.
+ * \param removed Handles which have been removed from the mapping.
+ */
+
+/**
+ * \fn void Channel::groupSelfContactChanged()
+ *
+ * Emitted when the value returned by groupSelfContact() changes.
+ */
+
+/**
+ * \fn void Channel::conferenceChannelMerged(const Tp::ChannelPtr &channel)
+ *
+ * Emitted when a new channel is added to the value of conferenceChannels().
+ *
+ * \param channel The channel that was added to conferenceChannels().
+ */
+
+/**
+ * \fn void Channel::conferenceChannelRemoved(const Tp::ChannelPtr &channel,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * Emitted when a new channel is removed from the value of conferenceChannels().
+ *
+ * \param channel The channel that was removed from conferenceChannels().
+ * \param details The change details.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/channel.h b/qt4/TelepathyQt4/channel.h
new file mode 100644
index 000000000..025addd82
--- /dev/null
+++ b/qt4/TelepathyQt4/channel.h
@@ -0,0 +1,256 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-channel.h>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+#include <QSet>
+#include <QSharedDataPointer>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class Connection;
+class PendingOperation;
+class PendingReady;
+
+class TELEPATHY_QT4_EXPORT Channel : public StatefulDBusProxy,
+ public OptionalInterfaceFactory<Channel>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Channel)
+
+public:
+ static const Feature FeatureCore;
+ static const Feature FeatureConferenceInitialInviteeContacts;
+
+ static ChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~Channel();
+
+ ConnectionPtr connection() const;
+
+ QVariantMap immutableProperties() const;
+
+ QString channelType() const;
+
+ HandleType targetHandleType() const;
+ uint targetHandle() const;
+ QString targetId() const;
+ ContactPtr targetContact() const;
+
+ bool isRequested() const;
+ ContactPtr initiatorContact() const;
+
+ PendingOperation *requestClose();
+ PendingOperation *requestLeave(const QString &message = QString(),
+ ChannelGroupChangeReason reason = ChannelGroupChangeReasonNone);
+
+ ChannelGroupFlags groupFlags() const;
+
+ bool groupCanAddContacts() const;
+ bool groupCanAddContactsWithMessage() const;
+ bool groupCanAcceptContactsWithMessage() const;
+ PendingOperation *groupAddContacts(const QList<ContactPtr> &contacts,
+ const QString &message = QString());
+ bool groupCanRescindContacts() const;
+ bool groupCanRescindContactsWithMessage() const;
+ bool groupCanRemoveContacts() const;
+ bool groupCanRemoveContactsWithMessage() const;
+ bool groupCanRejectContactsWithMessage() const;
+ bool groupCanDepartWithMessage() const;
+ PendingOperation *groupRemoveContacts(const QList<ContactPtr> &contacts,
+ const QString &message = QString(),
+ ChannelGroupChangeReason reason = ChannelGroupChangeReasonNone);
+
+ /**
+ * TODO: have parameters on these like
+ * Contacts groupContacts(bool includeSelfContact = true);
+ */
+ Contacts groupContacts() const;
+ Contacts groupLocalPendingContacts() const;
+ Contacts groupRemotePendingContacts() const;
+
+ class GroupMemberChangeDetails
+ {
+ public:
+ GroupMemberChangeDetails();
+ GroupMemberChangeDetails(const GroupMemberChangeDetails &other);
+ ~GroupMemberChangeDetails();
+
+ GroupMemberChangeDetails &operator=(const GroupMemberChangeDetails &other);
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ bool hasActor() const;
+ ContactPtr actor() const;
+
+ bool hasReason() const { return allDetails().contains(QLatin1String("change-reason")); }
+ ChannelGroupChangeReason reason() const { return (ChannelGroupChangeReason) qdbus_cast<uint>(allDetails().value(QLatin1String("change-reason"))); }
+
+ bool hasMessage() const { return allDetails().contains(QLatin1String("message")); }
+ QString message () const { return qdbus_cast<QString>(allDetails().value(QLatin1String("message"))); }
+
+ bool hasError() const { return allDetails().contains(QLatin1String("error")); }
+ QString error() const { return qdbus_cast<QString>(allDetails().value(QLatin1String("error"))); }
+
+ bool hasDebugMessage() const { return allDetails().contains(QLatin1String("debug-message")); }
+ QString debugMessage() const { return qdbus_cast<QString>(allDetails().value(QLatin1String("debug-message"))); }
+
+ QVariantMap allDetails() const;
+
+ private:
+ friend class Channel;
+ friend class Contact;
+ friend class ContactManager;
+
+ TELEPATHY_QT4_NO_EXPORT GroupMemberChangeDetails(const ContactPtr &actor, const QVariantMap &details);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ GroupMemberChangeDetails groupLocalPendingContactChangeInfo(const ContactPtr &contact) const;
+ GroupMemberChangeDetails groupSelfContactRemoveInfo() const;
+
+ bool groupAreHandleOwnersAvailable() const;
+ HandleOwnerMap groupHandleOwners() const;
+
+ bool groupIsSelfContactTracked() const;
+ ContactPtr groupSelfContact() const;
+
+ bool isConference() const;
+ Contacts conferenceInitialInviteeContacts() const;
+ QList<ChannelPtr> conferenceChannels() const;
+ QList<ChannelPtr> conferenceInitialChannels() const;
+ QHash<uint, ChannelPtr> conferenceOriginalChannels() const;
+
+ bool supportsConferenceMerging() const;
+ PendingOperation *conferenceMergeChannel(const ChannelPtr &channel);
+
+ bool supportsConferenceSplitting() const;
+ PendingOperation *conferenceSplitChannel();
+
+Q_SIGNALS:
+ void groupFlagsChanged(Tp::ChannelGroupFlags flags,
+ Tp::ChannelGroupFlags added, Tp::ChannelGroupFlags removed);
+
+ void groupCanAddContactsChanged(bool canAddContacts);
+ void groupCanRemoveContactsChanged(bool canRemoveContacts);
+ void groupCanRescindContactsChanged(bool canRescindContacts);
+
+ void groupMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void groupHandleOwnersChanged(const Tp::HandleOwnerMap &owners,
+ const Tp::UIntList &added, const Tp::UIntList &removed);
+
+ void groupSelfContactChanged();
+
+ void conferenceChannelMerged(const Tp::ChannelPtr &channel);
+ void conferenceChannelRemoved(const Tp::ChannelPtr &channel,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+protected:
+ Channel(const ConnectionPtr &connection,const QString &objectPath,
+ const QVariantMap &immutableProperties, const Feature &coreFeature);
+
+ Client::ChannelInterface *baseInterface() const;
+
+ bool groupSelfHandleIsLocalPending() const;
+
+protected Q_SLOTS:
+ PendingOperation *groupAddSelfHandle();
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotChannelType(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotHandle(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotInterfaces(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onClosed();
+
+ TELEPATHY_QT4_NO_EXPORT void onConnectionReady(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionInvalidated();
+
+ TELEPATHY_QT4_NO_EXPORT void gotGroupProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotGroupFlags(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotAllMembers(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotLocalPendingMembersWithInfo(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotSelfHandle(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotContacts(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onGroupFlagsChanged(uint added, uint removed);
+ TELEPATHY_QT4_NO_EXPORT void onMembersChanged(const QString &message,
+ const Tp::UIntList &added, const Tp::UIntList &removed,
+ const Tp::UIntList &localPending, const Tp::UIntList &remotePending,
+ uint actor, uint reason);
+ TELEPATHY_QT4_NO_EXPORT void onMembersChangedDetailed(
+ const Tp::UIntList &added, const Tp::UIntList &removed,
+ const Tp::UIntList &localPending, const Tp::UIntList &remotePending,
+ const QVariantMap &details);
+ TELEPATHY_QT4_NO_EXPORT void onHandleOwnersChanged(const Tp::HandleOwnerMap &added, const Tp::UIntList &removed);
+ TELEPATHY_QT4_NO_EXPORT void onSelfHandleChanged(uint selfHandle);
+
+ TELEPATHY_QT4_NO_EXPORT void gotConferenceProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotConferenceInitialInviteeContacts(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onConferenceChannelMerged(const QDBusObjectPath &channel, uint channelSpecificHandle,
+ const QVariantMap &properties);
+ TELEPATHY_QT4_NO_EXPORT void onConferenceChannelMerged(const QDBusObjectPath &channel);
+ TELEPATHY_QT4_NO_EXPORT void onConferenceChannelRemoved(const QDBusObjectPath &channel, const QVariantMap &details);
+ TELEPATHY_QT4_NO_EXPORT void onConferenceChannelRemoved(const QDBusObjectPath &channel);
+ TELEPATHY_QT4_NO_EXPORT void gotConferenceChannelRemovedActorContact(Tp::PendingOperation *op);
+
+private:
+ class PendingLeave;
+ friend class PendingLeave;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::Channel::GroupMemberChangeDetails);
+
+#endif
diff --git a/qt4/TelepathyQt4/channel.xml b/qt4/TelepathyQt4/channel.xml
new file mode 100644
index 000000000..c850462e2
--- /dev/null
+++ b/qt4/TelepathyQt4/channel.xml
@@ -0,0 +1,37 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel interfaces</tp:title>
+
+<xi:include href="../spec/Channel.xml"/>
+
+<xi:include href="../spec/Channel_Type_Contact_List.xml"/>
+<xi:include href="../spec/Channel_Type_Contact_Search.xml"/>
+<xi:include href="../spec/Channel_Type_DBus_Tube.xml"/>
+<xi:include href="../spec/Channel_Type_File_Transfer.xml"/>
+<xi:include href="../spec/Channel_Type_Room_List.xml"/>
+<xi:include href="../spec/Channel_Type_Server_Authentication.xml"/>
+<xi:include href="../spec/Channel_Type_Server_TLS_Connection.xml"/>
+<xi:include href="../spec/Channel_Type_Streamed_Media.xml"/>
+<xi:include href="../spec/Channel_Type_Stream_Tube.xml"/>
+<xi:include href="../spec/Channel_Type_Text.xml"/>
+<xi:include href="../spec/Channel_Type_Tubes.xml"/>
+
+<xi:include href="../spec/Channel_Interface_Anonymity.xml"/>
+<xi:include href="../spec/Channel_Interface_Call_State.xml"/>
+<xi:include href="../spec/Channel_Interface_Chat_State.xml"/>
+<xi:include href="../spec/Channel_Interface_Conference.xml"/>
+<xi:include href="../spec/Channel_Interface_Destroyable.xml"/>
+<xi:include href="../spec/Channel_Interface_DTMF.xml"/>
+<xi:include href="../spec/Channel_Interface_Group.xml"/>
+<xi:include href="../spec/Channel_Interface_Hold.xml"/>
+<xi:include href="../spec/Channel_Interface_Media_Signalling.xml"/>
+<xi:include href="../spec/Channel_Interface_Messages.xml"/>
+<xi:include href="../spec/Channel_Interface_Password.xml"/>
+<xi:include href="../spec/Channel_Interface_SASL_Authentication.xml"/>
+<xi:include href="../spec/Channel_Interface_Securable.xml"/>
+<xi:include href="../spec/Channel_Interface_Service_Point.xml"/>
+<xi:include href="../spec/Channel_Interface_Tube.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/client-registrar-internal.h b/qt4/TelepathyQt4/client-registrar-internal.h
new file mode 100644
index 000000000..4cf19832a
--- /dev/null
+++ b/qt4/TelepathyQt4/client-registrar-internal.h
@@ -0,0 +1,365 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_client_registrar_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_client_registrar_internal_h_HEADER_GUARD_
+
+#include <QtCore/QObject>
+#include <QtDBus/QtDBus>
+
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/Types>
+
+#include "TelepathyQt4/fake-handler-manager-internal.h"
+
+namespace Tp
+{
+
+class PendingOperation;
+
+class TELEPATHY_QT4_NO_EXPORT ClientAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Client\" >\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+
+public:
+ ClientAdaptor(ClientRegistrar *registrar, const QStringList &interfaces, QObject *parent);
+ virtual ~ClientAdaptor();
+
+ inline const ClientRegistrar *registrar() const
+ {
+ return mRegistrar;
+ }
+
+public: // Properties
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+private:
+ ClientRegistrar *mRegistrar;
+ QStringList mInterfaces;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ClientObserverAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Observer")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Client.Observer\" >\n"
+" <property name=\"ObserverChannelFilter\" type=\"aa{sv}\" access=\"read\" />\n"
+" <property name=\"Recover\" type=\"b\" access=\"read\" />\n"
+" <method name=\"ObserveChannels\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Connection\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Channels\" type=\"a(oa{sv})\" direction=\"in\" />\n"
+" <arg name=\"Dispatch_Operation\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Requests_Satisfied\" type=\"ao\" direction=\"in\" />\n"
+" <arg name=\"Observer_Info\" type=\"a{sv}\" direction=\"in\" />\n"
+" </method>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(Tp::ChannelClassList ObserverChannelFilter READ ObserverChannelFilter)
+ Q_PROPERTY(bool Recover READ Recover)
+
+public:
+ ClientObserverAdaptor(ClientRegistrar *registrar,
+ AbstractClientObserver *client,
+ QObject *parent);
+ virtual ~ClientObserverAdaptor();
+
+ inline const ClientRegistrar *registrar() const
+ {
+ return mRegistrar;
+ }
+
+public: // Properties
+ inline Tp::ChannelClassList ObserverChannelFilter() const
+ {
+ return mClient->observerFilter().bareClasses();
+ }
+
+ inline bool Recover() const
+ {
+ return mClient->shouldRecover();
+ }
+
+public Q_SLOTS: // Methods
+ void ObserveChannels(const QDBusObjectPath &account,
+ const QDBusObjectPath &connection,
+ const Tp::ChannelDetailsList &channels,
+ const QDBusObjectPath &dispatchOperation,
+ const Tp::ObjectPathList &requestsSatisfied,
+ const QVariantMap &observerInfo,
+ const QDBusMessage &message);
+
+private Q_SLOTS:
+ void onReadyOpFinished(Tp::PendingOperation *);
+
+private:
+ struct InvocationData : RefCounted
+ {
+ InvocationData() : readyOp(0) {}
+
+ PendingOperation *readyOp;
+ QString error, message;
+
+ MethodInvocationContextPtr<> ctx;
+ AccountPtr acc;
+ ConnectionPtr conn;
+ QList<ChannelPtr> chans;
+ ChannelDispatchOperationPtr dispatchOp;
+ QList<ChannelRequestPtr> chanReqs;
+ AbstractClientObserver::ObserverInfo observerInfo;
+ };
+ QLinkedList<SharedPtr<InvocationData> > mInvocations;
+
+ ClientRegistrar *mRegistrar;
+ QDBusConnection mBus;
+ AbstractClientObserver *mClient;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ClientApproverAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Approver")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Client.Approver\" >\n"
+" <property name=\"ApproverChannelFilter\" type=\"aa{sv}\" access=\"read\" />\n"
+" <method name=\"AddDispatchOperation\" >\n"
+" <arg name=\"Channels\" type=\"a(oa{sv})\" direction=\"in\" />\n"
+" <arg name=\"Dispatch_Operation\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Properties\" type=\"a{sv}\" direction=\"in\" />\n"
+" </method>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(Tp::ChannelClassList ApproverChannelFilter READ ApproverChannelFilter)
+
+public:
+ ClientApproverAdaptor(ClientRegistrar *registrar,
+ AbstractClientApprover *client,
+ QObject *parent);
+ virtual ~ClientApproverAdaptor();
+
+ inline const ClientRegistrar *registrar() const
+ {
+ return mRegistrar;
+ }
+
+public: // Properties
+ inline Tp::ChannelClassList ApproverChannelFilter() const
+ {
+ return mClient->approverFilter().bareClasses();
+ }
+
+public Q_SLOTS: // Methods
+ void AddDispatchOperation(const Tp::ChannelDetailsList &channels,
+ const QDBusObjectPath &dispatchOperation,
+ const QVariantMap &properties,
+ const QDBusMessage &message);
+
+private Q_SLOTS:
+ void onReadyOpFinished(Tp::PendingOperation *);
+
+private:
+ struct InvocationData : RefCounted
+ {
+ InvocationData() : readyOp(0) {}
+
+ PendingOperation *readyOp;
+ QString error, message;
+
+ MethodInvocationContextPtr<> ctx;
+ QList<ChannelPtr> chans;
+ ChannelDispatchOperationPtr dispatchOp;
+ };
+ QLinkedList<SharedPtr<InvocationData> > mInvocations;
+
+private:
+ ClientRegistrar *mRegistrar;
+ QDBusConnection mBus;
+ AbstractClientApprover *mClient;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ClientHandlerAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Handler")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Client.Handler\" >\n"
+" <property name=\"HandlerChannelFilter\" type=\"aa{sv}\" access=\"read\" />\n"
+" <property name=\"BypassApproval\" type=\"b\" access=\"read\" />\n"
+" <property name=\"Capabilities\" type=\"as\" access=\"read\" />\n"
+" <property name=\"HandledChannels\" type=\"ao\" access=\"read\" />\n"
+" <method name=\"HandleChannels\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Connection\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Channels\" type=\"a(oa{sv})\" direction=\"in\" />\n"
+" <arg name=\"Requests_Satisfied\" type=\"ao\" direction=\"in\" />\n"
+" <arg name=\"User_Action_Time\" type=\"t\" direction=\"in\" />\n"
+" <arg name=\"Handler_Info\" type=\"a{sv}\" direction=\"in\" />\n"
+" </method>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(Tp::ChannelClassList HandlerChannelFilter READ HandlerChannelFilter)
+ Q_PROPERTY(bool BypassApproval READ BypassApproval)
+ Q_PROPERTY(QStringList Capabilities READ Capabilities)
+ Q_PROPERTY(Tp::ObjectPathList HandledChannels READ HandledChannels)
+
+public:
+ ClientHandlerAdaptor(ClientRegistrar *registrar,
+ AbstractClientHandler *client,
+ QObject *parent);
+ virtual ~ClientHandlerAdaptor();
+
+ inline const ClientRegistrar *registrar() const
+ {
+ return mRegistrar;
+ }
+
+public: // Properties
+ inline Tp::ChannelClassList HandlerChannelFilter() const
+ {
+ return mClient->handlerFilter().bareClasses();
+ }
+
+ inline bool BypassApproval() const
+ {
+ return mClient->bypassApproval();
+ }
+
+ inline QStringList Capabilities() const
+ {
+ return mClient->handlerCapabilities().allTokens();
+ }
+
+ inline Tp::ObjectPathList HandledChannels() const
+ {
+ return FakeHandlerManager::instance()->handledChannels(mBus);
+ }
+
+public Q_SLOTS: // Methods
+ void HandleChannels(const QDBusObjectPath &account,
+ const QDBusObjectPath &connection,
+ const Tp::ChannelDetailsList &channels,
+ const Tp::ObjectPathList &requestsSatisfied,
+ qulonglong userActionTime,
+ const QVariantMap &handlerInfo,
+ const QDBusMessage &message);
+
+private Q_SLOTS:
+ void onReadyOpFinished(Tp::PendingOperation *);
+
+private:
+ struct InvocationData : RefCounted
+ {
+ InvocationData() : readyOp(0) {}
+
+ PendingOperation *readyOp;
+ QString error, message;
+
+ MethodInvocationContextPtr<> ctx;
+ AccountPtr acc;
+ ConnectionPtr conn;
+ QList<ChannelPtr> chans;
+ QList<ChannelRequestPtr> chanReqs;
+ QDateTime time;
+ AbstractClientHandler::HandlerInfo handlerInfo;
+ };
+ QLinkedList<SharedPtr<InvocationData> > mInvocations;
+
+private:
+ static void onContextFinished(const MethodInvocationContextPtr<> &context,
+ const QList<ChannelPtr> &channels, ClientHandlerAdaptor *self);
+
+ ClientRegistrar *mRegistrar;
+ QDBusConnection mBus;
+ AbstractClientHandler *mClient;
+
+ static QHash<QPair<QString, QString>, QList<ClientHandlerAdaptor *> > mAdaptorsForConnection;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ClientHandlerRequestsAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Interface.Requests")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Client.Interface.Requests\" >\n"
+" <method name=\"AddRequest\" >\n"
+" <arg name=\"Request\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Properties\" type=\"a{sv}\" direction=\"in\" />\n"
+" </method>\n"
+" <method name=\"RemoveRequest\" >\n"
+" <arg name=\"Request\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Error\" type=\"s\" direction=\"in\" />\n"
+" <arg name=\"Message\" type=\"s\" direction=\"in\" />\n"
+" </method>\n"
+" </interface>\n"
+ "")
+
+public:
+ ClientHandlerRequestsAdaptor(ClientRegistrar *registrar,
+ AbstractClientHandler *client,
+ QObject *parent);
+ virtual ~ClientHandlerRequestsAdaptor();
+
+ inline const ClientRegistrar *registrar() const
+ {
+ return mRegistrar;
+ }
+
+public Q_SLOTS: // Methods
+ void AddRequest(const QDBusObjectPath &request,
+ const QVariantMap &requestProperties,
+ const QDBusMessage &message);
+ void RemoveRequest(const QDBusObjectPath &request,
+ const QString &errorName, const QString &errorMessage,
+ const QDBusMessage &message);
+
+private:
+ ClientRegistrar *mRegistrar;
+ QDBusConnection mBus;
+ AbstractClientHandler *mClient;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ClientAdaptor*)
+Q_DECLARE_METATYPE(Tp::ClientApproverAdaptor*)
+Q_DECLARE_METATYPE(Tp::ClientHandlerAdaptor*)
+Q_DECLARE_METATYPE(Tp::ClientHandlerRequestsAdaptor*)
+Q_DECLARE_METATYPE(Tp::ClientObserverAdaptor*)
+
+#endif
diff --git a/qt4/TelepathyQt4/client-registrar.cpp b/qt4/TelepathyQt4/client-registrar.cpp
new file mode 100644
index 000000000..0276c7df8
--- /dev/null
+++ b/qt4/TelepathyQt4/client-registrar.cpp
@@ -0,0 +1,1038 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ClientRegistrar>
+#include "TelepathyQt4/client-registrar-internal.h"
+
+#include "TelepathyQt4/_gen/client-registrar.moc.hpp"
+#include "TelepathyQt4/_gen/client-registrar-internal.moc.hpp"
+
+#include "TelepathyQt4/channel-factory.h"
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/request-temporary-handler-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelDispatchOperation>
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/MethodInvocationContext>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+
+namespace Tp
+{
+
+class HandleChannelsInvocationContext : public MethodInvocationContext<>
+{
+ Q_DISABLE_COPY(HandleChannelsInvocationContext)
+
+public:
+ typedef void (*FinishedCb)(const MethodInvocationContextPtr<> &context,
+ const QList<ChannelPtr> &channels,
+ void *data);
+
+ static MethodInvocationContextPtr<> create(const QDBusConnection &bus,
+ const QDBusMessage &message, const QList<ChannelPtr> &channels,
+ FinishedCb finishedCb, void *finishedCbData)
+ {
+ return SharedPtr<MethodInvocationContext<> >(
+ new HandleChannelsInvocationContext(bus, message, channels,
+ finishedCb, finishedCbData));
+ }
+
+private:
+ HandleChannelsInvocationContext(const QDBusConnection &connection,
+ const QDBusMessage &message, const QList<ChannelPtr> &channels,
+ FinishedCb finishedCb, void *finishedCbData)
+ : MethodInvocationContext<>(connection, message),
+ mChannels(channels),
+ mFinishedCb(finishedCb),
+ mFinishedCbData(finishedCbData)
+ {
+ }
+
+ void onFinished()
+ {
+ if (mFinishedCb) {
+ mFinishedCb(MethodInvocationContextPtr<>(this), mChannels, mFinishedCbData);
+ }
+ }
+
+ QList<ChannelPtr> mChannels;
+ FinishedCb mFinishedCb;
+ void *mFinishedCbData;
+};
+
+ClientAdaptor::ClientAdaptor(ClientRegistrar *registrar, const QStringList &interfaces,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mRegistrar(registrar),
+ mInterfaces(interfaces)
+{
+}
+
+ClientAdaptor::~ClientAdaptor()
+{
+}
+
+ClientObserverAdaptor::ClientObserverAdaptor(ClientRegistrar *registrar,
+ AbstractClientObserver *client,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mRegistrar(registrar),
+ mBus(registrar->dbusConnection()),
+ mClient(client)
+{
+}
+
+ClientObserverAdaptor::~ClientObserverAdaptor()
+{
+}
+
+void ClientObserverAdaptor::ObserveChannels(const QDBusObjectPath &accountPath,
+ const QDBusObjectPath &connectionPath,
+ const Tp::ChannelDetailsList &channelDetailsList,
+ const QDBusObjectPath &dispatchOperationPath,
+ const Tp::ObjectPathList &requestsSatisfied,
+ const QVariantMap &observerInfo,
+ const QDBusMessage &message)
+{
+ debug() << "ObserveChannels: account:" << accountPath.path() <<
+ ", connection:" << connectionPath.path();
+
+ AccountFactoryConstPtr accFactory = mRegistrar->accountFactory();
+ ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory();
+ ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory();
+ ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory();
+
+ SharedPtr<InvocationData> invocation(new InvocationData());
+
+ QList<PendingOperation *> readyOps;
+
+ PendingReady *accReady = accFactory->proxy(QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME),
+ accountPath.path(),
+ connFactory,
+ chanFactory,
+ contactFactory);
+ invocation->acc = AccountPtr::qObjectCast(accReady->proxy());
+ readyOps.append(accReady);
+
+ QString connectionBusName = connectionPath.path().mid(1).replace(
+ QLatin1String("/"), QLatin1String("."));
+ PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(),
+ chanFactory, contactFactory);
+ invocation->conn = ConnectionPtr::qObjectCast(connReady->proxy());
+ readyOps.append(connReady);
+
+ foreach (const ChannelDetails &channelDetails, channelDetailsList) {
+ PendingReady *chanReady = chanFactory->proxy(invocation->conn,
+ channelDetails.channel.path(), channelDetails.properties);
+ ChannelPtr channel = ChannelPtr::qObjectCast(chanReady->proxy());
+ invocation->chans.append(channel);
+ readyOps.append(chanReady);
+ }
+
+ // Yes, we don't give the choice of making CDO and CR ready or not - however, readifying them is
+ // 0-1 D-Bus calls each, for CR mostly 0 - and their constructors start making them ready
+ // automatically, so we wouldn't save any D-Bus traffic anyway
+
+ if (!dispatchOperationPath.path().isEmpty() && dispatchOperationPath.path() != QLatin1String("/")) {
+ QVariantMap props;
+
+ // TODO: push to tp spec having all of the CDO immutable props be contained in observerInfo
+ // so we don't have to introspect the CDO either - then we can pretty much do:
+ //
+ // props = qdbus_cast<QVariantMap>(
+ // observerInfo.value(QLatin1String("dispatch-operation-properties")));
+ //
+ // Currently something like the following can be used for testing the CDO "we've got
+ // everything we need" codepath:
+ //
+ // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Account"),
+ // QVariant::fromValue(QDBusObjectPath(accountPath.path())));
+ // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"),
+ // QVariant::fromValue(QDBusObjectPath(connectionPath.path())));
+ // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Interfaces"),
+ // QVariant::fromValue(QStringList()));
+ // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".PossibleHandlers"),
+ // QVariant::fromValue(QStringList()));
+
+ invocation->dispatchOp = ChannelDispatchOperation::create(mBus, dispatchOperationPath.path(),
+ props,
+ invocation->chans,
+ accFactory,
+ connFactory,
+ chanFactory,
+ contactFactory);
+ readyOps.append(invocation->dispatchOp->becomeReady());
+ }
+
+ invocation->observerInfo = AbstractClientObserver::ObserverInfo(observerInfo);
+
+ ObjectImmutablePropertiesMap reqPropsMap = qdbus_cast<ObjectImmutablePropertiesMap>(
+ observerInfo.value(QLatin1String("request-properties")));
+ foreach (const QDBusObjectPath &reqPath, requestsSatisfied) {
+ ChannelRequestPtr channelRequest = ChannelRequest::create(invocation->acc,
+ reqPath.path(), reqPropsMap.value(reqPath));
+ invocation->chanReqs.append(channelRequest);
+ readyOps.append(channelRequest->becomeReady());
+ }
+
+ invocation->ctx = MethodInvocationContextPtr<>(new MethodInvocationContext<>(mBus, message));
+
+ invocation->readyOp = new PendingComposite(readyOps, invocation->ctx);
+ connect(invocation->readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onReadyOpFinished(Tp::PendingOperation*)));
+
+ mInvocations.append(invocation);
+
+ debug() << "Preparing proxies for ObserveChannels of" << channelDetailsList.size() << "channels"
+ << "for client" << mClient;
+}
+
+void ClientObserverAdaptor::onReadyOpFinished(Tp::PendingOperation *op)
+{
+ Q_ASSERT(!mInvocations.isEmpty());
+ Q_ASSERT(op->isFinished());
+
+ for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin();
+ i != mInvocations.end(); ++i) {
+ if ((*i)->readyOp != op) {
+ continue;
+ }
+
+ (*i)->readyOp = 0;
+
+ if (op->isError()) {
+ warning() << "Preparing proxies for ObserveChannels failed with" << op->errorName()
+ << op->errorMessage();
+ (*i)->error = op->errorName();
+ (*i)->message = op->errorMessage();
+ }
+
+ break;
+ }
+
+ while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) {
+ SharedPtr<InvocationData> invocation = mInvocations.takeFirst();
+
+ if (!invocation->error.isEmpty()) {
+ // We guarantee that the proxies were ready - so we can't invoke the client if they
+ // weren't made ready successfully. Fix the introspection code if this happens :)
+ invocation->ctx->setFinishedWithError(invocation->error, invocation->message);
+ continue;
+ }
+
+ debug() << "Invoking application observeChannels with" << invocation->chans.size()
+ << "channels on" << mClient;
+
+ mClient->observeChannels(invocation->ctx, invocation->acc, invocation->conn,
+ invocation->chans, invocation->dispatchOp, invocation->chanReqs,
+ invocation->observerInfo);
+ }
+}
+
+ClientApproverAdaptor::ClientApproverAdaptor(ClientRegistrar *registrar,
+ AbstractClientApprover *client,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mRegistrar(registrar),
+ mBus(registrar->dbusConnection()),
+ mClient(client)
+{
+}
+
+ClientApproverAdaptor::~ClientApproverAdaptor()
+{
+}
+
+void ClientApproverAdaptor::AddDispatchOperation(const Tp::ChannelDetailsList &channelDetailsList,
+ const QDBusObjectPath &dispatchOperationPath,
+ const QVariantMap &properties,
+ const QDBusMessage &message)
+{
+ AccountFactoryConstPtr accFactory = mRegistrar->accountFactory();
+ ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory();
+ ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory();
+ ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory();
+
+ QList<PendingOperation *> readyOps;
+
+ QDBusObjectPath connectionPath = qdbus_cast<QDBusObjectPath>(
+ properties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection")));
+ debug() << "addDispatchOperation: connection:" << connectionPath.path();
+ QString connectionBusName = connectionPath.path().mid(1).replace(
+ QLatin1String("/"), QLatin1String("."));
+ PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), chanFactory,
+ contactFactory);
+ ConnectionPtr connection = ConnectionPtr::qObjectCast(connReady->proxy());
+ readyOps.append(connReady);
+
+ SharedPtr<InvocationData> invocation(new InvocationData);
+
+ foreach (const ChannelDetails &channelDetails, channelDetailsList) {
+ PendingReady *chanReady = chanFactory->proxy(connection, channelDetails.channel.path(),
+ channelDetails.properties);
+ invocation->chans.append(ChannelPtr::qObjectCast(chanReady->proxy()));
+ readyOps.append(chanReady);
+ }
+
+ invocation->dispatchOp = ChannelDispatchOperation::create(mBus,
+ dispatchOperationPath.path(), properties, invocation->chans, accFactory, connFactory,
+ chanFactory, contactFactory);
+ readyOps.append(invocation->dispatchOp->becomeReady());
+
+ invocation->ctx = MethodInvocationContextPtr<>(new MethodInvocationContext<>(mBus, message));
+
+ invocation->readyOp = new PendingComposite(readyOps, invocation->ctx);
+ connect(invocation->readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onReadyOpFinished(Tp::PendingOperation*)));
+
+ mInvocations.append(invocation);
+}
+
+void ClientApproverAdaptor::onReadyOpFinished(Tp::PendingOperation *op)
+{
+ Q_ASSERT(!mInvocations.isEmpty());
+ Q_ASSERT(op->isFinished());
+
+ for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin();
+ i != mInvocations.end(); ++i) {
+ if ((*i)->readyOp != op) {
+ continue;
+ }
+
+ (*i)->readyOp = 0;
+
+ if (op->isError()) {
+ warning() << "Preparing proxies for AddDispatchOperation failed with" << op->errorName()
+ << op->errorMessage();
+ (*i)->error = op->errorName();
+ (*i)->message = op->errorMessage();
+ }
+
+ break;
+ }
+
+ while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) {
+ SharedPtr<InvocationData> invocation = mInvocations.takeFirst();
+
+ if (!invocation->error.isEmpty()) {
+ // We guarantee that the proxies were ready - so we can't invoke the client if they
+ // weren't made ready successfully. Fix the introspection code if this happens :)
+ invocation->ctx->setFinishedWithError(invocation->error, invocation->message);
+ continue;
+ }
+
+ debug() << "Invoking application addDispatchOperation with CDO"
+ << invocation->dispatchOp->objectPath() << "on" << mClient;
+
+ mClient->addDispatchOperation(invocation->ctx, invocation->dispatchOp);
+ }
+}
+
+QHash<QPair<QString, QString>, QList<ClientHandlerAdaptor *> > ClientHandlerAdaptor::mAdaptorsForConnection;
+
+ClientHandlerAdaptor::ClientHandlerAdaptor(ClientRegistrar *registrar,
+ AbstractClientHandler *client,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mRegistrar(registrar),
+ mBus(registrar->dbusConnection()),
+ mClient(client)
+{
+ QList<ClientHandlerAdaptor *> &handlerAdaptors =
+ mAdaptorsForConnection[qMakePair(mBus.name(), mBus.baseService())];
+ handlerAdaptors.append(this);
+}
+
+ClientHandlerAdaptor::~ClientHandlerAdaptor()
+{
+ QPair<QString, QString> busId = qMakePair(mBus.name(), mBus.baseService());
+ QList<ClientHandlerAdaptor *> &handlerAdaptors =
+ mAdaptorsForConnection[busId];
+ handlerAdaptors.removeOne(this);
+ if (handlerAdaptors.isEmpty()) {
+ mAdaptorsForConnection.remove(busId);
+ }
+}
+
+void ClientHandlerAdaptor::HandleChannels(const QDBusObjectPath &accountPath,
+ const QDBusObjectPath &connectionPath,
+ const Tp::ChannelDetailsList &channelDetailsList,
+ const Tp::ObjectPathList &requestsSatisfied,
+ qulonglong userActionTime_t,
+ const QVariantMap &handlerInfo,
+ const QDBusMessage &message)
+{
+ debug() << "HandleChannels: account:" << accountPath.path() <<
+ ", connection:" << connectionPath.path();
+
+ AccountFactoryConstPtr accFactory = mRegistrar->accountFactory();
+ ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory();
+ ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory();
+ ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory();
+
+ SharedPtr<InvocationData> invocation(new InvocationData());
+ QList<PendingOperation *> readyOps;
+
+ RequestTemporaryHandler *tempHandler = dynamic_cast<RequestTemporaryHandler *>(mClient);
+ if (tempHandler) {
+ debug() << " This is a temporary handler for the Request & Handle API,"
+ << "giving an early signal of the invocation";
+ tempHandler->setDBusHandlerInvoked();
+ }
+
+ PendingReady *accReady = accFactory->proxy(QLatin1String(TELEPATHY_ACCOUNT_MANAGER_BUS_NAME),
+ accountPath.path(),
+ connFactory,
+ chanFactory,
+ contactFactory);
+ invocation->acc = AccountPtr::qObjectCast(accReady->proxy());
+ readyOps.append(accReady);
+
+ QString connectionBusName = connectionPath.path().mid(1).replace(
+ QLatin1String("/"), QLatin1String("."));
+ PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(),
+ chanFactory, contactFactory);
+ invocation->conn = ConnectionPtr::qObjectCast(connReady->proxy());
+ readyOps.append(connReady);
+
+ foreach (const ChannelDetails &channelDetails, channelDetailsList) {
+ PendingReady *chanReady = chanFactory->proxy(invocation->conn,
+ channelDetails.channel.path(), channelDetails.properties);
+ ChannelPtr channel = ChannelPtr::qObjectCast(chanReady->proxy());
+ invocation->chans.append(channel);
+ readyOps.append(chanReady);
+ }
+
+ invocation->handlerInfo = AbstractClientHandler::HandlerInfo(handlerInfo);
+
+ ObjectImmutablePropertiesMap reqPropsMap = qdbus_cast<ObjectImmutablePropertiesMap>(
+ handlerInfo.value(QLatin1String("request-properties")));
+ foreach (const QDBusObjectPath &reqPath, requestsSatisfied) {
+ ChannelRequestPtr channelRequest = ChannelRequest::create(invocation->acc,
+ reqPath.path(), reqPropsMap.value(reqPath));
+ invocation->chanReqs.append(channelRequest);
+ readyOps.append(channelRequest->becomeReady());
+ }
+
+ // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690
+ if (userActionTime_t != 0) {
+ invocation->time = QDateTime::fromTime_t((uint) userActionTime_t);
+ }
+
+ invocation->ctx = HandleChannelsInvocationContext::create(mBus, message,
+ invocation->chans,
+ reinterpret_cast<HandleChannelsInvocationContext::FinishedCb>(
+ &ClientHandlerAdaptor::onContextFinished),
+ this);
+
+ invocation->readyOp = new PendingComposite(readyOps, invocation->ctx);
+ connect(invocation->readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onReadyOpFinished(Tp::PendingOperation*)));
+
+ mInvocations.append(invocation);
+
+ debug() << "Preparing proxies for HandleChannels of" << channelDetailsList.size() << "channels"
+ << "for client" << mClient;
+}
+
+void ClientHandlerAdaptor::onReadyOpFinished(Tp::PendingOperation *op)
+{
+ Q_ASSERT(!mInvocations.isEmpty());
+ Q_ASSERT(op->isFinished());
+
+ for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin();
+ i != mInvocations.end(); ++i) {
+ if ((*i)->readyOp != op) {
+ continue;
+ }
+
+ (*i)->readyOp = 0;
+
+ if (op->isError()) {
+ warning() << "Preparing proxies for HandleChannels failed with" << op->errorName()
+ << op->errorMessage();
+ (*i)->error = op->errorName();
+ (*i)->message = op->errorMessage();
+ }
+
+ break;
+ }
+
+ while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) {
+ SharedPtr<InvocationData> invocation = mInvocations.takeFirst();
+
+ if (!invocation->error.isEmpty()) {
+ RequestTemporaryHandler *tempHandler = dynamic_cast<RequestTemporaryHandler *>(mClient);
+ if (tempHandler) {
+ debug() << " This is a temporary handler for the Request & Handle API, indicating failure";
+ tempHandler->setDBusHandlerErrored(invocation->error, invocation->message);
+ }
+
+ // We guarantee that the proxies were ready - so we can't invoke the client if they
+ // weren't made ready successfully. Fix the introspection code if this happens :)
+ invocation->ctx->setFinishedWithError(invocation->error, invocation->message);
+ continue;
+ }
+
+ debug() << "Invoking application handleChannels with" << invocation->chans.size()
+ << "channels on" << mClient;
+
+ mClient->handleChannels(invocation->ctx, invocation->acc, invocation->conn,
+ invocation->chans, invocation->chanReqs, invocation->time, invocation->handlerInfo);
+ }
+}
+
+void ClientHandlerAdaptor::onContextFinished(
+ const MethodInvocationContextPtr<> &context,
+ const QList<ChannelPtr> &channels, ClientHandlerAdaptor *self)
+{
+ if (!context->isError()) {
+ debug() << "HandleChannels context finished successfully, "
+ "updating handled channels";
+
+ // register the channels in FakeHandlerManager so we report HandledChannels correctly
+ FakeHandlerManager::instance()->registerChannels(channels);
+ }
+}
+
+ClientHandlerRequestsAdaptor::ClientHandlerRequestsAdaptor(
+ ClientRegistrar *registrar,
+ AbstractClientHandler *client,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mRegistrar(registrar),
+ mBus(registrar->dbusConnection()),
+ mClient(client)
+{
+}
+
+ClientHandlerRequestsAdaptor::~ClientHandlerRequestsAdaptor()
+{
+}
+
+void ClientHandlerRequestsAdaptor::AddRequest(
+ const QDBusObjectPath &request,
+ const QVariantMap &requestProperties,
+ const QDBusMessage &message)
+{
+ debug() << "AddRequest:" << request.path();
+ message.setDelayedReply(true);
+ mBus.send(message.createReply());
+ mClient->addRequest(ChannelRequest::create(mBus,
+ request.path(), requestProperties,
+ mRegistrar->accountFactory(),
+ mRegistrar->connectionFactory(),
+ mRegistrar->channelFactory(),
+ mRegistrar->contactFactory()));
+}
+
+void ClientHandlerRequestsAdaptor::RemoveRequest(
+ const QDBusObjectPath &request,
+ const QString &errorName, const QString &errorMessage,
+ const QDBusMessage &message)
+{
+ debug() << "RemoveRequest:" << request.path() << "-" << errorName
+ << "-" << errorMessage;
+ message.setDelayedReply(true);
+ mBus.send(message.createReply());
+ mClient->removeRequest(ChannelRequest::create(mBus,
+ request.path(), QVariantMap(),
+ mRegistrar->accountFactory(),
+ mRegistrar->connectionFactory(),
+ mRegistrar->channelFactory(),
+ mRegistrar->contactFactory()), errorName, errorMessage);
+}
+
+struct TELEPATHY_QT4_NO_EXPORT ClientRegistrar::Private
+{
+ Private(const QDBusConnection &bus, const AccountFactoryConstPtr &accFactory,
+ const ConnectionFactoryConstPtr &connFactory, const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : bus(bus), accFactory(accFactory), connFactory(connFactory), chanFactory(chanFactory),
+ contactFactory(contactFactory)
+ {
+ if (accFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the account factory is not the proxy connection";
+ }
+
+ if (connFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the connection factory is not the proxy connection";
+ }
+
+ if (chanFactory->dbusConnection().name() != bus.name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection";
+ }
+ }
+
+ QDBusConnection bus;
+
+ AccountFactoryConstPtr accFactory;
+ ConnectionFactoryConstPtr connFactory;
+ ChannelFactoryConstPtr chanFactory;
+ ContactFactoryConstPtr contactFactory;
+
+ QHash<AbstractClientPtr, QString> clients;
+ QHash<AbstractClientPtr, QObject*> clientObjects;
+ QSet<QString> services;
+};
+
+/**
+ * \class ClientRegistrar
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/client-registrar.h <TelepathyQt4/ClientRegistrar>
+ *
+ * \brief The ClientRegistrar class is responsible for registering Telepathy
+ * clients (Observer, Approver, Handler).
+ *
+ * Clients should inherit AbstractClientObserver, AbstractClientApprover,
+ * AbstractClientHandler or some combination of these, by using multiple
+ * inheritance, and register themselves using registerClient().
+ *
+ * See the individual classes descriptions for more details.
+ *
+ * \section cr_usage_sec Usage
+ *
+ * \subsection cr_create_sec Creating a client registrar object
+ *
+ * One way to create a ClientRegistrar object is to just call the create method.
+ * For example:
+ *
+ * \code ClientRegistrarPtr cr = ClientRegistrar::create(); \endcode
+ *
+ * You can also provide a D-Bus connection as a QDBusConnection:
+ *
+ * \code ClientRegistrarPtr cr = ClientRegistrar::create(QDBusConnection::systemBus()); \endcode
+ *
+ * \subsection cr_registering_sec Registering a client
+ *
+ * To register a client, just call registerClient() with a given AbstractClientPtr
+ * pointing to a valid AbstractClient instance.
+ *
+ * \code
+ *
+ * class MyClient : public AbstractClientObserver, public AbstractClientHandler
+ * {
+ * ...
+ * };
+ *
+ * ...
+ *
+ * ClientRegistrarPtr cr = ClientRegistrar::create();
+ * SharedPtr<MyClient> client = SharedPtr<MyClient>(new MyClient(...));
+ * cr->registerClient(AbstractClientPtr::dynamicCast(client), "myclient");
+ *
+ * \endcode
+ *
+ * \sa AbstractClientObserver, AbstractClientApprover, AbstractClientHandler
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Create a new client registrar object using the given \a bus.
+ *
+ * The instance will use an account factory creating Tp::Account objects with no features
+ * ready, a connection factory creating Tp::Connection objects with no features ready, and a channel
+ * factory creating stock Telepathy-Qt4 channel subclasses, as appropriate, with no features ready.
+ *
+ * \param bus QDBusConnection to use.
+ * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object.
+ */
+ClientRegistrarPtr ClientRegistrar::create(const QDBusConnection &bus)
+{
+ return create(bus, AccountFactory::create(bus), ConnectionFactory::create(bus),
+ ChannelFactory::create(bus), ContactFactory::create());
+}
+
+/**
+ * Create a new client registrar object using QDBusConnection::sessionBus() and the given factories.
+ *
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object.
+ */
+ClientRegistrarPtr ClientRegistrar::create(
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return create(QDBusConnection::sessionBus(), accountFactory, connectionFactory, channelFactory,
+ contactFactory);
+}
+
+/**
+ * Create a new client registrar object using the given \a bus and the given factories.
+ *
+ * \param bus QDBusConnection to use.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object.
+ */
+ClientRegistrarPtr ClientRegistrar::create(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ClientRegistrarPtr(new ClientRegistrar(bus, accountFactory, connectionFactory,
+ channelFactory, contactFactory));
+}
+
+/**
+ * Create a new client registrar object using the bus and factories of the given Account \a manager.
+ *
+ * Using this create method will enable (like any other way of passing the same factories to an AM
+ * and a registrar) getting the same Account/Connection etc. proxy instances from both
+ * AccountManager and AbstractClient implementations.
+ *
+ * \param manager The AccountManager the bus and factories of which should be used.
+ * \return A ClientRegistrarPtr object pointing to the newly ClientRegistrar object.
+ */
+ClientRegistrarPtr ClientRegistrar::create(const AccountManagerPtr &manager)
+{
+ if (!manager) {
+ return ClientRegistrarPtr();
+ }
+
+ return create(manager->dbusConnection(), manager->accountFactory(),
+ manager->connectionFactory(), manager->channelFactory(), manager->contactFactory());
+}
+
+/**
+ * Construct a new client registrar object using the given \a bus and the given factories.
+ *
+ * \param bus QDBusConnection to use.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ */
+ClientRegistrar::ClientRegistrar(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : Object(),
+ mPriv(new Private(bus, accountFactory, connectionFactory, channelFactory, contactFactory))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ClientRegistrar::~ClientRegistrar()
+{
+ unregisterClients();
+ delete mPriv;
+}
+
+/**
+ * Return the D-Bus connection being used by this client registrar.
+ *
+ * \return A QDBusConnection object.
+ */
+QDBusConnection ClientRegistrar::dbusConnection() const
+{
+ return mPriv->bus;
+}
+
+/**
+ * Get the account factory used by this client registrar.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the registrar would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the AccountFactory object.
+ */
+AccountFactoryConstPtr ClientRegistrar::accountFactory() const
+{
+ return mPriv->accFactory;
+}
+
+/**
+ * Get the connection factory used by this client registrar.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the registrar would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ConnectionFactory object.
+ */
+ConnectionFactoryConstPtr ClientRegistrar::connectionFactory() const
+{
+ return mPriv->connFactory;
+}
+
+/**
+ * Get the channel factory used by this client registrar.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the registrar would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ChannelFactory object.
+ */
+ChannelFactoryConstPtr ClientRegistrar::channelFactory() const
+{
+ return mPriv->chanFactory;
+}
+
+/**
+ * Get the contact factory used by this client registrar.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the registrar would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ContactFactory object.
+ */
+ContactFactoryConstPtr ClientRegistrar::contactFactory() const
+{
+ return mPriv->contactFactory;
+}
+
+/**
+ * Return the list of clients registered using registerClient() on this client
+ * registrar.
+ *
+ * \return A list of pointers to AbstractClient objects.
+ * \sa registerClient(), unregisterClient()
+ */
+QList<AbstractClientPtr> ClientRegistrar::registeredClients() const
+{
+ return mPriv->clients.keys();
+}
+
+/**
+ * Register a client on D-Bus.
+ *
+ * The client registrar will export the appropriate D-Bus interfaces,
+ * based on the abstract classes subclassed by \param client.
+ *
+ * If each of a client instance should be able to manipulate channels
+ * separately, set unique to true.
+ *
+ * The client name MUST be a non-empty string of ASCII digits, letters, dots
+ * and/or underscores, starting with a letter, and without sets of
+ * two consecutive dots or a dot followed by a digit.
+ *
+ * This method will do nothing if the client is already registered, and \c true
+ * will be returned.
+ *
+ * To unregister a client use unregisterClient().
+ *
+ * \param client The client to register.
+ * \param clientName The client name used to register.
+ * \param unique Whether each of a client instance is able to manipulate
+ * channels separately.
+ * \return \c true if \a client was successfully registered, \c false otherwise.
+ * \sa registeredClients(), unregisterClient()
+ */
+bool ClientRegistrar::registerClient(const AbstractClientPtr &client,
+ const QString &clientName, bool unique)
+{
+ if (!client) {
+ warning() << "Unable to register a null client";
+ return false;
+ }
+
+ if (mPriv->clients.contains(client)) {
+ debug() << "Client already registered";
+ return true;
+ }
+
+ QString busName = QLatin1String("org.freedesktop.Telepathy.Client.");
+ busName.append(clientName);
+ if (unique) {
+ // o.f.T.Client.clientName.<unique_bus_name>_<pointer> should be enough to identify
+ // an unique identifier
+ busName.append(QString(QLatin1String(".%1_%2"))
+ .arg(mPriv->bus.baseService()
+ .replace(QLatin1String(":"), QLatin1String("_"))
+ .replace(QLatin1String("."), QLatin1String("_")))
+ .arg((intptr_t) client.data(), 0, 16));
+ }
+
+ if (mPriv->services.contains(busName) ||
+ !mPriv->bus.registerService(busName)) {
+ warning() << "Unable to register client: busName" <<
+ busName << "already registered";
+ return false;
+ }
+
+ QObject *object = new QObject(this);
+ QStringList interfaces;
+
+ AbstractClientHandler *handler =
+ dynamic_cast<AbstractClientHandler*>(client.data());
+ if (handler) {
+ // export o.f.T.Client.Handler
+ new ClientHandlerAdaptor(this, handler, object);
+ interfaces.append(
+ QLatin1String("org.freedesktop.Telepathy.Client.Handler"));
+ if (handler->wantsRequestNotification()) {
+ // export o.f.T.Client.Interface.Requests
+ new ClientHandlerRequestsAdaptor(this, handler, object);
+ interfaces.append(
+ QLatin1String(
+ "org.freedesktop.Telepathy.Client.Interface.Requests"));
+ }
+ }
+
+ AbstractClientObserver *observer =
+ dynamic_cast<AbstractClientObserver*>(client.data());
+ if (observer) {
+ // export o.f.T.Client.Observer
+ new ClientObserverAdaptor(this, observer, object);
+ interfaces.append(
+ QLatin1String("org.freedesktop.Telepathy.Client.Observer"));
+ }
+
+ AbstractClientApprover *approver =
+ dynamic_cast<AbstractClientApprover*>(client.data());
+ if (approver) {
+ // export o.f.T.Client.Approver
+ new ClientApproverAdaptor(this, approver, object);
+ interfaces.append(
+ QLatin1String("org.freedesktop.Telepathy.Client.Approver"));
+ }
+
+ if (interfaces.isEmpty()) {
+ warning() << "Client does not implement any known interface";
+ // cleanup
+ mPriv->bus.unregisterService(busName);
+ return false;
+ }
+
+ // export o.f.T.Client interface
+ new ClientAdaptor(this, interfaces, object);
+
+ QString objectPath = QString(QLatin1String("/%1")).arg(busName);
+ objectPath.replace(QLatin1String("."), QLatin1String("/"));
+ if (!mPriv->bus.registerObject(objectPath, object)) {
+ // this shouldn't happen, but let's make sure
+ warning() << "Unable to register client: objectPath" <<
+ objectPath << "already registered";
+ // cleanup
+ delete object;
+ mPriv->bus.unregisterService(busName);
+ return false;
+ }
+
+ if (handler) {
+ handler->setRegistered(true);
+ }
+
+ debug() << "Client registered - busName:" << busName <<
+ "objectPath:" << objectPath << "interfaces:" << interfaces;
+
+ mPriv->services.insert(busName);
+ mPriv->clients.insert(client, objectPath);
+ mPriv->clientObjects.insert(client, object);
+
+ return true;
+}
+
+/**
+ * Unregister a client registered using registerClient() on this client
+ * registrar.
+ *
+ * If \a client was not registered previously, \c false will be returned.
+ *
+ * \param client The client to unregister.
+ * \return \c true if \a client was successfully unregistered, \c false otherwise.
+ * \sa registeredClients(), registerClient()
+ */
+bool ClientRegistrar::unregisterClient(const AbstractClientPtr &client)
+{
+ if (!mPriv->clients.contains(client)) {
+ warning() << "Trying to unregister an unregistered client";
+ return false;
+ }
+
+ AbstractClientHandler *handler =
+ dynamic_cast<AbstractClientHandler*>(client.data());
+ if (handler) {
+ handler->setRegistered(false);
+ }
+
+ QString objectPath = mPriv->clients.value(client);
+ mPriv->bus.unregisterObject(objectPath);
+ mPriv->clients.remove(client);
+ QObject *object = mPriv->clientObjects.value(client);
+ // delete object here and it's children (adaptors), to make sure if adaptor
+ // is keeping a static list of adaptors per connection, the list is updated.
+ delete object;
+ mPriv->clientObjects.remove(client);
+
+ QString busName = objectPath.mid(1).replace(QLatin1String("/"),
+ QLatin1String("."));
+ mPriv->bus.unregisterService(busName);
+ mPriv->services.remove(busName);
+
+ debug() << "Client unregistered - busName:" << busName <<
+ "objectPath:" << objectPath;
+
+ return true;
+}
+
+/**
+ * Unregister all clients registered using registerClient() on this client
+ * registrar.
+ *
+ * \sa registeredClients(), registerClient(), unregisterClient()
+ */
+void ClientRegistrar::unregisterClients()
+{
+ // copy the hash as it will be modified
+ QHash<AbstractClientPtr, QString> clients = mPriv->clients;
+
+ QHash<AbstractClientPtr, QString>::const_iterator end =
+ clients.constEnd();
+ QHash<AbstractClientPtr, QString>::const_iterator it =
+ clients.constBegin();
+ while (it != end) {
+ unregisterClient(it.key());
+ ++it;
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/client-registrar.h b/qt4/TelepathyQt4/client-registrar.h
new file mode 100644
index 000000000..a31de2075
--- /dev/null
+++ b/qt4/TelepathyQt4/client-registrar.h
@@ -0,0 +1,97 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_client_registrar_h_HEADER_GUARD_
+#define _TelepathyQt4_client_registrar_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/Types>
+
+#include <QDBusConnection>
+#include <QString>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT ClientRegistrar : public Object
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ClientRegistrar)
+
+public:
+ static ClientRegistrarPtr create(const QDBusConnection &bus);
+ static ClientRegistrarPtr create(
+ const AccountFactoryConstPtr &accountFactory =
+ AccountFactory::create(QDBusConnection::sessionBus()),
+ const ConnectionFactoryConstPtr &connectionFactory =
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ const ChannelFactoryConstPtr &channelFactory =
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+ static ClientRegistrarPtr create(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ static ClientRegistrarPtr create(const AccountManagerPtr &accountManager);
+
+ ~ClientRegistrar();
+
+ QDBusConnection dbusConnection() const;
+
+ AccountFactoryConstPtr accountFactory() const;
+ ConnectionFactoryConstPtr connectionFactory() const;
+ ChannelFactoryConstPtr channelFactory() const;
+ ContactFactoryConstPtr contactFactory() const;
+
+ QList<AbstractClientPtr> registeredClients() const;
+ bool registerClient(const AbstractClientPtr &client,
+ const QString &clientName, bool unique = false);
+ bool unregisterClient(const AbstractClientPtr &client);
+ void unregisterClients();
+
+private:
+ ClientRegistrar(const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+
+ static QHash<QPair<QString, QString>, ClientRegistrar *> registrarForConnection;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/client.cpp b/qt4/TelepathyQt4/client.cpp
new file mode 100644
index 000000000..f3dec1bd2
--- /dev/null
+++ b/qt4/TelepathyQt4/client.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Client>
+
+#include "TelepathyQt4/_gen/cli-client-body.hpp"
+#include "TelepathyQt4/_gen/cli-client.moc.hpp"
diff --git a/qt4/TelepathyQt4/client.h b/qt4/TelepathyQt4/client.h
new file mode 100644
index 000000000..835bea9b3
--- /dev/null
+++ b/qt4/TelepathyQt4/client.h
@@ -0,0 +1,32 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_client_h_HEADER_GUARD_
+#define _TelepathyQt4_client_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-client.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/client.xml b/qt4/TelepathyQt4/client.xml
new file mode 100644
index 000000000..d4d0f6de7
--- /dev/null
+++ b/qt4/TelepathyQt4/client.xml
@@ -0,0 +1,14 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Client interfaces</tp:title>
+
+<xi:include href="../spec/Client.xml"/>
+<xi:include href="../spec/Client_Observer.xml"/>
+<xi:include href="../spec/Client_Approver.xml"/>
+<xi:include href="../spec/Client_Handler.xml"/>
+
+<xi:include href="../spec/Client_Interface_Requests.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/connection-capabilities.cpp b/qt4/TelepathyQt4/connection-capabilities.cpp
new file mode 100644
index 000000000..5e73fe114
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-capabilities.cpp
@@ -0,0 +1,303 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ConnectionCapabilities>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+/**
+ * \class ConnectionCapabilities
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/connection-capabilities.h <TelepathyQt4/ConnectionCapabilities>
+ *
+ * \brief The ConnectionCapabilities class represents the capabilities of a
+ * Connection.
+ */
+
+/**
+ * Construct a new ConnectionCapabilities object.
+ */
+ConnectionCapabilities::ConnectionCapabilities()
+ : CapabilitiesBase()
+{
+}
+
+/**
+ * Construct a new ConnectionCapabilities object using the give \a rccs.
+ *
+ * \param rccs RequestableChannelClassList representing the capabilities of a
+ * Connection.
+ */
+ConnectionCapabilities::ConnectionCapabilities(const RequestableChannelClassList &rccs)
+ : CapabilitiesBase(rccs, false)
+{
+}
+
+/**
+ * Construct a new ConnectionCapabilities object using the give \a rccSpecs.
+ *
+ * \param rccSpecs RequestableChannelClassSpecList representing the capabilities of a
+ * Connection.
+ */
+ConnectionCapabilities::ConnectionCapabilities(const RequestableChannelClassSpecList &rccSpecs)
+ : CapabilitiesBase(rccSpecs, false)
+{
+}
+
+/**
+ * Class destructor.
+ */
+ConnectionCapabilities::~ConnectionCapabilities()
+{
+}
+
+/**
+ * Return true if named text chatrooms can be joined by providing a
+ * chatroom identifier.
+ *
+ * If the protocol is such that chatrooms can be joined, but only via
+ * a more elaborate D-Bus API than normal (because more information is needed),
+ * then this method will return false.
+ *
+ * \return \c true if Account::ensureTextChatroom() can be expected to work.
+ */
+bool ConnectionCapabilities::textChatrooms() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::textChatroom())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating conference media calls is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::conferenceStreamedMediaCalls() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::conferenceStreamedMediaCall())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating conference media calls is supported.
+ *
+ * This method will also check whether inviting new contacts when creating a conference media call
+ * channel by providing additional members to initial invitees (as opposed to merging several
+ * channels into one new conference channel) is supported.
+ *
+ * If providing additional members is supported, it is also possible to request conference media
+ * calls with fewer than two (even zero) already established media calls.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::conferenceStreamedMediaCallsWithInvitees() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating conference text chats is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::conferenceTextChats() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChat())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating conference text chats is supported.
+ *
+ * This method will also check whether inviting new contacts when creating a conference text chat
+ * channel by providing additional members to initial invitees (as opposed to merging several
+ * channels into one new conference channel) is supported.
+ *
+ * If providing additional members is supported, it is also possible to request conference text
+ * chats with fewer than two (even zero) already established text chats.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::conferenceTextChatsWithInvitees() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChatWithInvitees())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating conference text chat rooms is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::conferenceTextChatrooms() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChatroom())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating conference text chat rooms is supported.
+ *
+ * This method will also check whether inviting new contacts when creating a conference text chat
+ * room channel by providing additional members to initial invitees (as opposed to merging several
+ * channels into one new conference channel) is supported.
+ *
+ * If providing additional members is supported, it is also possible to request conference text
+ * chat rooms with fewer than two (even zero) already established text chat rooms.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::conferenceTextChatroomsWithInvitees() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChatroomWithInvitees())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * \deprecated Use contactSearches() instead.
+ */
+bool ConnectionCapabilities::contactSearch()
+{
+ return contactSearches();
+}
+
+/**
+ * \deprecated Use contactSearchesWithSpecificServer() instead.
+ */
+bool ConnectionCapabilities::contactSearchWithSpecificServer() const
+{
+ return contactSearchesWithSpecificServer();
+}
+
+/**
+ * \deprecated Use contactSearchesWithLimit() instead.
+ */
+bool ConnectionCapabilities::contactSearchWithLimit() const
+{
+ return contactSearchesWithLimit();
+}
+
+/**
+ * Return whether creating a ContactSearch channel is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::contactSearches() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::contactSearch())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating a ContactSearch channel specifying a server is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::contactSearchesWithSpecificServer() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::contactSearchWithSpecificServer())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating a ContactSearch channel specifying a limit is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::contactSearchesWithLimit() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::contactSearchWithLimit())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether creating a StreamTube channel by providing a contact identifier is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ConnectionCapabilities::streamTubes() const
+{
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(RequestableChannelClassSpec::streamTube())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/connection-capabilities.h b/qt4/TelepathyQt4/connection-capabilities.h
new file mode 100644
index 000000000..0f05443a1
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-capabilities.h
@@ -0,0 +1,77 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_capabilities_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_capabilities_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/CapabilitiesBase>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TestBackdoors;
+
+class TELEPATHY_QT4_EXPORT ConnectionCapabilities : public CapabilitiesBase
+{
+public:
+ ConnectionCapabilities();
+ virtual ~ConnectionCapabilities();
+
+ bool textChatrooms() const;
+
+ bool conferenceStreamedMediaCalls() const;
+ bool conferenceStreamedMediaCallsWithInvitees() const;
+ bool conferenceTextChats() const;
+ bool conferenceTextChatsWithInvitees() const;
+ bool conferenceTextChatrooms() const;
+ bool conferenceTextChatroomsWithInvitees() const;
+
+ bool contactSearches() const;
+ bool contactSearchesWithSpecificServer() const;
+ bool contactSearchesWithLimit() const;
+
+ TELEPATHY_QT4_DEPRECATED bool contactSearch();
+ TELEPATHY_QT4_DEPRECATED bool contactSearchWithSpecificServer() const;
+ TELEPATHY_QT4_DEPRECATED bool contactSearchWithLimit() const;
+
+ bool streamTubes() const;
+
+protected:
+ friend class Account;
+ friend class Connection;
+ friend class ProtocolInfo;
+ friend class TestBackdoors;
+
+ ConnectionCapabilities(const RequestableChannelClassList &rccs);
+ ConnectionCapabilities(const RequestableChannelClassSpecList &rccSpecs);
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ConnectionCapabilities);
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-factory.cpp b/qt4/TelepathyQt4/connection-factory.cpp
new file mode 100644
index 000000000..2915d225a
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-factory.cpp
@@ -0,0 +1,150 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ConnectionFactory>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/DBusProxy>
+
+namespace Tp
+{
+
+/**
+ * \class ConnectionFactory
+ * \ingroup utils
+ * \headerfile TelepathyQt4/connection-factory.h <TelepathyQt4/ConnectionFactory>
+ *
+ * \brief The ConnectionFactory class is responsible for constructing Connection
+ * objects according to application-defined settings.
+ *
+ * The class is used by Account and other classes which construct Connection proxy
+ * instances to enable sharing instances of application-defined Connection
+ * subclasses with certain features always ready.
+ */
+
+/**
+ * Create a new ConnectionFactory object.
+ *
+ * Optionally, the \a features to make ready on all constructed proxies can be specified. The
+ * default is to make no features ready. It should be noted that unlike Connection::becomeReady(),
+ * FeatureCore isn't assumed. If no features are specified, which is the default behavior, no
+ * Connection::becomeReady() call is made at all and the proxy won't be Connection::isReady().
+ *
+ * \param bus The QDBusConnection for proxies constructed using this factory to use.
+ * \param features The features to make ready on constructed Connections.
+ * \return A ConnectionFactoryPtr object pointing to the newly created
+ * ConnectionFactory object.
+ */
+ConnectionFactoryPtr ConnectionFactory::create(const QDBusConnection &bus,
+ const Features &features)
+{
+ return ConnectionFactoryPtr(new ConnectionFactory(bus, features));
+}
+
+/**
+ * Construct a new ConnectionFactory object.
+ *
+ * As in create(), it should be noted that unlike Connection::becomeReady(), FeatureCore isn't
+ * assumed. If no \a features are specified, no Connection::becomeReady() call is made at all and
+ * the proxy won't be Connection::isReady().
+ *
+ * \param bus The QDBusConnection for proxies constructed using this factory to use.
+ * \param features The features to make ready on constructed Connections.
+ */
+ConnectionFactory::ConnectionFactory(const QDBusConnection &bus, const Features &features)
+ : FixedFeatureFactory(bus)
+{
+ addFeatures(features);
+}
+
+/**
+ * Class destructor.
+ */
+ConnectionFactory::~ConnectionFactory()
+{
+}
+
+/**
+ * Constructs a Connection proxy and begins making it ready.
+ *
+ * If a valid proxy already exists in the factory cache for the given combination of \a busName and
+ * \a objectPath, it is returned instead. All newly created proxies are automatically cached until
+ * they're either DBusProxy::invalidated() or the last reference to them outside the factory has
+ * been dropped.
+ *
+ * The proxy can be accessed immediately after this function returns using PendingReady::proxy().
+ * The ready operation only finishes, however, when the features specified by features(), if any,
+ * are made ready as much as possible. If the service doesn't support a given feature, they won't
+ * obviously be ready even if the operation finished successfully, as is the case for
+ * Connection::becomeReady().
+ *
+ * \param busName The bus/service name of the D-Bus connection object the proxy is constructed for.
+ * \param objectPath The object path of the connection.
+ * \param chanFactory The channel factory to use for the Connection.
+ * \param contactFactory The channel factory to use for the Connection.
+ * \return A PendingReady operation with the proxy in PendingReady::proxy().
+ */
+PendingReady *ConnectionFactory::proxy(const QString &busName, const QString &objectPath,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const
+{
+ DBusProxyPtr proxy = cachedProxy(busName, objectPath);
+ if (proxy.isNull()) {
+ proxy = construct(busName, objectPath, chanFactory, contactFactory);
+ }
+
+ return nowHaveProxy(proxy);
+}
+
+/**
+ * Can be used by subclasses to override the Connection subclass constructed by the factory.
+ *
+ * This is automatically called by proxy() to construct proxy instances if no valid cached proxy is
+ * found.
+ *
+ * The default implementation constructs Tp::Connection objects.
+ *
+ * \param busName The bus/service name of the D-Bus Connection object the proxy is constructed for.
+ * \param objectPath The object path of the connection.
+ * \param chanFactory The channel factory to use for the Connection.
+ * \param contactFactory The channel factory to use for the Connection.
+ * \return A pointer to the constructed Connection object.
+ */
+ConnectionPtr ConnectionFactory::construct(const QString &busName, const QString &objectPath,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const
+{
+ return Connection::create(dbusConnection(), busName, objectPath, chanFactory, contactFactory);
+}
+
+/**
+ * Transforms well-known names to the corresponding unique names, as is appropriate for Connection
+ *
+ * \param uniqueOrWellKnown The name to transform.
+ * \return The unique name corresponding to \a uniqueOrWellKnown (which may be it itself).
+ */
+QString ConnectionFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const
+{
+ return StatefulDBusProxy::uniqueNameFrom(dbusConnection(), uniqueOrWellKnown);
+}
+
+}
diff --git a/qt4/TelepathyQt4/connection-factory.h b/qt4/TelepathyQt4/connection-factory.h
new file mode 100644
index 000000000..3308d1b1f
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-factory.h
@@ -0,0 +1,78 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/FixedFeatureFactory>
+
+// For Q_DISABLE_COPY
+#include <QtGlobal>
+
+#include <QString>
+
+class QDBusConnection;
+
+namespace Tp
+{
+
+class PendingReady;
+
+class TELEPATHY_QT4_EXPORT ConnectionFactory : public FixedFeatureFactory
+{
+public:
+ static ConnectionFactoryPtr create(const QDBusConnection &bus,
+ const Features &features = Features());
+
+ virtual ~ConnectionFactory();
+
+ PendingReady *proxy(const QString &busName, const QString &objectPath,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const;
+
+protected:
+ ConnectionFactory(const QDBusConnection &bus, const Features &features);
+
+ virtual ConnectionPtr construct(const QString &busName, const QString &objectPath,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const;
+ virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const;
+ // Nothing we'd like to prepare()
+ // Fixed features
+
+private:
+ struct Private;
+ Private *mPriv; // Currently unused, just for future-proofing
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-internal.h b/qt4/TelepathyQt4/connection-internal.h
new file mode 100644
index 000000000..bf6c8a69f
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-internal.h
@@ -0,0 +1,61 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Connection>
+
+#include <TelepathyQt4/PendingReady>
+
+#include <QSet>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT Connection::PendingConnect : public PendingReady
+{
+ Q_OBJECT
+
+public:
+ PendingConnect(const ConnectionPtr &connection, const Features &requestedFeatures);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onConnectReply(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void onStatusChanged(Tp::ConnectionStatus newStatus);
+ TELEPATHY_QT4_NO_EXPORT void onBecomeReadyReply(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onConnInvalidated(Tp::DBusProxy *proxy, const QString &error, const QString &message);
+
+private:
+ friend class ConnectionLowlevel;
+};
+
+class ConnectionHelper
+{
+public:
+ static QString statusReasonToErrorName(Tp::ConnectionStatusReason reason,
+ Tp::ConnectionStatus oldStatus);
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-lowlevel.h b/qt4/TelepathyQt4/connection-lowlevel.h
new file mode 100644
index 000000000..30f1bc80c
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-lowlevel.h
@@ -0,0 +1,96 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_lowlevel_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_lowlevel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class Connection;
+class PendingChannel;
+class PendingContactAttributes;
+class PendingHandles;
+class PendingOperation;
+class PendingReady;
+
+class TELEPATHY_QT4_EXPORT ConnectionLowlevel : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ConnectionLowlevel)
+
+public:
+ ~ConnectionLowlevel();
+
+ bool isValid() const;
+ ConnectionPtr connection() const;
+
+ PendingReady *requestConnect(const Features &requestedFeatures = Features());
+ PendingOperation *requestDisconnect();
+
+ SimpleStatusSpecMap allowedPresenceStatuses() const;
+ uint maxPresenceStatusMessageLength() const;
+
+ PendingOperation *setSelfPresence(const QString &status, const QString &statusMessage);
+
+ PendingChannel *createChannel(const QVariantMap &request);
+ PendingChannel *createChannel(const QVariantMap &request, int timeout);
+ PendingChannel *ensureChannel(const QVariantMap &request);
+ PendingChannel *ensureChannel(const QVariantMap &request, int timeout);
+
+ PendingHandles *requestHandles(HandleType handleType, const QStringList &names);
+ PendingHandles *referenceHandles(HandleType handleType, const UIntList &handles);
+
+ PendingContactAttributes *contactAttributes(const UIntList &handles,
+ const QStringList &interfaces, bool reference = true);
+ QStringList contactAttributeInterfaces() const;
+
+ void injectContactIds(const HandleIdentifierMap &contactIds);
+ void injectContactId(uint handle, const QString &contactId);
+
+private:
+ friend class Connection;
+ friend class ContactManager;
+ friend class PendingContacts;
+
+ TELEPATHY_QT4_NO_EXPORT ConnectionLowlevel(Connection *parent);
+
+ TELEPATHY_QT4_NO_EXPORT bool hasImmortalHandles() const;
+
+ TELEPATHY_QT4_NO_EXPORT bool hasContactId(uint handle) const;
+ TELEPATHY_QT4_NO_EXPORT QString contactId(uint handle) const;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-manager-internal.h b/qt4/TelepathyQt4/connection-manager-internal.h
new file mode 100644
index 000000000..8d622c453
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-manager-internal.h
@@ -0,0 +1,159 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_manager_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_manager_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/PendingStringList>
+
+#include <QDBusConnection>
+#include <QLatin1String>
+#include <QQueue>
+#include <QSet>
+#include <QString>
+#include <QWeakPointer>
+
+namespace Tp
+{
+
+class ConnectionManager;
+class ReadinessHelper;
+
+struct TELEPATHY_QT4_NO_EXPORT ConnectionManager::Private
+{
+ Private(ConnectionManager *parent, const QString &name,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ ~Private();
+
+ bool parseConfigFile();
+
+ static void introspectMain(Private *self);
+ void introspectProtocolsLegacy();
+ void introspectParametersLegacy();
+
+ static QString makeBusName(const QString &name);
+ static QString makeObjectPath(const QString &name);
+
+ class PendingNames;
+ class ProtocolWrapper;
+
+ // Public object
+ ConnectionManager *parent;
+ ConnectionManagerLowlevelPtr lowlevel;
+
+ QString name;
+
+ // Instance of generated interface class
+ Client::ConnectionManagerInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ ConnectionFactoryConstPtr connFactory;
+ ChannelFactoryConstPtr chanFactory;
+ ContactFactoryConstPtr contactFactory;
+
+ // Introspection
+ QQueue<QString> parametersQueue;
+ ProtocolInfoList protocols;
+ QSet<SharedPtr<ProtocolWrapper> > wrappers;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ConnectionManagerLowlevel::Private
+{
+ Private(ConnectionManager *cm)
+ : cm(QWeakPointer<ConnectionManager>(cm))
+ {
+ }
+
+ QWeakPointer<ConnectionManager> cm;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ConnectionManager::Private::PendingNames : public PendingStringList
+{
+ Q_OBJECT
+
+public:
+ PendingNames(const QDBusConnection &bus);
+ ~PendingNames() {};
+
+private Q_SLOTS:
+ void onCallFinished(QDBusPendingCallWatcher *);
+ void continueProcessing();
+
+private:
+ void invokeMethod(const QLatin1String &method);
+ void parseResult(const QStringList &names);
+
+ QQueue<QLatin1String> mMethodsQueue;
+ QSet<QString> mResult;
+ QDBusConnection mBus;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ConnectionManager::Private::ProtocolWrapper :
+ public StatelessDBusProxy, public OptionalInterfaceFactory<ProtocolWrapper>
+{
+ Q_OBJECT
+
+public:
+ static const Feature FeatureCore;
+
+ ProtocolWrapper(const QDBusConnection &bus, const QString &busName,
+ const QString &objectPath,
+ const QString &cmName, const QString &name, const QVariantMap &props);
+ ~ProtocolWrapper();
+
+ ProtocolInfo info() const { return mInfo; }
+
+ inline Client::DBus::PropertiesInterface *propertiesInterface() const
+ {
+ return optionalInterface<Client::DBus::PropertiesInterface>(BypassInterfaceCheck);
+ }
+
+private Q_SLOTS:
+ void gotMainProperties(QDBusPendingCallWatcher *watcher);
+ void gotAvatarsProperties(QDBusPendingCallWatcher *watcher);
+ void gotPresenceProperties(QDBusPendingCallWatcher *watcher);
+
+private:
+ static void introspectMain(ProtocolWrapper *self);
+ void introspectAvatars();
+ void introspectPresence();
+
+ void continueIntrospection();
+
+ void fillRCCs();
+ bool receiveProperties(const QVariantMap &props);
+
+ ReadinessHelper *mReadinessHelper;
+ ProtocolInfo mInfo;
+ QVariantMap mImmutableProps;
+ QQueue<void (ProtocolWrapper::*)()> introspectQueue;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-manager-lowlevel.h b/qt4/TelepathyQt4/connection-manager-lowlevel.h
new file mode 100644
index 000000000..d55e53e74
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-manager-lowlevel.h
@@ -0,0 +1,64 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_manager_lowlevel_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_manager_lowlevel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class PendingConnection;
+
+class TELEPATHY_QT4_EXPORT ConnectionManagerLowlevel : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ConnectionManagerLowlevel)
+
+public:
+ ~ConnectionManagerLowlevel();
+
+ bool isValid() const;
+ ConnectionManagerPtr connectionManager() const;
+
+ PendingConnection *requestConnection(const QString &protocolName,
+ const QVariantMap &parameters);
+
+private:
+ friend class ConnectionManager;
+
+ TELEPATHY_QT4_NO_EXPORT ConnectionManagerLowlevel(ConnectionManager *parent);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-manager.cpp b/qt4/TelepathyQt4/connection-manager.cpp
new file mode 100644
index 000000000..6b081c9e8
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-manager.cpp
@@ -0,0 +1,1078 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/ConnectionManagerLowlevel>
+#include "TelepathyQt4/connection-manager-internal.h"
+
+#include "TelepathyQt4/_gen/cli-connection-manager-body.hpp"
+#include "TelepathyQt4/_gen/cli-connection-manager.moc.hpp"
+#include "TelepathyQt4/_gen/connection-manager.moc.hpp"
+#include "TelepathyQt4/_gen/connection-manager-internal.moc.hpp"
+#include "TelepathyQt4/_gen/connection-manager-lowlevel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/PendingConnection>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/ManagerFile>
+#include <TelepathyQt4/Types>
+
+#include <QDBusConnectionInterface>
+#include <QQueue>
+#include <QStringList>
+#include <QTimer>
+
+namespace
+{
+
+bool checkValidProtocolName(const QString &protocolName)
+{
+ if (!protocolName[0].isLetter()) {
+ return false;
+ }
+
+ int length = protocolName.length();
+ if (length <= 1) {
+ return true;
+ }
+
+ QChar ch;
+ for (int i = 1; i < length; ++i) {
+ ch = protocolName[i];
+ if (!ch.isLetterOrNumber() && ch != QLatin1Char('-')) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+}
+
+namespace Tp
+{
+
+ConnectionManager::Private::PendingNames::PendingNames(const QDBusConnection &bus)
+ : PendingStringList(SharedPtr<RefCounted>()),
+ mBus(bus)
+{
+ mMethodsQueue.enqueue(QLatin1String("ListNames"));
+ mMethodsQueue.enqueue(QLatin1String("ListActivatableNames"));
+ QTimer::singleShot(0, this, SLOT(continueProcessing()));
+}
+
+void ConnectionManager::Private::PendingNames::onCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QStringList> reply = *watcher;
+
+ if (!reply.isError()) {
+ parseResult(reply.value());
+ continueProcessing();
+ } else {
+ warning() << "Failure: error " << reply.error().name() <<
+ ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::Private::PendingNames::continueProcessing()
+{
+ if (!mMethodsQueue.isEmpty()) {
+ QLatin1String method = mMethodsQueue.dequeue();
+ invokeMethod(method);
+ }
+ else {
+ debug() << "Success: list" << mResult;
+ setResult(mResult.toList());
+ setFinished();
+ }
+}
+
+void ConnectionManager::Private::PendingNames::invokeMethod(const QLatin1String &method)
+{
+ QDBusPendingCall call = mBus.interface()->asyncCallWithArgumentList(
+ method, QList<QVariant>());
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void ConnectionManager::Private::PendingNames::parseResult(const QStringList &names)
+{
+ foreach (const QString name, names) {
+ if (name.startsWith(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_MANAGER "."))) {
+ mResult << name.right(name.length() - 44);
+ }
+ }
+}
+
+const Feature ConnectionManager::Private::ProtocolWrapper::FeatureCore =
+ Feature(QLatin1String(ConnectionManager::Private::ProtocolWrapper::staticMetaObject.className()),
+ 0, true);
+
+ConnectionManager::Private::ProtocolWrapper::ProtocolWrapper(
+ const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const QString &cmName, const QString &name, const QVariantMap &props)
+ : StatelessDBusProxy(bus, busName, objectPath, FeatureCore),
+ OptionalInterfaceFactory<ProtocolWrapper>(this),
+ mReadinessHelper(readinessHelper()),
+ mInfo(ProtocolInfo(cmName, name)),
+ mImmutableProps(props)
+{
+ fillRCCs();
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As Protocol does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &ProtocolWrapper::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ mReadinessHelper->addIntrospectables(introspectables);
+}
+
+ConnectionManager::Private::ProtocolWrapper::~ProtocolWrapper()
+{
+}
+
+void ConnectionManager::Private::ProtocolWrapper::introspectMain(
+ ConnectionManager::Private::ProtocolWrapper *self)
+{
+ Client::DBus::PropertiesInterface *properties = self->propertiesInterface();
+ Q_ASSERT(properties != 0);
+
+ if (self->receiveProperties(self->mImmutableProps)) {
+ debug() << "Got everything we want from the immutable props for" <<
+ self->info().name();
+
+ if (self->hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_AVATARS)) {
+ self->introspectQueue.enqueue(&ProtocolWrapper::introspectAvatars);
+ } else {
+ debug() << "Full functionality requires CM support for the Protocol.Avatars interface";
+ }
+
+ if (self->hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_PRESENCE)) {
+ self->introspectQueue.enqueue(&ProtocolWrapper::introspectPresence);
+ } else {
+ debug() << "Full functionality requires CM support for the Protocol.Presence interface";
+ }
+
+ self->continueIntrospection();
+ return;
+ }
+
+ debug() << "Not enough immutable properties, calling Properties::GetAll(Protocol) for" <<
+ self->info().name();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->GetAll(TP_QT4_IFACE_PROTOCOL),
+ self);
+ self->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+}
+
+void ConnectionManager::Private::ProtocolWrapper::introspectAvatars()
+{
+ Client::DBus::PropertiesInterface *properties = propertiesInterface();
+ Q_ASSERT(properties != 0);
+
+ debug() << "Calling Properties::GetAll(Protocol.Avatars)";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->GetAll(TP_QT4_IFACE_PROTOCOL_INTERFACE_AVATARS),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotAvatarsProperties(QDBusPendingCallWatcher*)));
+}
+
+void ConnectionManager::Private::ProtocolWrapper::introspectPresence()
+{
+ Client::DBus::PropertiesInterface *properties = propertiesInterface();
+ Q_ASSERT(properties != 0);
+
+ debug() << "Calling Properties::GetAll(Protocol.Presence)";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->GetAll(TP_QT4_IFACE_PROTOCOL_INTERFACE_PRESENCE),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotPresenceProperties(QDBusPendingCallWatcher*)));
+}
+
+void ConnectionManager::Private::ProtocolWrapper::continueIntrospection()
+{
+ if (introspectQueue.isEmpty()) {
+ mReadinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ (this->*(introspectQueue.dequeue()))();
+ }
+}
+
+void ConnectionManager::Private::ProtocolWrapper::gotMainProperties(
+ QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties.GetAll(Protocol)";
+
+ QVariantMap unqualifiedProps = reply.value();
+ QVariantMap qualifiedProps;
+ foreach (QString unqualified, unqualifiedProps.keys()) {
+ qualifiedProps.insert(
+ QString(QLatin1String("%1.%2")).
+ arg(TP_QT4_IFACE_PROTOCOL).
+ arg(unqualified),
+ unqualifiedProps.value(unqualified));
+ }
+ receiveProperties(qualifiedProps);
+ } else {
+ warning().nospace() <<
+ "Properties.GetAll(Protocol) failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ warning() << " Full functionality requires CM support for the Protocol interface";
+ }
+
+ if (hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_AVATARS)) {
+ introspectQueue.enqueue(&ProtocolWrapper::introspectAvatars);
+ } else {
+ debug() << "Full functionality requires CM support for the Protocol.Avatars interface";
+ }
+
+ if (hasInterface(TP_QT4_IFACE_PROTOCOL_INTERFACE_PRESENCE)) {
+ introspectQueue.enqueue(&ProtocolWrapper::introspectPresence);
+ } else {
+ debug() << "Full functionality requires CM support for the Protocol.Presence interface";
+ }
+
+ continueIntrospection();
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::Private::ProtocolWrapper::gotAvatarsProperties(
+ QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties.GetAll(Protocol.Avatars)";
+ props = reply.value();
+
+ QStringList supportedMimeTypes = qdbus_cast<QStringList>(
+ props[QLatin1String("SupportedAvatarMIMETypes")]);
+ uint minHeight = qdbus_cast<uint>(props[QLatin1String("MinimumAvatarHeight")]);
+ uint maxHeight = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarHeight")]);
+ uint recommendedHeight = qdbus_cast<uint>(
+ props[QLatin1String("RecommendedAvatarHeight")]);
+ uint minWidth = qdbus_cast<uint>(props[QLatin1String("MinimumAvatarWidth")]);
+ uint maxWidth = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarWidth")]);
+ uint recommendedWidth = qdbus_cast<uint>(
+ props[QLatin1String("RecommendedAvatarWidth")]);
+ uint maxBytes = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarBytes")]);
+ mInfo.setAvatarRequirements(AvatarSpec(supportedMimeTypes,
+ minHeight, maxHeight, recommendedHeight,
+ minWidth, maxWidth, recommendedWidth,
+ maxBytes));
+ } else {
+ warning().nospace() <<
+ "Properties.GetAll(Protocol.Avatars) failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ warning() << " Full functionality requires CM support for the Protocol.Avatars interface";
+ }
+
+ continueIntrospection();
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::Private::ProtocolWrapper::gotPresenceProperties(
+ QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties.GetAll(Protocol.Presence)";
+ props = reply.value();
+ mInfo.setAllowedPresenceStatuses(PresenceSpecList(qdbus_cast<SimpleStatusSpecMap>(
+ props[QLatin1String("Statuses")])));
+ } else {
+ warning().nospace() <<
+ "Properties.GetAll(Protocol.Presence) failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ warning() << " Full functionality requires CM support for the Protocol.Presence interface";
+ }
+
+ continueIntrospection();
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::Private::ProtocolWrapper::fillRCCs()
+{
+ RequestableChannelClassList classes;
+
+ QVariantMap fixedProps;
+ QStringList allowedProps;
+
+ // Text chatrooms
+ fixedProps.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ fixedProps.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ static_cast<uint>(HandleTypeRoom));
+
+ RequestableChannelClass textChatroom = {fixedProps, allowedProps};
+ classes.append(textChatroom);
+
+ // 1-1 text chats
+ fixedProps[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")] =
+ static_cast<uint>(HandleTypeContact);
+
+ RequestableChannelClass text = {fixedProps, allowedProps};
+ classes.append(text);
+
+ // Media calls
+ fixedProps[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")] =
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+
+ RequestableChannelClass media = {fixedProps, allowedProps};
+ classes.append(media);
+
+ // Initially audio-only calls
+ allowedProps.push_back(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"));
+
+ RequestableChannelClass initialAudio = {fixedProps, allowedProps};
+ classes.append(initialAudio);
+
+ // Initially AV calls
+ allowedProps.push_back(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"));
+
+ RequestableChannelClass initialAV = {fixedProps, allowedProps};
+ classes.append(initialAV);
+
+ // Initially video-only calls
+ allowedProps.removeAll(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"));
+
+ RequestableChannelClass initialVideo = {fixedProps, allowedProps};
+ classes.append(initialVideo);
+
+ // That also settles upgrading calls, because the media classes don't have ImmutableStreams
+
+ mInfo.setRequestableChannelClasses(classes);
+}
+
+bool ConnectionManager::Private::ProtocolWrapper::receiveProperties(const QVariantMap &props)
+{
+ bool gotEverything =
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Interfaces")) &&
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Parameters")) &&
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".ConnectionInterfaces")) &&
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses")) &&
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".VCardField")) &&
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".EnglishName")) &&
+ props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Icon"));
+
+ setInterfaces(qdbus_cast<QStringList>(
+ props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Interfaces")]));
+ mReadinessHelper->setInterfaces(interfaces());
+
+ ParamSpecList parameters = qdbus_cast<ParamSpecList>(
+ props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Parameters")]);
+ foreach (const ParamSpec &spec, parameters) {
+ mInfo.addParameter(spec);
+ }
+
+ mInfo.setVCardField(qdbus_cast<QString>(
+ props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".VCardField")]));
+ QString englishName = qdbus_cast<QString>(
+ props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".EnglishName")]);
+ if (englishName.isEmpty()) {
+ QStringList words = mInfo.name().split(QLatin1Char('-'));
+ for (int i = 0; i < words.size(); ++i) {
+ words[i][0] = words[i].at(0).toUpper();
+ }
+ englishName = words.join(QLatin1String(" "));
+ }
+ mInfo.setEnglishName(englishName);
+ QString iconName = qdbus_cast<QString>(
+ props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Icon")]);
+ if (iconName.isEmpty()) {
+ iconName = QString(QLatin1String("im-%1")).arg(mInfo.name());
+ }
+ mInfo.setIconName(iconName);
+
+ // Don't overwrite the everything-is-possible RCCs with an empty list if there is no RCCs key
+ if (props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses"))) {
+ mInfo.setRequestableChannelClasses(qdbus_cast<RequestableChannelClassList>(
+ props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses")]));
+ }
+
+ return gotEverything;
+}
+
+ConnectionManager::Private::Private(ConnectionManager *parent, const QString &name,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : parent(parent),
+ lowlevel(ConnectionManagerLowlevelPtr(new ConnectionManagerLowlevel(parent))),
+ name(name),
+ baseInterface(new Client::ConnectionManagerInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ connFactory(connFactory),
+ chanFactory(chanFactory),
+ contactFactory(contactFactory)
+{
+ debug() << "Creating new ConnectionManager:" << parent->busName();
+
+ if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the connection factory is not the proxy connection";
+ }
+
+ if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection";
+ }
+
+ ReadinessHelper::Introspectables introspectables;
+
+ // As ConnectionManager does not have predefined statuses let's simulate one (0)
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+ConnectionManager::Private::~Private()
+{
+ delete baseInterface;
+}
+
+bool ConnectionManager::Private::parseConfigFile()
+{
+ ManagerFile f(name);
+ if (!f.isValid()) {
+ return false;
+ }
+
+ foreach (const QString &protocol, f.protocols()) {
+ ProtocolInfo info(name, protocol);
+
+ foreach (const ParamSpec &spec, f.parameters(protocol)) {
+ info.addParameter(spec);
+ }
+ info.setRequestableChannelClasses(
+ f.requestableChannelClasses(protocol));
+ info.setVCardField(f.vcardField(protocol));
+ info.setEnglishName(f.englishName(protocol));
+ info.setIconName(f.iconName(protocol));
+ info.setAllowedPresenceStatuses(f.allowedPresenceStatuses(protocol));
+ info.setAvatarRequirements(f.avatarRequirements(protocol));
+
+ protocols.append(info);
+ }
+
+ return true;
+}
+
+void ConnectionManager::Private::introspectMain(ConnectionManager::Private *self)
+{
+ if (self->parseConfigFile()) {
+ self->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ return;
+ }
+
+ warning() << "Error parsing config file for connection manager"
+ << self->name << "- introspecting";
+
+ debug() << "Calling Properties::GetAll(ConnectionManager)";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_MANAGER)),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+}
+
+void ConnectionManager::Private::introspectProtocolsLegacy()
+{
+ debug() << "Calling ConnectionManager::ListProtocols";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ baseInterface->ListProtocols(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotProtocolsLegacy(QDBusPendingCallWatcher*)));
+}
+
+void ConnectionManager::Private::introspectParametersLegacy()
+{
+ foreach (const QString &protocolName, parametersQueue) {
+ debug() << "Calling ConnectionManager::GetParameters(" << protocolName << ")";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ baseInterface->GetParameters(protocolName), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotParametersLegacy(QDBusPendingCallWatcher*)));
+ }
+}
+
+QString ConnectionManager::Private::makeBusName(const QString &name)
+{
+ return QString(QLatin1String(
+ TELEPATHY_CONNECTION_MANAGER_BUS_NAME_BASE)).append(name);
+}
+
+QString ConnectionManager::Private::makeObjectPath(const QString &name)
+{
+ return QString(QLatin1String(
+ TELEPATHY_CONNECTION_MANAGER_OBJECT_PATH_BASE)).append(name);
+}
+
+/**
+ * \class ConnectionManagerLowlevel
+ * \ingroup clientcm
+ * \headerfile TelepathyQt4/connection-manager-lowlevel.h <TelepathyQt4/ConnectionManagerLowlevel>
+ *
+ * \brief The ConnectionManagerLowlevel class extends ConnectionManager with
+ * support to low-level features.
+ */
+
+ConnectionManagerLowlevel::ConnectionManagerLowlevel(ConnectionManager *cm)
+ : mPriv(new Private(cm))
+{
+}
+
+ConnectionManagerLowlevel::~ConnectionManagerLowlevel()
+{
+ delete mPriv;
+}
+
+bool ConnectionManagerLowlevel::isValid() const
+{
+ return !mPriv->cm.isNull();
+}
+
+ConnectionManagerPtr ConnectionManagerLowlevel::connectionManager() const
+{
+ return ConnectionManagerPtr(mPriv->cm);
+}
+
+/**
+ * \class ConnectionManager
+ * \ingroup clientcm
+ * \headerfile TelepathyQt4/connection-manager.h <TelepathyQt4/ConnectionManager>
+ *
+ * \brief The ConnectionManager class represents a Telepathy connection manager.
+ *
+ * Connection managers allow connections to be made on one or more protocols.
+ *
+ * Most client applications should use this functionality via the
+ * AccountManager, to allow connections to be shared between client
+ * applications.
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * ConnectionManager object usable.
+ *
+ * Note that this feature must be enabled in order to use most ConnectionManager
+ * methods.
+ * See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature ConnectionManager::FeatureCore = Feature(QLatin1String(ConnectionManager::staticMetaObject.className()), 0, true);
+
+/**
+ * Create a new ConnectionManager object.
+ *
+ * The instance will use a connection factory creating Tp::Connection objects with no features
+ * ready, and a channel factory creating stock Telepathy-Qt4 channel subclasses, as appropriate,
+ * with no features ready.
+ *
+ * \param bus QDBusConnection to use.
+ * \param name Name of the connection manager.
+ * \return A ConnectionManagerPtr object pointing to the newly created
+ * ConnectionManager object.
+ */
+ConnectionManagerPtr ConnectionManager::create(const QDBusConnection &bus, const QString &name)
+{
+ return ConnectionManagerPtr(new ConnectionManager(QDBusConnection::sessionBus(), name,
+ ConnectionFactory::create(bus), ChannelFactory::create(bus),
+ ContactFactory::create()));
+}
+
+/**
+ * Create a new ConnectionManager using QDBusConnection::sessionBus() and the given factories.
+ *
+ * The channel factory is passed to any Connection objects created by this manager
+ * object. In fact, they're not used directly by ConnectionManager at all.
+ *
+ * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus().
+ *
+ * \param name Name of the connection manager.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ConnectionManagerPtr object pointing to the newly created
+ * ConnectionManager object.
+ */
+ConnectionManagerPtr ConnectionManager::create(const QString &name,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ConnectionManagerPtr(new ConnectionManager(QDBusConnection::sessionBus(), name,
+ connectionFactory, channelFactory, contactFactory));
+}
+
+/**
+ * Create a new ConnectionManager using the given \a bus and the given factories.
+ *
+ * The channel factory is passed to any Connection objects created by this manager
+ * object. In fact, they're not used directly by ConnectionManager at all.
+ *
+ * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus().
+ *
+ * \param bus QDBusConnection to use.
+ * \param name Name of the connection manager.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ConnectionManagerPtr object pointing to the newly created
+ * ConnectionManager object.
+ */
+ConnectionManagerPtr ConnectionManager::create(const QDBusConnection &bus,
+ const QString &name,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ConnectionManagerPtr(new ConnectionManager(bus, name,
+ connectionFactory, channelFactory, contactFactory));
+}
+
+/**
+ * Construct a new ConnectionManager object using the given \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param name Name of the connection manager.
+ */
+ConnectionManager::ConnectionManager(const QDBusConnection &bus,
+ const QString &name,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : StatelessDBusProxy(bus, Private::makeBusName(name),
+ Private::makeObjectPath(name), FeatureCore),
+ OptionalInterfaceFactory<ConnectionManager>(this),
+ mPriv(new Private(this, name, connectionFactory, channelFactory, contactFactory))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ConnectionManager::~ConnectionManager()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the short name of the connection manager (e.g. "gabble").
+ *
+ * \return The name of the connection manager.
+ */
+QString ConnectionManager::name() const
+{
+ return mPriv->name;
+}
+
+/**
+ * Return the connection factory used by this manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ConnectionFactory object.
+ */
+ConnectionFactoryConstPtr ConnectionManager::connectionFactory() const
+{
+ return mPriv->connFactory;
+}
+
+/**
+ * Return the channel factory used by this manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ChannelFactory object.
+ */
+ChannelFactoryConstPtr ConnectionManager::channelFactory() const
+{
+ return mPriv->chanFactory;
+}
+
+/**
+ * Return the contact factory used by this manager.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the manager would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ContactFactory object.
+ */
+ContactFactoryConstPtr ConnectionManager::contactFactory() const
+{
+ return mPriv->contactFactory;
+}
+
+/**
+ * Return a list of strings identifying the protocols supported by this
+ * connection manager, as described in the \telepathy_spec (e.g. "jabber").
+ *
+ * These identifiers are not intended to be displayed to users directly; user
+ * interfaces are responsible for mapping them to localized strings.
+ *
+ * This method requires ConnectionManager::FeatureCore to be ready.
+ *
+ * \return A list of supported protocols.
+ */
+QStringList ConnectionManager::supportedProtocols() const
+{
+ QStringList protocols;
+ foreach (const ProtocolInfo &info, mPriv->protocols) {
+ protocols.append(info.name());
+ }
+ return protocols;
+}
+
+/**
+ * Return a list of protocols info for this connection manager.
+ *
+ * Note that the returned ProtocolInfoList contents should not be freed.
+ *
+ * This method requires ConnectionManager::FeatureCore to be ready.
+ *
+ * \return A list of ṔrotocolInfo.
+ */
+const ProtocolInfoList &ConnectionManager::protocols() const
+{
+ return mPriv->protocols;
+}
+
+/**
+ * Return whether this connection manager implements the protocol specified by
+ * \a protocolName.
+ *
+ * This method requires ConnectionManager::FeatureCore to be ready.
+ *
+ * \return \c true if the protocol is supported, \c false otherwise.
+ * \sa protocol(), protocols()
+ */
+bool ConnectionManager::hasProtocol(const QString &protocolName) const
+{
+ foreach (const ProtocolInfo &info, mPriv->protocols) {
+ if (info.name() == protocolName) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return the ProtocolInfo object for the protocol specified by
+ * \a protocolName.
+ *
+ * This method requires ConnectionManager::FeatureCore to be ready.
+ *
+ * \param protocolName The name of the protocol.
+ * \return A ProtocolInfo object which will return \c for ProtocolInfo::isValid() if the protocol
+ * specified by \a protocolName is not supported.
+ * \sa hasProtocol()
+ */
+ProtocolInfo ConnectionManager::protocol(const QString &protocolName) const
+{
+ foreach (const ProtocolInfo &info, mPriv->protocols) {
+ if (info.name() == protocolName) {
+ return info;
+ }
+ }
+ return ProtocolInfo();
+}
+
+/**
+ * Request a Connection object representing a given account on a given protocol
+ * with the given parameters.
+ *
+ * Return a pending operation representing the Connection object which will
+ * succeed when the connection has been created or fail if an error occurred.
+ *
+ * \param protocol Name of the protocol to create the account for.
+ * \param parameters Account parameters.
+ * \return A PendingOperation which will emit PendingConnection::finished when
+ * the account has been created of failed its creation process.
+ */
+PendingConnection *ConnectionManagerLowlevel::requestConnection(const QString &protocol,
+ const QVariantMap &parameters)
+{
+ if (!isValid()) {
+ return new PendingConnection(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection manager has been destroyed already"));
+ }
+
+ return new PendingConnection(ConnectionManagerPtr(mPriv->cm),
+ protocol, parameters);
+}
+
+/**
+ * Return a pending operation from which a list of all installed connection
+ * manager short names (such as "gabble" or "haze") can be retrieved if it
+ * succeeds.
+ *
+ * \return A PendingStringList which will emit PendingStringList::finished
+ * when this object has finished or failed getting the connection
+ * manager names.
+ */
+PendingStringList *ConnectionManager::listNames(const QDBusConnection &bus)
+{
+ return new ConnectionManager::Private::PendingNames(bus);
+}
+
+ConnectionManagerLowlevelPtr ConnectionManager::lowlevel()
+{
+ return mPriv->lowlevel;
+}
+
+ConnectionManagerLowlevelConstPtr ConnectionManager::lowlevel() const
+{
+ return mPriv->lowlevel;
+}
+
+/**
+ * Return the Client::ConnectionManagerInterface for this ConnectionManager.
+ * This method is protected since the convenience methods provided by this
+ * class should generally be used instead of calling D-Bus methods
+ * directly.
+ *
+ * \return A pointer to the existing Client::ConnectionManagerInterface for this
+ * ConnectionManager object.
+ */
+Client::ConnectionManagerInterface *ConnectionManager::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+/**** Private ****/
+void ConnectionManager::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to Properties.GetAll(ConnectionManager)";
+ props = reply.value();
+
+ // If Interfaces is not supported, the spec says to assume it's
+ // empty, so keep the empty list mPriv was initialized with
+ if (props.contains(QLatin1String("Interfaces"))) {
+ setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")]));
+ mPriv->readinessHelper->setInterfaces(interfaces());
+ }
+ } else {
+ warning().nospace() <<
+ "Properties.GetAll(ConnectionManager) failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error());
+ watcher->deleteLater();
+ return;
+ }
+
+ ProtocolPropertiesMap protocolsMap =
+ qdbus_cast<ProtocolPropertiesMap>(props[QLatin1String("Protocols")]);
+ if (!protocolsMap.isEmpty()) {
+ ProtocolPropertiesMap::const_iterator i = protocolsMap.constBegin();
+ ProtocolPropertiesMap::const_iterator end = protocolsMap.constEnd();
+ while (i != end) {
+ QString protocolName = i.key();
+ if (!checkValidProtocolName(protocolName)) {
+ warning() << "Protocol has an invalid name" << protocolName << "- ignoring";
+ continue;
+ }
+
+ QString escapedProtocolName = protocolName;
+ escapedProtocolName.replace(QLatin1Char('-'), QLatin1Char('_'));
+ QString protocolPath = QString(
+ QLatin1String("%1/%2")).arg(objectPath()).arg(escapedProtocolName);
+ SharedPtr<Private::ProtocolWrapper> wrapper = SharedPtr<Private::ProtocolWrapper>(
+ new Private::ProtocolWrapper(
+ dbusConnection(), busName(), protocolPath,
+ mPriv->name, protocolName, i.value()));
+ connect(wrapper->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onProtocolReady(Tp::PendingOperation*)));
+ mPriv->wrappers.insert(wrapper);
+ ++i;
+ }
+ } else {
+ mPriv->introspectProtocolsLegacy();
+ }
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::gotProtocolsLegacy(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QStringList> reply = *watcher;
+ QStringList protocolsNames;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to ConnectionManager.ListProtocols";
+ protocolsNames = reply.value();
+
+ foreach (const QString &protocolName, protocolsNames) {
+ mPriv->protocols.append(ProtocolInfo(mPriv->name, protocolName));
+ mPriv->parametersQueue.enqueue(protocolName);
+ }
+
+ mPriv->introspectParametersLegacy();
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error());
+
+ warning().nospace() <<
+ "ConnectionManager.ListProtocols failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+
+ // FIXME shouldn't this invalidate the CM?
+ }
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::gotParametersLegacy(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<ParamSpecList> reply = *watcher;
+ QString protocolName = mPriv->parametersQueue.dequeue();
+ bool found = false;
+ int pos = 0;
+ foreach (const ProtocolInfo &info, mPriv->protocols) {
+ if (info.name() == protocolName) {
+ found = true;
+ break;
+ }
+ ++pos;
+ }
+ Q_ASSERT(found);
+
+ if (!reply.isError()) {
+ debug() << QString(QLatin1String("Got reply to ConnectionManager.GetParameters(%1)")).arg(protocolName);
+ ParamSpecList parameters = reply.value();
+ ProtocolInfo &info = mPriv->protocols[pos];
+ foreach (const ParamSpec &spec, parameters) {
+ debug() << "Parameter" << spec.name << "has flags" << spec.flags
+ << "and signature" << spec.signature;
+
+ info.addParameter(spec);
+ }
+ } else {
+ // let's remove this protocol as we can't get the params
+ mPriv->protocols.removeAt(pos);
+
+ warning().nospace() <<
+ QString(QLatin1String("ConnectionManager.GetParameters(%1) failed: ")).arg(protocolName) <<
+ reply.error().name() << ": " << reply.error().message();
+ }
+
+ if (mPriv->parametersQueue.isEmpty()) {
+ if (!mPriv->protocols.isEmpty()) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ // we could not retrieve the params for any protocol, fail core.
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error());
+ }
+ }
+
+ watcher->deleteLater();
+}
+
+void ConnectionManager::onProtocolReady(Tp::PendingOperation *op)
+{
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ SharedPtr<Private::ProtocolWrapper> wrapper =
+ SharedPtr<Private::ProtocolWrapper>::qObjectCast(pr->proxy());
+ ProtocolInfo info = wrapper->info();
+
+ mPriv->wrappers.remove(wrapper);
+
+ if (!op->isError()) {
+ mPriv->protocols.append(info);
+ } else {
+ warning().nospace() << "Protocol(" << info.name() << ")::becomeReady "
+ "failed: " << op->errorName() << ": " << op->errorMessage();
+ }
+
+ if (mPriv->wrappers.isEmpty()) {
+ if (!mPriv->protocols.isEmpty()) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ // we could not make any Protocol objects ready, fail core.
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ op->errorName(), op->errorMessage());
+ }
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/connection-manager.h b/qt4/TelepathyQt4/connection-manager.h
new file mode 100644
index 000000000..36e0e7a18
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-manager.h
@@ -0,0 +1,125 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_manager_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_manager_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-connection-manager.h>
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/ProtocolInfo>
+#include <TelepathyQt4/ProtocolParameter>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class ConnectionManagerLowlevel;
+class PendingConnection;
+class PendingStringList;
+
+class TELEPATHY_QT4_EXPORT ConnectionManager : public StatelessDBusProxy,
+ public OptionalInterfaceFactory<ConnectionManager>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ConnectionManager)
+ Q_PROPERTY(QString name READ name)
+ Q_PROPERTY(QStringList supportedProtocols READ supportedProtocols)
+ Q_PROPERTY(ProtocolInfoList protocols READ protocols)
+
+public:
+ static const Feature FeatureCore;
+
+ static ConnectionManagerPtr create(const QDBusConnection &bus,
+ const QString &name);
+ static ConnectionManagerPtr create(const QString &name,
+ const ConnectionFactoryConstPtr &connectionFactory =
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ const ChannelFactoryConstPtr &channelFactory =
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+ static ConnectionManagerPtr create(const QDBusConnection &bus,
+ const QString &name,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+
+ virtual ~ConnectionManager();
+
+ QString name() const;
+
+ ConnectionFactoryConstPtr connectionFactory() const;
+ ChannelFactoryConstPtr channelFactory() const;
+ ContactFactoryConstPtr contactFactory() const;
+
+ QStringList supportedProtocols() const;
+ const ProtocolInfoList &protocols() const;
+ bool hasProtocol(const QString &protocolName) const;
+ ProtocolInfo protocol(const QString &protocolName) const;
+
+ static PendingStringList *listNames(
+ const QDBusConnection &bus = QDBusConnection::sessionBus());
+
+#if defined(BUILDING_TELEPATHY_QT4) || defined(TP_QT4_ENABLE_LOWLEVEL_API)
+ ConnectionManagerLowlevelPtr lowlevel();
+ ConnectionManagerLowlevelConstPtr lowlevel() const;
+#endif
+
+protected:
+ ConnectionManager(const QDBusConnection &bus, const QString &name,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+
+ Client::ConnectionManagerInterface *baseInterface() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void gotProtocolsLegacy(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void gotParametersLegacy(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void onProtocolReady(Tp::PendingOperation *);
+
+private:
+ friend class PendingConnection;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/connection-manager.xml b/qt4/TelepathyQt4/connection-manager.xml
new file mode 100644
index 000000000..597a78dfe
--- /dev/null
+++ b/qt4/TelepathyQt4/connection-manager.xml
@@ -0,0 +1,12 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Connection Manager interfaces</tp:title>
+
+<xi:include href="../spec/Connection_Manager.xml"/>
+<xi:include href="../spec/Protocol.xml"/>
+<xi:include href="../spec/Protocol_Interface_Avatars.xml"/>
+<xi:include href="../spec/Protocol_Interface_Presence.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/connection.cpp b/qt4/TelepathyQt4/connection.cpp
new file mode 100644
index 000000000..4bec14bde
--- /dev/null
+++ b/qt4/TelepathyQt4/connection.cpp
@@ -0,0 +1,2578 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include "TelepathyQt4/connection-internal.h"
+
+#include "TelepathyQt4/_gen/cli-connection.moc.hpp"
+#include "TelepathyQt4/_gen/cli-connection-body.hpp"
+#include "TelepathyQt4/_gen/connection.moc.hpp"
+#include "TelepathyQt4/_gen/connection-internal.moc.hpp"
+#include "TelepathyQt4/_gen/connection-lowlevel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContactAttributes>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include <QMap>
+#include <QMetaObject>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QPair>
+#include <QQueue>
+#include <QString>
+#include <QTimer>
+#include <QtGlobal>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT Connection::Private
+{
+ Private(Connection *parent,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ ~Private();
+
+ void init();
+
+ static void introspectMain(Private *self);
+ void introspectMainFallbackStatus();
+ void introspectMainFallbackInterfaces();
+ void introspectMainFallbackSelfHandle();
+ void introspectCapabilities();
+ void introspectContactAttributeInterfaces();
+ static void introspectSelfContact(Private *self);
+ static void introspectSimplePresence(Private *self);
+ static void introspectRoster(Private *self);
+ static void introspectRosterGroups(Private *self);
+ static void introspectBalance(Private *self);
+ static void introspectConnected(Private *self);
+
+ void continueMainIntrospection();
+ void setCurrentStatus(uint status);
+ void forceCurrentStatus(uint status);
+ void setInterfaces(const QStringList &interfaces);
+
+ // Should always be used instead of directly using baseclass invalidate()
+ void invalidateResetCaps(const QString &errorName, const QString &errorMessage);
+
+ struct HandleContext;
+
+ // Public object
+ Connection *parent;
+ ConnectionLowlevelPtr lowlevel;
+
+ // Factories
+ ChannelFactoryConstPtr chanFactory;
+ ContactFactoryConstPtr contactFactory;
+
+ // Instance of generated interface class
+ Client::ConnectionInterface *baseInterface;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ // Optional interface proxies
+ Client::ConnectionInterfaceSimplePresenceInterface *simplePresence;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ QQueue<void (Private::*)()> introspectMainQueue;
+
+ // FeatureCore
+ // keep pendingStatus and pendingStatusReason until we emit statusChanged
+ // so Connection::status() and Connection::statusReason() are consistent
+ bool introspectingConnected;
+
+ uint pendingStatus;
+ uint pendingStatusReason;
+ uint status;
+ uint statusReason;
+ ErrorDetails errorDetails;
+
+ uint selfHandle;
+
+ bool immortalHandles;
+
+ ConnectionCapabilities caps;
+
+ ContactManagerPtr contactManager;
+
+ // FeatureSelfContact
+ bool introspectingSelfContact;
+ bool reintrospectSelfContactRequired;
+ ContactPtr selfContact;
+ QStringList contactAttributeInterfaces;
+
+ // FeatureSimplePresence
+ SimpleStatusSpecMap simplePresenceStatuses;
+ uint maxPresenceStatusMessageLength;
+
+ // FeatureAccountBalance
+ CurrencyAmount accountBalance;
+
+ // misc
+ // (Bus connection name, service name) -> HandleContext
+ static QMap<QPair<QString, QString>, HandleContext *> handleContexts;
+ static QMutex handleContextsLock;
+ HandleContext *handleContext;
+
+ QString cmName;
+ QString protocolName;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ConnectionLowlevel::Private
+{
+ Private(Connection *conn)
+ : conn(QWeakPointer<Connection>(conn))
+ {
+ }
+
+ QWeakPointer<Connection> conn;
+ HandleIdentifierMap contactsIds;
+};
+
+// Handle tracking
+struct TELEPATHY_QT4_NO_EXPORT Connection::Private::HandleContext
+{
+ struct Type
+ {
+ QMap<uint, uint> refcounts;
+ QSet<uint> toRelease;
+ uint requestsInFlight;
+ bool releaseScheduled;
+
+ Type()
+ : requestsInFlight(0),
+ releaseScheduled(false)
+ {
+ }
+ };
+
+ HandleContext()
+ : refcount(0)
+ {
+ }
+
+ int refcount;
+ QMutex lock;
+ QMap<uint, Type> types;
+};
+
+Connection::Private::Private(Connection *parent,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory)
+ : parent(parent),
+ lowlevel(ConnectionLowlevelPtr(new ConnectionLowlevel(parent))),
+ chanFactory(chanFactory),
+ contactFactory(contactFactory),
+ baseInterface(new Client::ConnectionInterface(parent)),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ simplePresence(0),
+ readinessHelper(parent->readinessHelper()),
+ introspectingConnected(false),
+ pendingStatus((uint) -1),
+ pendingStatusReason(ConnectionStatusReasonNoneSpecified),
+ status((uint) -1),
+ statusReason(ConnectionStatusReasonNoneSpecified),
+ selfHandle(0),
+ immortalHandles(false),
+ contactManager(ContactManagerPtr(new ContactManager(parent))),
+ introspectingSelfContact(false),
+ reintrospectSelfContactRequired(false),
+ maxPresenceStatusMessageLength(0),
+ handleContext(0)
+{
+ accountBalance.amount = 0;
+ accountBalance.scale = 0;
+
+ Q_ASSERT(properties != 0);
+
+ if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) {
+ warning() << " The D-Bus connection in the channel factory is not the proxy connection";
+ }
+
+ init();
+
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << (uint) -1 <<
+ ConnectionStatusDisconnected <<
+ ConnectionStatusConnected, // makesSenseForStatuses
+ Features(), // dependsOnFeatures (none)
+ QStringList(), // dependsOnInterfaces (none)
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ ReadinessHelper::Introspectable introspectableSelfContact(
+ QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectSelfContact,
+ this);
+ introspectables[FeatureSelfContact] = introspectableSelfContact;
+
+ ReadinessHelper::Introspectable introspectableSimplePresence(
+ QSet<uint>() << ConnectionStatusDisconnected <<
+ ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectSimplePresence,
+ this);
+ introspectables[FeatureSimplePresence] = introspectableSimplePresence;
+
+ ReadinessHelper::Introspectable introspectableRoster(
+ QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectRoster,
+ this);
+ introspectables[FeatureRoster] = introspectableRoster;
+
+ ReadinessHelper::Introspectable introspectableRosterGroups(
+ QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureRoster, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectRosterGroups,
+ this);
+ introspectables[FeatureRosterGroups] = introspectableRosterGroups;
+
+ ReadinessHelper::Introspectable introspectableBalance(
+ QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_BALANCE), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectBalance,
+ this);
+ introspectables[FeatureAccountBalance] = introspectableBalance;
+
+ ReadinessHelper::Introspectable introspectableConnected(
+ QSet<uint>() << (uint) -1 <<
+ ConnectionStatusDisconnected <<
+ ConnectionStatusConnecting <<
+ ConnectionStatusConnected, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures (none)
+ QStringList(), // dependsOnInterfaces (none)
+ (ReadinessHelper::IntrospectFunc) &Private::introspectConnected,
+ this);
+ introspectables[FeatureConnected] = introspectableConnected;
+
+ readinessHelper->addIntrospectables(introspectables);
+ readinessHelper->setCurrentStatus(status);
+ parent->connect(readinessHelper,
+ SIGNAL(statusReady(uint)),
+ SLOT(onStatusReady(uint)));
+
+ // FIXME: QRegExp probably isn't the most efficient possible way to parse
+ // this :-)
+ QRegExp rx(QLatin1String("^" TELEPATHY_CONNECTION_OBJECT_PATH_BASE
+ "([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM
+ "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol
+ "/([_A-Za-z][_A-Za-z0-9]*)" // account-specific part
+ ));
+
+ if (rx.exactMatch(parent->objectPath())) {
+ cmName = rx.cap(1);
+ protocolName = rx.cap(2);
+ } else {
+ warning() << "Connection object path is not spec-compliant, "
+ "trying again with a different account-specific part check";
+
+ rx = QRegExp(QLatin1String("^" TELEPATHY_CONNECTION_OBJECT_PATH_BASE
+ "([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM
+ "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol
+ "/([_A-Za-z0-9]*)" // account-specific part
+ ));
+ if (rx.exactMatch(parent->objectPath())) {
+ cmName = rx.cap(1);
+ protocolName = rx.cap(2);
+ } else {
+ warning() << "Not a valid Connection object path:" <<
+ parent->objectPath();
+ }
+ }
+}
+
+Connection::Private::~Private()
+{
+ contactManager->resetRoster();
+
+ // Clear selfContact so its handle will be released cleanly before the
+ // handleContext
+ selfContact.reset();
+
+ QMutexLocker locker(&handleContextsLock);
+ // All handle contexts locked, so safe
+ if (!--handleContext->refcount) {
+ if (!immortalHandles) {
+ debug() << "Destroying HandleContext";
+
+ foreach (uint handleType, handleContext->types.keys()) {
+ HandleContext::Type type = handleContext->types[handleType];
+
+ if (!type.refcounts.empty()) {
+ debug() << " Still had references to" <<
+ type.refcounts.size() << "handles, releasing now";
+ baseInterface->ReleaseHandles(handleType, type.refcounts.keys());
+ }
+
+ if (!type.toRelease.empty()) {
+ debug() << " Was going to release" <<
+ type.toRelease.size() << "handles, doing that now";
+ baseInterface->ReleaseHandles(handleType, type.toRelease.toList());
+ }
+ }
+
+ }
+
+ handleContexts.remove(qMakePair(baseInterface->connection().name(),
+ parent->objectPath()));
+ delete handleContext;
+ } else {
+ Q_ASSERT(handleContext->refcount > 0);
+ }
+}
+
+void Connection::Private::init()
+{
+ debug() << "Connecting to ConnectionError()";
+ parent->connect(baseInterface,
+ SIGNAL(ConnectionError(QString,QVariantMap)),
+ SLOT(onConnectionError(QString,QVariantMap)));
+ debug() << "Connecting to StatusChanged()";
+ parent->connect(baseInterface,
+ SIGNAL(StatusChanged(uint,uint)),
+ SLOT(onStatusChanged(uint,uint)));
+ debug() << "Connecting to SelfHandleChanged()";
+ parent->connect(baseInterface,
+ SIGNAL(SelfHandleChanged(uint)),
+ SLOT(onSelfHandleChanged(uint)));
+
+ QMutexLocker locker(&handleContextsLock);
+ QString busConnectionName = baseInterface->connection().name();
+
+ if (handleContexts.contains(qMakePair(busConnectionName, parent->objectPath()))) {
+ debug() << "Reusing existing HandleContext for" << parent->objectPath();
+ handleContext = handleContexts[
+ qMakePair(busConnectionName, parent->objectPath())];
+ } else {
+ debug() << "Creating new HandleContext for" << parent->objectPath();
+ handleContext = new HandleContext;
+ handleContexts[
+ qMakePair(busConnectionName, parent->objectPath())] = handleContext;
+ }
+
+ // All handle contexts locked, so safe
+ ++handleContext->refcount;
+}
+
+void Connection::Private::introspectMain(Connection::Private *self)
+{
+ debug() << "Calling Properties::GetAll(Connection)";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CONNECTION)),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotMainProperties(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectMainFallbackStatus()
+{
+ debug() << "Calling GetStatus()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(baseInterface->GetStatus(),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotStatus(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectMainFallbackInterfaces()
+{
+ debug() << "Calling GetInterfaces()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(baseInterface->GetInterfaces(),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotInterfaces(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectMainFallbackSelfHandle()
+{
+ debug() << "Calling GetSelfHandle()";
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(baseInterface->GetSelfHandle(),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotSelfHandle(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectCapabilities()
+{
+ debug() << "Retrieving capabilities";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS),
+ QLatin1String("RequestableChannelClasses")), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotCapabilities(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectContactAttributeInterfaces()
+{
+ debug() << "Retrieving contact attribute interfaces";
+ QDBusPendingCall call =
+ properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS),
+ QLatin1String("ContactAttributeInterfaces"));
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(call, parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotContactAttributeInterfaces(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectSelfContact(Connection::Private *self)
+{
+ debug() << "Building self contact";
+
+ Q_ASSERT(!self->introspectingSelfContact);
+
+ self->introspectingSelfContact = true;
+ self->reintrospectSelfContactRequired = false;
+
+ PendingContacts *contacts = self->contactManager->contactsForHandles(
+ UIntList() << self->selfHandle);
+ self->parent->connect(contacts,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotSelfContact(Tp::PendingOperation*)));
+}
+
+void Connection::Private::introspectSimplePresence(Connection::Private *self)
+{
+ Q_ASSERT(self->properties != 0);
+
+ debug() << "Calling Properties::Get("
+ "Connection.I.SimplePresence.Statuses)";
+ QDBusPendingCall call =
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE));
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(call, self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotSimpleStatuses(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectRoster(Connection::Private *self)
+{
+ debug() << "Introspecting roster";
+
+ PendingOperation *op = self->contactManager->introspectRoster();
+ self->parent->connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onIntrospectRosterFinished(Tp::PendingOperation*)));
+}
+
+void Connection::Private::introspectRosterGroups(Connection::Private *self)
+{
+ debug() << "Introspecting roster groups";
+
+ PendingOperation *op = self->contactManager->introspectRosterGroups();
+ self->parent->connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onIntrospectRosterGroupsFinished(Tp::PendingOperation*)));
+}
+
+void Connection::Private::introspectBalance(Connection::Private *self)
+{
+ debug() << "Introspecting balance";
+
+ // we already checked if balance interface exists, so bypass requests
+ // interface checking
+ Client::ConnectionInterfaceBalanceInterface *iface =
+ self->parent->interface<Client::ConnectionInterfaceBalanceInterface>();
+
+ debug() << "Connecting to Balance.BalanceChanged";
+ self->parent->connect(iface,
+ SIGNAL(BalanceChanged(Tp::CurrencyAmount)),
+ SLOT(onBalanceChanged(Tp::CurrencyAmount)));
+
+ debug() << "Retrieving balance";
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ self->properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_BALANCE),
+ QLatin1String("AccountBalance")), self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotBalance(QDBusPendingCallWatcher*)));
+}
+
+void Connection::Private::introspectConnected(Connection::Private *self)
+{
+ Q_ASSERT(!self->introspectingConnected);
+ self->introspectingConnected = true;
+
+ if (self->pendingStatus == ConnectionStatusConnected) {
+ self->readinessHelper->setIntrospectCompleted(FeatureConnected, true);
+ self->introspectingConnected = false;
+ }
+}
+
+void Connection::Private::continueMainIntrospection()
+{
+ if (!parent->isValid()) {
+ debug() << parent << "stopping main introspection, as it has been invalidated";
+ return;
+ }
+
+ if (introspectMainQueue.isEmpty()) {
+ readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ (this->*(introspectMainQueue.dequeue()))();
+ }
+}
+
+void Connection::Private::setCurrentStatus(uint status)
+{
+ // ReadinessHelper waits for all in-flight introspection ops to finish for the current status
+ // before proceeding to a new one.
+ //
+ // Therefore we don't need any safeguarding here to prevent finishing introspection when there
+ // is a pending status change. However, we can speed up the process slightly by canceling any
+ // pending introspect ops from our local introspection queue when it's waiting for us.
+
+ introspectMainQueue.clear();
+
+ if (introspectingConnected) {
+ // On the other hand, we have to finish the Connected introspection for now, as
+ // ReadinessHelper would otherwise wait indefinitely for it to land
+ debug() << "Finishing FeatureConnected for status" << this->status <<
+ "to allow ReadinessHelper to introspect new status" << status;
+ readinessHelper->setIntrospectCompleted(FeatureConnected, true);
+ introspectingConnected = false;
+ }
+
+ readinessHelper->setCurrentStatus(status);
+}
+
+void Connection::Private::forceCurrentStatus(uint status)
+{
+ // only update the status if we did not get it from StatusChanged
+ if (pendingStatus == (uint) -1) {
+ debug() << "Got status:" << status;
+ pendingStatus = status;
+ // No need to re-run introspection as we just received the status. Let
+ // the introspection continue normally but update readinessHelper with
+ // the correct status.
+ readinessHelper->forceCurrentStatus(status);
+ }
+}
+
+void Connection::Private::setInterfaces(const QStringList &interfaces)
+{
+ debug() << "Got interfaces:" << interfaces;
+ parent->setInterfaces(interfaces);
+ readinessHelper->setInterfaces(interfaces);
+}
+
+void Connection::Private::invalidateResetCaps(const QString &error, const QString &message)
+{
+ caps.updateRequestableChannelClasses(RequestableChannelClassList());
+ parent->invalidate(error, message);
+}
+
+/**
+ * \class ConnectionLowlevel
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/connection-lowlevel.h <TelepathyQt4/ConnectionLowlevel>
+ *
+ * \brief The ConnectionLowlevel class extends Connection with support to
+ * low-level features.
+ */
+
+ConnectionLowlevel::ConnectionLowlevel(Connection *conn)
+ : mPriv(new Private(conn))
+{
+}
+
+ConnectionLowlevel::~ConnectionLowlevel()
+{
+ delete mPriv;
+}
+
+bool ConnectionLowlevel::isValid() const
+{
+ return !mPriv->conn.isNull();
+}
+
+ConnectionPtr ConnectionLowlevel::connection() const
+{
+ return ConnectionPtr(mPriv->conn);
+}
+
+Connection::PendingConnect::PendingConnect(const ConnectionPtr &connection,
+ const Features &requestedFeatures)
+ : PendingReady(connection, requestedFeatures)
+{
+ if (!connection) {
+ // Called when the connection had already been destroyed
+ return;
+ }
+
+ QDBusPendingCall call = connection->baseInterface()->Connect();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+
+ connect(connection.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ this,
+ SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this,
+ SLOT(onConnectReply(QDBusPendingCallWatcher*)));
+}
+
+void Connection::PendingConnect::onConnectReply(QDBusPendingCallWatcher *watcher)
+{
+ ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy());
+
+ if (watcher->isError()) {
+ debug() << "Connect failed with" <<
+ watcher->error().name() << ": " << watcher->error().message();
+ setFinishedWithError(watcher->error());
+ connection->disconnect(
+ this,
+ SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString)));
+ } else {
+ if (connection->status() == ConnectionStatusConnected) {
+ onStatusChanged(ConnectionStatusConnected);
+ } else {
+ // Wait for statusChanged()! Connect returning just means that the connection has
+ // started to connect - spec quoted for truth:
+ //
+ // Connect () -> nothing
+ // Request that the connection be established. This will be done asynchronously and
+ // errors will be returned by emitting StatusChanged signals.
+ //
+ // Which should actually say progress and/or errors IMO, but anyway...
+ connect(connection.data(),
+ SIGNAL(statusChanged(Tp::ConnectionStatus)),
+ SLOT(onStatusChanged(Tp::ConnectionStatus)));
+ }
+ }
+
+ watcher->deleteLater();
+}
+
+void Connection::PendingConnect::onStatusChanged(ConnectionStatus newStatus)
+{
+ ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy());
+
+ if (newStatus == ConnectionStatusDisconnected) {
+ debug() << "Connection became disconnected while a PendingConnect was underway";
+ setFinishedWithError(connection->invalidationReason(), connection->invalidationMessage());
+
+ connection->disconnect(this,
+ SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ return;
+ }
+
+ if (newStatus == ConnectionStatusConnected) {
+ // OK, the connection is Connected now - finally, we'll get down to business
+ connect(connection->becomeReady(requestedFeatures()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onBecomeReadyReply(Tp::PendingOperation*)));
+ }
+}
+
+void Connection::PendingConnect::onBecomeReadyReply(Tp::PendingOperation *op)
+{
+ ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy());
+
+ // We don't care about future disconnects even if they happen before we are destroyed
+ // (which happens two mainloop iterations from now)
+ connection->disconnect(this,
+ SLOT(onStatusChanged(Tp::ConnectionStatus)));
+ connection->disconnect(this,
+ SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ if (op->isError()) {
+ debug() << "Connection->becomeReady failed with" <<
+ op->errorName() << ": " << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ } else {
+ debug() << "Connected";
+
+ if (connection->isValid()) {
+ setFinished();
+ } else {
+ debug() << " ... but the Connection was immediately invalidated!";
+ setFinishedWithError(connection->invalidationReason(), connection->invalidationMessage());
+ }
+ }
+}
+
+void Connection::PendingConnect::onConnInvalidated(Tp::DBusProxy *proxy, const QString &error,
+ const QString &message)
+{
+ ConnectionPtr connection = ConnectionPtr::qObjectCast(this->proxy());
+
+ Q_ASSERT(proxy == connection.data());
+
+ if (!isFinished()) {
+ debug() << "Unable to connect. Connection invalidated";
+ setFinishedWithError(error, message);
+ }
+
+ connection->disconnect(this,
+ SLOT(onStatusChanged(Tp::ConnectionStatus)));
+}
+
+QMap<QPair<QString, QString>, Connection::Private::HandleContext*> Connection::Private::handleContexts;
+QMutex Connection::Private::handleContextsLock;
+
+/**
+ * \class Connection
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/connection.h <TelepathyQt4/Connection>
+ *
+ * \brief The Connection class represents a Telepathy connection.
+ *
+ * This models a connection to a single user account on a communication service.
+ *
+ * Contacts, and server-stored lists (such as subscribed contacts,
+ * block lists, or allow lists) on a service are all represented using the
+ * ContactManager object on the connection, which is valid only for the lifetime
+ * of the connection object.
+ *
+ * The remote object accessor functions on this object (status(),
+ * statusReason(), and so on) don't make any D-Bus calls; instead, they return/use
+ * values cached from a previous introspection run. The introspection process
+ * populates their values in the most efficient way possible based on what the
+ * service implements.
+ *
+ * To avoid unnecessary D-Bus traffic, some accessors only return valid
+ * information after specific features have been enabled.
+ * For instance, to retrieve the connection self contact, it is necessary to
+ * enable the feature Connection::FeatureSelfContact.
+ * See the individual methods descriptions for more details.
+ *
+ * Connection features can be enabled by constructing a ConnectionFactory and enabling
+ * the desired features, and passing it to AccountManager, Account or ClientRegistrar
+ * when creating them as appropriate. However, if a particular
+ * feature is only ever used in a specific circumstance, such as an user opening
+ * some settings dialog separate from the general view of the application,
+ * features can be later enabled as needed by calling becomeReady() with the additional
+ * features, and waiting for the resulting PendingOperation to finish.
+ *
+ * As an addition to accessors, signals are emitted to indicate that properties have changed,
+ * for example statusChanged()(), selfContactChanged(), etc.
+ *
+ * \section conn_usage_sec Usage
+ *
+ * \subsection conn_create_sec Creating a connection object
+ *
+ * The easiest way to create connection objects is through Account. One can
+ * just use the Account::connection method to get an account active connection.
+ *
+ * If you already know the object path, you can just call create().
+ * For example:
+ *
+ * \code ConnectionPtr conn = Connection::create(busName, objectPath); \endcode
+ *
+ * A ConnectionPtr object is returned, which will automatically keep
+ * track of object lifetime.
+ *
+ * You can also provide a D-Bus connection as a QDBusConnection:
+ *
+ * \code
+ *
+ * ConnectionPtr conn = Connection::create(QDBusConnection::sessionBus(),
+ * busName, objectPath);
+ *
+ * \endcode
+ *
+ * \subsection conn_ready_sec Making connection ready to use
+ *
+ * A Connection object needs to become ready before usage, meaning that the
+ * introspection process finished and the object accessors can be used.
+ *
+ * To make the object ready, use becomeReady() and wait for the
+ * PendingOperation::finished() signal to be emitted.
+ *
+ * \code
+ *
+ * class MyClass : public QObject
+ * {
+ * QOBJECT
+ *
+ * public:
+ * MyClass(QObject *parent = 0);
+ * ~MyClass() { }
+ *
+ * private Q_SLOTS:
+ * void onConnectionReady(Tp::PendingOperation*);
+ *
+ * private:
+ * ConnectionPtr conn;
+ * };
+ *
+ * MyClass::MyClass(const QString &busName, const QString &objectPath,
+ * QObject *parent)
+ * : QObject(parent)
+ * conn(Connection::create(busName, objectPath))
+ * {
+ * // connect and become ready
+ * connect(conn->requestConnect(),
+ * SIGNAL(finished(Tp::PendingOperation*)),
+ * SLOT(onConnectionReady(Tp::PendingOperation*)));
+ * }
+ *
+ * void MyClass::onConnectionReady(Tp::PendingOperation *op)
+ * {
+ * if (op->isError()) {
+ * qWarning() << "Account cannot become ready:" <<
+ * op->errorName() << "-" << op->errorMessage();
+ * return;
+ * }
+ *
+ * // Connection is now ready
+ * }
+ *
+ * \endcode
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * Connection object usable.
+ *
+ * Note that this feature must be enabled in order to use most Connection
+ * methods.
+ * See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature Connection::FeatureCore = Feature(QLatin1String(Connection::staticMetaObject.className()), 0, true);
+
+/**
+ * Feature used to retrieve the connection self contact.
+ *
+ * See self contact specific methods' documentation for more details.
+ *
+ * \sa selfContact(), selfContactChanged()
+ */
+const Feature Connection::FeatureSelfContact = Feature(QLatin1String(Connection::staticMetaObject.className()), 1);
+
+/**
+ * Feature used to retrieve/keep track of the connection self presence.
+ *
+ * See simple presence specific methods' documentation for more details.
+ */
+const Feature Connection::FeatureSimplePresence = Feature(QLatin1String(Connection::staticMetaObject.className()), 2);
+
+/**
+ * Feature used to enable roster support on Connection::contactManager.
+ *
+ * See ContactManager roster specific methods' documentation for more details.
+ *
+ * \sa ContactManager::allKnownContacts()
+ */
+const Feature Connection::FeatureRoster = Feature(QLatin1String(Connection::staticMetaObject.className()), 4);
+
+/**
+ * Feature used to enable roster groups support on Connection::contactManager.
+ *
+ * See ContactManager roster groups specific methods' documentation for more
+ * details.
+ *
+ * \sa ContactManager::allKnownGroups()
+ */
+const Feature Connection::FeatureRosterGroups = Feature(QLatin1String(Connection::staticMetaObject.className()), 5);
+
+/**
+ * Feature used to retrieve/keep track of the connection account balance.
+ *
+ * See account balance specific methods' documentation for more details.
+ *
+ * \sa accountBalance(), accountBalanceChanged()
+ */
+const Feature Connection::FeatureAccountBalance = Feature(QLatin1String(Connection::staticMetaObject.className()), 6);
+
+/**
+ * When this feature is prepared, it means that the connection status() is
+ * ConnectionStatusConnected.
+ *
+ * Note that if ConnectionFactory is being used with FeatureConnected set, Connection objects will
+ * only be signalled by the library when the corresponding connection is in status()
+ * ConnectionStatusConnected.
+ */
+const Feature Connection::FeatureConnected = Feature(QLatin1String(Connection::staticMetaObject.className()), 7);
+
+/**
+ * Create a new connection object using QDBusConnection::sessionBus().
+ *
+ * A warning is printed if the factories are not for QDBusConnection::sessionBus().
+ *
+ * \param busName The connection well-known bus name (sometimes called a
+ * "service name").
+ * \param objectPath The connection object path.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ConnectionPtr object pointing to the newly created Connection object.
+ */
+ConnectionPtr Connection::create(const QString &busName,
+ const QString &objectPath,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ConnectionPtr(new Connection(QDBusConnection::sessionBus(),
+ busName, objectPath, channelFactory, contactFactory,
+ Connection::FeatureCore));
+}
+
+/**
+ * Create a new connection object using the given \a bus.
+ *
+ * A warning is printed if the factories are not for \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param busName The connection well-known bus name (sometimes called a
+ * "service name").
+ * \param objectPath The connection object path.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \return A ConnectionPtr object pointing to the newly created Connection object.
+ */
+ConnectionPtr Connection::create(const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return ConnectionPtr(new Connection(bus, busName, objectPath, channelFactory, contactFactory,
+ Connection::FeatureCore));
+}
+
+/**
+ * Construct a new connection object using the given \a bus.
+ *
+ * A warning is printed if the factories are not for \a bus.
+ *
+ * \param bus QDBusConnection to use.
+ * \param busName The connection's well-known bus name (sometimes called a
+ * "service name").
+ * \param objectPath The connection object path.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \param coreFeature The core feature of the Connection subclass. The corresponding introspectable
+ * should depend on Connection::FeatureCore.
+ */
+Connection::Connection(const QDBusConnection &bus,
+ const QString &busName,
+ const QString &objectPath,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const Feature &coreFeature)
+ : StatefulDBusProxy(bus, busName, objectPath, coreFeature),
+ OptionalInterfaceFactory<Connection>(this),
+ mPriv(new Private(this, channelFactory, contactFactory))
+{
+}
+
+/**
+ * Class destructor.
+ */
+Connection::~Connection()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the channel factory used by this connection.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the account would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ChannelFactory object.
+ */
+ChannelFactoryConstPtr Connection::channelFactory() const
+{
+ return mPriv->chanFactory;
+}
+
+/**
+ * Return the contact factory used by this connection.
+ *
+ * Only read access is provided. This allows constructing object instances and examining the object
+ * construction settings, but not changing settings. Allowing changes would lead to tricky
+ * situations where objects constructed at different times by the account would have unpredictably
+ * different construction settings (eg. subclass).
+ *
+ * \return A read-only pointer to the ContactFactory object.
+ */
+ContactFactoryConstPtr Connection::contactFactory() const
+{
+ return mPriv->contactFactory;
+}
+
+/**
+ * Return the connection manager name of this connection.
+ *
+ * \return The connection manager name.
+ */
+QString Connection::cmName() const
+{
+ return mPriv->cmName;
+}
+
+/**
+ * Return the protocol name of this connection.
+ *
+ * \return The protocol name.
+ */
+QString Connection::protocolName() const
+{
+ return mPriv->protocolName;
+}
+
+/**
+ * Return the status of this connection.
+ *
+ * Change notification is via the statusChanged() signal.
+ *
+ * This method requires Connection::FeatureCore to be ready.
+ *
+ * \return The status as #ConnectionStatus.
+ * \sa statusChanged(), statusReason(), errorDetails()
+ */
+ConnectionStatus Connection::status() const
+{
+ return (ConnectionStatus) mPriv->status;
+}
+
+/**
+ * Return the reason for this connection status.
+ *
+ * The validity and change rules are the same as for status().
+ *
+ * The status reason should be only used as a fallback in error handling when the application
+ * doesn't understand an error name given as the invalidation reason, which may in some cases be
+ * domain/UI-specific.
+ *
+ * This method requires Connection::FeatureCore to be ready.
+ *
+ * \return The status reason as #ConnectionStatusReason.
+ * \sa invalidated(), invalidationReason()
+ */
+ConnectionStatusReason Connection::statusReason() const
+{
+ return (ConnectionStatusReason) mPriv->statusReason;
+}
+
+struct TELEPATHY_QT4_NO_EXPORT Connection::ErrorDetails::Private : public QSharedData
+{
+ Private(const QVariantMap &details)
+ : details(details) {}
+
+ QVariantMap details;
+};
+
+/**
+ * \class Connection::ErrorDetails
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/connection.h <TelepathyQt4/Connection>
+ *
+ * \brief The Connection::ErrorDetails class represents the details of a connection error.
+ *
+ * It contains detailed information about the reason for the connection going invalidated().
+ *
+ * Some services may provide additional error information in the ConnectionError D-Bus signal, when
+ * a Connection is disconnected / has become unusable. If the service didn't provide any, or has not
+ * been invalidated yet, the instance will be invalid, as returned by isValid().
+ *
+ * The information provided by invalidationReason() and this class should always be used in error
+ * handling in preference to statusReason(). The status reason can be used as a fallback, however,
+ * if the client doesn't understand what a particular value returned by invalidationReason() means,
+ * as it may be domain-specific with some services.
+ *
+ * Connection::errorDetails() can be used to return the instance containing the details for
+ * invalidating that connection after invalidated() has been emitted.
+ */
+
+/**
+ * Constructs a new invalid ErrorDetails instance.
+ */
+Connection::ErrorDetails::ErrorDetails()
+ : mPriv(0)
+{
+}
+
+/**
+ * Construct a error details instance with the given details. The instance will indicate that
+ * it is valid.
+ */
+Connection::ErrorDetails::ErrorDetails(const QVariantMap &details)
+ : mPriv(new Private(details))
+{
+}
+
+/**
+ * Copy constructor.
+ */
+Connection::ErrorDetails::ErrorDetails(const ErrorDetails &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+Connection::ErrorDetails::~ErrorDetails()
+{
+}
+
+/**
+ * Assigns all information (validity, details) from other to this.
+ */
+Connection::ErrorDetails &Connection::ErrorDetails::operator=(
+ const ErrorDetails &other)
+{
+ if (this->mPriv.constData() != other.mPriv.constData())
+ this->mPriv = other.mPriv;
+
+ return *this;
+}
+
+/**
+ * \fn bool Connection::ErrorDetails::isValid() const
+ *
+ * Return whether or not the details are valid (have actually been received from the service).
+ *
+ * \return \c true if valid, \c false otherwise.
+ */
+
+/**
+ * \fn bool Connection::ErrorDetails::hasDebugMessage() const
+ *
+ * Return whether or not the details specify a debug message.
+ *
+ * If present, the debug message will likely be the same string as the one returned by
+ * invalidationMessage().
+ *
+ * The debug message is purely informational, offered for display for bug reporting purposes, and
+ * should not be attempted to be parsed.
+ *
+ * \return \c true if debug message is present, \c false otherwise.
+ * \sa debugMessage()
+ */
+
+/**
+ * \fn QString Connection::ErrorDetails::debugMessage() const
+ *
+ * Return the debug message specified by the details, if any.
+ *
+ * If present, the debug message will likely be the same string as the one returned by
+ * invalidationMessage().
+ *
+ * The debug message is purely informational, offered for display for bug reporting purposes, and
+ * should not be attempted to be parsed.
+ *
+ * \return The debug message, or an empty string if there is none.
+ * \sa hasDebugMessage()
+ */
+
+/**
+ * Return a map containing all details given in the low-level ConnectionError signal.
+ *
+ * This is useful for accessing domain-specific additional details.
+ *
+ * \return The details of the connection error as QVariantMap.
+ */
+QVariantMap Connection::ErrorDetails::allDetails() const
+{
+ return isValid() ? mPriv->details : QVariantMap();
+}
+
+/**
+ * Return detailed information about the reason for the connection going invalidated().
+ *
+ * Some services may provide additional error information in the ConnectionError D-Bus signal, when
+ * a Connection is disconnected / has become unusable. If the service didn't provide any, or has not
+ * been invalidated yet, an invalid instance is returned.
+ *
+ * The information provided by invalidationReason() and this method should always be used in error
+ * handling in preference to statusReason(). The status reason can be used as a fallback, however,
+ * if the client doesn't understand what a particular value returned by invalidationReason() means,
+ * as it may be domain-specific with some services.
+ *
+ * \return The error details as a Connection::ErrorDetails object.
+ * \sa status(), statusReason(), invalidationReason()
+ */
+const Connection::ErrorDetails &Connection::errorDetails() const
+{
+ if (isValid()) {
+ warning() << "Connection::errorDetails() used on" << objectPath() << "which is valid";
+ }
+
+ return mPriv->errorDetails;
+}
+
+/**
+ * Return the handle representing the user on this connection.
+ *
+ * Note that if the connection is not yet in the ConnectionStatusConnected state,
+ * the value of this property may be zero.
+ *
+ * Change notification is via the selfHandleChanged() signal.
+ *
+ * This method requires Connection::FeatureCore to be ready.
+ *
+ * \return The user handle.
+ * \sa selfHandleChanged(), selfContact()
+ */
+uint Connection::selfHandle() const
+{
+ return mPriv->selfHandle;
+}
+
+/**
+ * Return a dictionary of presence statuses valid for use in this connection.
+ *
+ * The value may have changed arbitrarily during the time the
+ * Connection spends in status ConnectionStatusConnecting,
+ * again staying fixed for the entire time in ConnectionStatusConnected.
+ *
+ * This method requires Connection::FeatureSimplePresence to be ready.
+ *
+ * \return The allowed statuses as a map from string identifiers to SimpleStatusSpec objects.
+ */
+SimpleStatusSpecMap ConnectionLowlevel::allowedPresenceStatuses() const
+{
+ if (!isValid()) {
+ warning() << "ConnectionLowlevel::selfHandle() "
+ "called for a connection which is already destroyed";
+ return SimpleStatusSpecMap();
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ if (!conn->isReady(Connection::FeatureSimplePresence)) {
+ warning() << "Trying to retrieve allowed presence statuses from connection, but "
+ "simple presence is not supported or was not requested. "
+ "Enable FeatureSimplePresence in this connection";
+ }
+
+ return conn->mPriv->simplePresenceStatuses;
+}
+
+/**
+ * Return the maximum length for a presence status message.
+ *
+ * The value may have changed arbitrarily during the time the
+ * Connection spends in status ConnectionStatusConnecting,
+ * again staying fixed for the entire time in ConnectionStatusConnected.
+ *
+ * This method requires Connection::FeatureSimplePresence to be ready.
+ *
+ * \return The maximum length, or 0 if there is no limit.
+ */
+uint ConnectionLowlevel::maxPresenceStatusMessageLength() const
+{
+ if (!isValid()) {
+ warning() << "ConnectionLowlevel::maxPresenceStatusMessageLength() "
+ "called for a connection which is already destroyed";
+ return 0;
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ if (!conn->isReady(Connection::FeatureSimplePresence)) {
+ warning() << "Trying to retrieve max presence status message length connection, but "
+ "simple presence is not supported or was not requested. "
+ "Enable FeatureSimplePresence in this connection";
+ }
+
+ return conn->mPriv->maxPresenceStatusMessageLength;
+}
+
+/**
+ * Set the self presence status.
+ *
+ * This should generally only be called by an Account Manager. In typical usage,
+ * Account::setRequestedPresence() should be used instead.
+ *
+ * \a status must be one of the allowed statuses returned by
+ * allowedPresenceStatuses().
+ *
+ * Note that clients SHOULD set the status message for the local user to the
+ * empty string, unless the user has actually provided a specific message (i.e.
+ * one that conveys more information than the ConnectionStatus).
+ *
+ * \param status The desired status.
+ * \param statusMessage The desired status message.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa allowedPresenceStatuses()
+ */
+PendingOperation *ConnectionLowlevel::setSelfPresence(const QString &status,
+ const QString &statusMessage)
+{
+ if (!isValid()) {
+ warning() << "ConnectionLowlevel::selfHandle() called for a connection which is already destroyed";
+ return new PendingFailure(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("Connection already destroyed"),
+ ConnectionPtr());
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE))) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Connection does not support SimplePresence"), conn);
+ }
+
+ Client::ConnectionInterfaceSimplePresenceInterface *simplePresenceInterface =
+ conn->interface<Client::ConnectionInterfaceSimplePresenceInterface>();
+ return new PendingVoid(
+ simplePresenceInterface->SetPresence(status, statusMessage), conn);
+}
+
+/**
+ * Return the object representing the user on this connection.
+ *
+ * Note that if the connection is not yet in the ConnectionStatusConnected state, the value of this
+ * property may be null.
+ *
+ * Change notification is via the selfContactChanged() signal.
+ *
+ * This method requires Connection::FeatureSelfContact to be ready.
+ *
+ * \return A pointer to the Contact object, or a null ContactPtr if unknown.
+ * \sa selfContactChanged(), selfHandle()
+ */
+ContactPtr Connection::selfContact() const
+{
+ if (!isReady(FeatureSelfContact)) {
+ warning() << "Connection::selfContact() used, but becomeReady(FeatureSelfContact) "
+ "hasn't been completed!";
+ }
+
+ return mPriv->selfContact;
+}
+
+/**
+ * Return the user's balance on the account corresponding to this connection.
+ *
+ * A negative amount may be possible on some services, and indicates that the user
+ * owes money to the service provider.
+ *
+ * Change notification is via the accountBalanceChanged() signal.
+ *
+ * This method requires Connection::FeatureAccountBalance to be ready.
+ *
+ * \return The account balance as #CurrencyAmount.
+ * \sa accountBalanceChanged()
+ */
+CurrencyAmount Connection::accountBalance() const
+{
+ if (!isReady(FeatureAccountBalance)) {
+ warning() << "Connection::accountBalance() used before connection "
+ "FeatureAccountBalance is ready";
+ }
+
+ return mPriv->accountBalance;
+}
+
+/**
+ * Return the capabilities for this connection.
+ *
+ * User interfaces can use this information to show or hide UI components.
+ *
+ * This property cannot change after the connection has gone to state
+ * ConnectionStatusConnected, so there is no change notification.
+ *
+ * This method requires Connection::FeatureCore to be ready.
+ *
+ * @return The capabilities of this connection.
+ */
+ConnectionCapabilities Connection::capabilities() const
+{
+ if (!isReady(Connection::FeatureCore)) {
+ warning() << "Connection::capabilities() used before connection "
+ "FeatureCore is ready";
+ }
+
+ return mPriv->caps;
+}
+
+void Connection::onStatusReady(uint status)
+{
+ Q_ASSERT(status == mPriv->pendingStatus);
+
+ if (mPriv->status == status) {
+ return;
+ }
+
+ mPriv->status = status;
+ mPriv->statusReason = mPriv->pendingStatusReason;
+
+ if (isValid()) {
+ emit statusChanged((ConnectionStatus) mPriv->status);
+ } else {
+ debug() << this << " not emitting statusChanged because it has been invalidated";
+ }
+}
+
+void Connection::onStatusChanged(uint status, uint reason)
+{
+ debug() << "StatusChanged from" << mPriv->pendingStatus
+ << "to" << status << "with reason" << reason;
+
+ if (mPriv->pendingStatus == status) {
+ warning() << "New status was the same as the old status! Ignoring"
+ "redundant StatusChanged";
+ return;
+ }
+
+ uint oldStatus = mPriv->pendingStatus;
+ mPriv->pendingStatus = status;
+ mPriv->pendingStatusReason = reason;
+
+ switch (status) {
+ case ConnectionStatusConnected:
+ debug() << "Performing introspection for the Connected status";
+ mPriv->setCurrentStatus(status);
+ break;
+
+ case ConnectionStatusConnecting:
+ mPriv->setCurrentStatus(status);
+ break;
+
+ case ConnectionStatusDisconnected:
+ {
+ QString errorName = ConnectionHelper::statusReasonToErrorName(
+ (ConnectionStatusReason) reason,
+ (ConnectionStatus) oldStatus);
+
+ // TODO should we signal statusChanged to Disconnected here or just
+ // invalidate?
+ // Also none of the pendingOperations will finish. The
+ // user should just consider them to fail as the connection
+ // is invalid
+ onStatusReady(ConnectionStatusDisconnected);
+ mPriv->invalidateResetCaps(errorName,
+ QString(QLatin1String("ConnectionStatusReason = %1")).arg(uint(reason)));
+ }
+ break;
+
+ default:
+ warning() << "Unknown connection status" << status;
+ break;
+ }
+}
+
+void Connection::onConnectionError(const QString &error,
+ const QVariantMap &details)
+{
+ debug().nospace() << "Connection(" << objectPath() << ") got ConnectionError(" << error
+ << ") with " << details.size() << " details";
+
+ mPriv->errorDetails = details;
+ mPriv->invalidateResetCaps(error,
+ details.value(QLatin1String("debug-message")).toString());
+}
+
+void Connection::gotMainProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ QVariantMap props;
+
+ if (!reply.isError()) {
+ props = reply.value();
+ } else {
+ warning().nospace() << "Properties::GetAll(Connection) failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ // let's try to fallback first before failing
+ }
+
+ uint status = static_cast<uint>(-1);
+ if (props.contains(QLatin1String("Status"))
+ && ((status = qdbus_cast<uint>(props[QLatin1String("Status")])) <=
+ ConnectionStatusDisconnected)) {
+ mPriv->forceCurrentStatus(status);
+ } else {
+ // only introspect status if we did not got it from StatusChanged
+ if (mPriv->pendingStatus == (uint) -1) {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectMainFallbackStatus);
+ }
+ }
+
+ if (props.contains(QLatin1String("Interfaces"))) {
+ mPriv->setInterfaces(qdbus_cast<QStringList>(
+ props[QLatin1String("Interfaces")]));
+ } else {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectMainFallbackInterfaces);
+ }
+
+ if (props.contains(QLatin1String("SelfHandle"))) {
+ mPriv->selfHandle = qdbus_cast<uint>(
+ props[QLatin1String("SelfHandle")]);
+ } else {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectMainFallbackSelfHandle);
+ }
+
+ if (props.contains(QLatin1String("HasImmortalHandles"))) {
+ mPriv->immortalHandles = qdbus_cast<bool>(props[QLatin1String("HasImmortalHandles")]);
+ }
+
+ if (hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_REQUESTS)) {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectCapabilities);
+ }
+
+ if (hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACTS)) {
+ mPriv->introspectMainQueue.enqueue(
+ &Private::introspectContactAttributeInterfaces);
+ }
+
+ mPriv->continueMainIntrospection();
+
+ watcher->deleteLater();
+}
+
+void Connection::gotStatus(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<uint> reply = *watcher;
+
+ if (!reply.isError()) {
+ mPriv->forceCurrentStatus(reply.value());
+
+ mPriv->continueMainIntrospection();
+ } else {
+ warning().nospace() << "GetStatus() failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ mPriv->invalidateResetCaps(reply.error().name(), reply.error().message());
+ }
+
+ watcher->deleteLater();
+}
+
+void Connection::gotInterfaces(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QStringList> reply = *watcher;
+
+ if (!reply.isError()) {
+ mPriv->setInterfaces(reply.value());
+ }
+ else {
+ warning().nospace() << "GetInterfaces() failed with " <<
+ reply.error().name() << ": " << reply.error().message() <<
+ " - assuming no new interfaces";
+ // let's not fail if GetInterfaces fail
+ }
+
+ mPriv->continueMainIntrospection();
+
+ watcher->deleteLater();
+}
+
+void Connection::gotSelfHandle(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<uint> reply = *watcher;
+
+ if (!reply.isError()) {
+ mPriv->selfHandle = reply.value();
+ debug() << "Got self handle:" << mPriv->selfHandle;
+
+ mPriv->continueMainIntrospection();
+ } else {
+ warning().nospace() << "GetSelfHandle() failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore,
+ false, reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void Connection::gotCapabilities(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QDBusVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got capabilities";
+ mPriv->caps.updateRequestableChannelClasses(
+ qdbus_cast<RequestableChannelClassList>(reply.value().variant()));
+ } else {
+ warning().nospace() << "Getting capabilities failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ // let's not fail if retrieving capabilities fail
+ }
+
+ mPriv->continueMainIntrospection();
+
+ watcher->deleteLater();
+}
+
+void Connection::gotContactAttributeInterfaces(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QDBusVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got contact attribute interfaces";
+ mPriv->contactAttributeInterfaces = qdbus_cast<QStringList>(reply.value().variant());
+ } else {
+ warning().nospace() << "Getting contact attribute interfaces failed with " <<
+ reply.error().name() << ": " << reply.error().message();
+ // let's not fail if retrieving contact attribute interfaces fail
+ // TODO should we remove Contacts interface from interfaces?
+ }
+
+ mPriv->continueMainIntrospection();
+
+ watcher->deleteLater();
+}
+
+void Connection::gotSimpleStatuses(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ QVariantMap props = reply.value();
+
+ mPriv->simplePresenceStatuses = qdbus_cast<SimpleStatusSpecMap>(
+ props[QLatin1String("Statuses")]);
+ mPriv->maxPresenceStatusMessageLength = qdbus_cast<uint>(
+ props[QLatin1String("MaximumStatusMessageLength")]);
+
+ debug() << "Got" << mPriv->simplePresenceStatuses.size() <<
+ "simple presence statuses - max status message length is" <<
+ mPriv->maxPresenceStatusMessageLength;
+
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureSimplePresence, true);
+ }
+ else {
+ warning().nospace() << "Getting simple presence statuses failed with " <<
+ reply.error().name() << ":" << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureSimplePresence, false, reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void Connection::gotSelfContact(PendingOperation *op)
+{
+ PendingContacts *pending = qobject_cast<PendingContacts *>(op);
+
+ if (pending->isValid()) {
+ Q_ASSERT(pending->contacts().size() == 1);
+ ContactPtr contact = pending->contacts()[0];
+
+ if (mPriv->selfContact != contact) {
+ mPriv->selfContact = contact;
+
+ if (!isReady(FeatureSelfContact)) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureSelfContact, true);
+ }
+
+ emit selfContactChanged();
+ }
+ } else {
+ warning().nospace() << "Getting self contact failed with " <<
+ pending->errorName() << ":" << pending->errorMessage();
+
+ // check if the feature is already there, and for some reason introspectSelfContact
+ // failed when called the second time
+ if (!isReady(FeatureSelfContact)) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureSelfContact, false,
+ op->errorName(), op->errorMessage());
+ }
+
+ if (mPriv->selfContact) {
+ mPriv->selfContact.reset();
+ emit selfContactChanged();
+ }
+ }
+
+ mPriv->introspectingSelfContact = false;
+
+ if (mPriv->reintrospectSelfContactRequired) {
+ mPriv->introspectSelfContact(mPriv);
+ }
+}
+
+void Connection::onIntrospectRosterFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning().nospace() << "Introspecting roster failed with " <<
+ op->errorName() << ": " << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, false,
+ op->errorName(), op->errorMessage());
+ return;
+ }
+
+ debug() << "Introspecting roster finished";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, true);
+}
+
+void Connection::onIntrospectRosterGroupsFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning().nospace() << "Introspecting roster groups failed with " <<
+ op->errorName() << ": " << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, false,
+ op->errorName(), op->errorMessage());
+ return;
+ }
+
+ debug() << "Introspecting roster groups finished";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, true);
+}
+
+void Connection::gotBalance(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got balance";
+ mPriv->accountBalance = qdbus_cast<CurrencyAmount>(reply.value());
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureAccountBalance, true);
+ } else {
+ warning().nospace() << "Getting balance failed with " <<
+ reply.error().name() << ":" << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureAccountBalance, false,
+ reply.error().name(), reply.error().message());
+ }
+
+ watcher->deleteLater();
+}
+
+/**
+ * Return the Client::ConnectionInterface interface proxy object for this connection.
+ * This method is protected since the convenience methods provided by this
+ * class should generally be used instead of calling D-Bus methods
+ * directly.
+ *
+ * \return A pointer to the existing Client::ConnectionInterface object for this
+ * Connection object.
+ */
+Client::ConnectionInterface *Connection::baseInterface() const
+{
+ return mPriv->baseInterface;
+}
+
+/**
+ * Same as \c createChannel(request, -1)
+ */
+PendingChannel *ConnectionLowlevel::createChannel(const QVariantMap &request)
+{
+ return createChannel(request, -1);
+}
+
+/**
+ * Asynchronously creates a channel satisfying the given request.
+ *
+ * In typical usage, only the Channel Dispatcher should call this. Ordinary
+ * applications should use the Account::createChannel() family of methods
+ * (which invoke the Channel Dispatcher's services).
+ *
+ * The request MUST contain the following keys:
+ * org.freedesktop.Telepathy.Channel.ChannelType
+ * org.freedesktop.Telepathy.Channel.TargetHandleType
+ *
+ * Upon completion, the reply to the request can be retrieved through the
+ * returned PendingChannel object. The object also provides access to the
+ * parameters with which the call was made and a signal to connect to get
+ * notification of the request finishing processing. See the documentation
+ * for that class for more info.
+ *
+ * \param request A dictionary containing the desirable properties.
+ * \param timeout The D-Bus timeout in milliseconds used for the method call.
+ * If timeout is -1, a default implementation-defined value that
+ * is suitable for inter-process communications (generally,
+ * 25 seconds) will be used.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * when the channel has been created, or an error occurred.
+ * \sa PendingChannel, ensureChannel(),
+ * Account::createChannel(), Account::createAndHandleChannel(),
+ * Account::ensureChannel(), Account::ensureAndHandleChannel()
+ */
+PendingChannel *ConnectionLowlevel::createChannel(const QVariantMap &request,
+ int timeout)
+{
+ if (!isValid()) {
+ return new PendingChannel(ConnectionPtr(),
+ TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"));
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ if (conn->mPriv->pendingStatus != ConnectionStatusConnected) {
+ warning() << "Calling createChannel with connection not yet connected";
+ return new PendingChannel(conn,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection not yet connected"));
+ }
+
+ if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS))) {
+ warning() << "Requests interface is not support by this connection";
+ return new PendingChannel(conn,
+ QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Connection does not support Requests Interface"));
+ }
+
+ if (!request.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) {
+ return new PendingChannel(conn,
+ QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid 'request' argument"));
+ }
+
+ debug() << "Creating a Channel";
+ PendingChannel *channel = new PendingChannel(conn, request, true, timeout);
+ return channel;
+}
+
+/**
+ * Same as \c ensureChannel(request, -1)
+ */
+PendingChannel *ConnectionLowlevel::ensureChannel(const QVariantMap &request)
+{
+ return ensureChannel(request, -1);
+}
+
+/**
+ * Asynchronously ensures a channel exists satisfying the given request.
+ *
+ * In typical usage, only the Channel Dispatcher should call this. Ordinary
+ * applications should use the Account::ensureChannel() family of methods
+ * (which invoke the Channel Dispatcher's services).
+ *
+ * The request MUST contain the following keys:
+ * org.freedesktop.Telepathy.Channel.ChannelType
+ * org.freedesktop.Telepathy.Channel.TargetHandleType
+ *
+ * Upon completion, the reply to the request can be retrieved through the
+ * returned PendingChannel object. The object also provides access to the
+ * parameters with which the call was made and a signal to connect to get
+ * notification of the request finishing processing. See the documentation
+ * for that class for more info.
+ *
+ * \param request A dictionary containing the desirable properties.
+ * \param timeout The D-Bus timeout in milliseconds used for the method call.
+ * If timeout is -1, a default implementation-defined value that
+ * is suitable for inter-process communications (generally,
+ * 25 seconds) will be used.
+ * \return A PendingChannel which will emit PendingChannel::finished
+ * when the channel is ensured to exist, or an error occurred.
+ * \sa PendingChannel, createChannel(),
+ * Account::createChannel(), Account::createAndHandleChannel(),
+ * Account::ensureChannel(), Account::ensureAndHandleChannel()
+ */
+PendingChannel *ConnectionLowlevel::ensureChannel(const QVariantMap &request,
+ int timeout)
+{
+ if (!isValid()) {
+ return new PendingChannel(ConnectionPtr(),
+ TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"));
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ if (conn->mPriv->pendingStatus != ConnectionStatusConnected) {
+ warning() << "Calling ensureChannel with connection not yet connected";
+ return new PendingChannel(conn,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection not yet connected"));
+ }
+
+ if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS))) {
+ warning() << "Requests interface is not support by this connection";
+ return new PendingChannel(conn,
+ QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Connection does not support Requests Interface"));
+ }
+
+ if (!request.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) {
+ return new PendingChannel(conn,
+ QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid 'request' argument"));
+ }
+
+ debug() << "Creating a Channel";
+ PendingChannel *channel = new PendingChannel(conn, request, false, timeout);
+ return channel;
+}
+
+/**
+ * Request handles of the given type for the given entities (contacts,
+ * rooms, lists, etc.).
+ *
+ * Typically one doesn't need to request and use handles directly; instead, string identifiers
+ * and/or Contact objects are used in most APIs. File a bug for APIs in which there is no
+ * alternative to using handles. In particular however using low-level DBus interfaces for which
+ * there is no corresponding high-level (or one is implementing that abstraction) functionality does
+ * and will always require using bare handles.
+ *
+ * Upon completion, the reply to the request can be retrieved through the
+ * returned PendingHandles object. The object also provides access to the
+ * parameters with which the call was made and a signal to connect to to get
+ * notification of the request finishing processing. See the documentation
+ * for that class for more info.
+ *
+ * The returned PendingHandles object should be freed using
+ * its QObject::deleteLater() method after it is no longer used. However,
+ * all PendingHandles objects resulting from requests to a particular
+ * Connection will be freed when the Connection itself is freed. Conversely,
+ * this means that the PendingHandles object should not be used after the
+ * Connection is destroyed.
+ *
+ * \param handleType Type for the handles to request, as specified in
+ * #HandleType.
+ * \param names Names of the entities to request handles for.
+ * \return A PendingHandles which will emit PendingHandles::finished
+ * when the handles have been requested, or an error occurred.
+ * \sa PendingHandles
+ */
+PendingHandles *ConnectionLowlevel::requestHandles(HandleType handleType, const QStringList &names)
+{
+ debug() << "Request for" << names.length() << "handles of type" << handleType;
+
+ if (!isValid()) {
+ return new PendingHandles(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"));
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+ if (!hasImmortalHandles()) {
+ Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+ handleContext->types[handleType].requestsInFlight++;
+ }
+
+ PendingHandles *pending =
+ new PendingHandles(conn, handleType, names);
+ return pending;
+}
+
+/**
+ * Request a reference to the given handles. Handles not explicitly
+ * requested (via requestHandles()) but eg. observed in a signal need to be
+ * referenced to guarantee them staying valid.
+ *
+ * Typically one doesn't need to reference and use handles directly; instead, string identifiers
+ * and/or Contact objects are used in most APIs. File a bug for APIs in which there is no
+ * alternative to using handles. In particular however using low-level DBus interfaces for which
+ * there is no corresponding high-level (or one is implementing that abstraction) functionality does
+ * and will always require using bare handles.
+ *
+ * Upon completion, the reply to the operation can be retrieved through the
+ * returned PendingHandles object. The object also provides access to the
+ * parameters with which the call was made and a signal to connect to to get
+ * notification of the request finishing processing. See the documentation
+ * for that class for more info.
+ *
+ * The returned PendingHandles object should be freed using
+ * its QObject::deleteLater() method after it is no longer used. However,
+ * all PendingHandles objects resulting from requests to a particular
+ * Connection will be freed when the Connection itself is freed. Conversely,
+ * this means that the PendingHandles object should not be used after the
+ * Connection is destroyed.
+ *
+ * \sa PendingHandles
+ *
+ * \param handleType Type of the handles given, as specified in #HandleType.
+ * \param handles Handles to request a reference to.
+ * \return A PendingHandles which will emit PendingHandles::finished
+ * when the handles have been referenced, or an error occurred.
+ */
+PendingHandles *ConnectionLowlevel::referenceHandles(HandleType handleType, const UIntList &handles)
+{
+ debug() << "Reference of" << handles.length() << "handles of type" << handleType;
+
+ if (!isValid()) {
+ return new PendingHandles(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"));
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+ UIntList alreadyHeld;
+ UIntList notYetHeld;
+ if (!hasImmortalHandles()) {
+ Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+
+ foreach (uint handle, handles) {
+ if (handleContext->types[handleType].refcounts.contains(handle) ||
+ handleContext->types[handleType].toRelease.contains(handle)) {
+ alreadyHeld.push_back(handle);
+ }
+ else {
+ notYetHeld.push_back(handle);
+ }
+ }
+
+ debug() << " Already holding" << alreadyHeld.size() <<
+ "of the handles -" << notYetHeld.size() << "to go";
+ } else {
+ alreadyHeld = handles;
+ }
+
+ PendingHandles *pending =
+ new PendingHandles(conn, handleType, handles, alreadyHeld, notYetHeld);
+ return pending;
+}
+
+/**
+ * Start an asynchronous request that the connection be connected.
+ *
+ * When using a full-fledged Telepathy setup with an Account Manager service, the Account methods
+ * Account::setRequestedPresence() and Account::reconnect() must be used instead.
+ *
+ * The returned PendingOperation will finish successfully when the connection
+ * has reached ConnectionStatusConnected and the requested \a features are all ready, or
+ * finish with an error if a fatal error occurs during that process.
+ *
+ * \param requestedFeatures The features which should be enabled
+ * \return A PendingReady which will emit PendingReady::finished
+ * when the Connection has reached #ConnectionStatusConnected, and initial setup
+ * for basic functionality, plus the given features, has succeeded or
+ * failed.
+ */
+PendingReady *ConnectionLowlevel::requestConnect(const Features &requestedFeatures)
+{
+ if (!isValid()) {
+ Connection::PendingConnect *pending = new Connection::PendingConnect(ConnectionPtr(),
+ requestedFeatures);
+ pending->setFinishedWithError(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"));
+ return pending;
+ }
+
+ return new Connection::PendingConnect(ConnectionPtr(mPriv->conn), requestedFeatures);
+}
+
+/**
+ * Start an asynchronous request that the connection be disconnected.
+ * The returned PendingOperation object will signal the success or failure
+ * of this request; under normal circumstances, it can be expected to
+ * succeed.
+ *
+ * When using a full-fledged Telepathy setup with an Account Manager service,
+ * Account::setRequestedPresence() with Presence::offline() as an argument should generally be used
+ * instead.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ */
+PendingOperation *ConnectionLowlevel::requestDisconnect()
+{
+ if (!isValid()) {
+ return new PendingFailure(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"), ConnectionPtr());
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ return new PendingVoid(conn->baseInterface()->Disconnect(), conn);
+}
+
+/**
+ * Requests attributes for contacts. Optionally, the handles of the contacts
+ * will be referenced automatically. Essentially, this method wraps
+ * ConnectionInterfaceContactsInterface::GetContactAttributes(), integrating it
+ * with the rest of the handle-referencing machinery.
+ *
+ * This is very low-level API the Contact/ContactManager API provides a higher level of abstraction
+ * for the same functionality.
+ *
+ * Upon completion, the reply to the request can be retrieved through the
+ * returned PendingContactAttributes object. The object also provides access to
+ * the parameters with which the call was made and a signal to connect to to get
+ * notification of the request finishing processing. See the documentation for
+ * that class for more info.
+ *
+ * If the remote object doesn't support the Contacts interface (as signified by
+ * the list returned by interfaces() not containing
+ * #TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACTS), the returned
+ * PendingContactAttributes instance will fail instantly with the error
+ * #TP_QT4_ERROR_NOT_IMPLEMENTED.
+ *
+ * Similarly, if the connection isn't both connected and ready
+ * (<code>status() == ConnectionStatusConnected && isReady(Connection::FeatureCore)</code>),
+ * the returned PendingContactAttributes instance will fail instantly with the
+ * error #TP_QT4_ERROR_NOT_AVAILABLE.
+ *
+ * This method requires Connection::FeatureCore to be ready.
+ *
+ * \sa PendingContactAttributes
+ *
+ * \param handles A list of handles of type HandleTypeContact
+ * \param interfaces D-Bus interfaces for which the client requires information
+ * \param reference Whether the handles should additionally be referenced.
+ * \return A PendingContactAttributes which will emit PendingContactAttributes::fininshed
+ * when the contact attributes have been retrieved, or an error occurred.
+ */
+PendingContactAttributes *ConnectionLowlevel::contactAttributes(const UIntList &handles,
+ const QStringList &interfaces, bool reference)
+{
+ debug() << "Request for attributes for" << handles.size() << "contacts";
+
+ if (!isValid()) {
+ PendingContactAttributes *pending = new PendingContactAttributes(ConnectionPtr(),
+ handles, interfaces, reference);
+ pending->failImmediately(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("The connection has been destroyed"));
+ return pending;
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ PendingContactAttributes *pending =
+ new PendingContactAttributes(conn,
+ handles, interfaces, reference);
+ if (!conn->isReady(Connection::FeatureCore)) {
+ warning() << "ConnectionLowlevel::contactAttributes() used when not ready";
+ pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("The connection isn't ready"));
+ return pending;
+ } else if (conn->mPriv->pendingStatus != ConnectionStatusConnected) {
+ warning() << "ConnectionLowlevel::contactAttributes() used with status" << conn->status() << "!= ConnectionStatusConnected";
+ pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("The connection isn't Connected"));
+ return pending;
+ } else if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) {
+ warning() << "ConnectionLowlevel::contactAttributes() used without the remote object supporting"
+ << "the Contacts interface";
+ pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The connection doesn't support the Contacts interface"));
+ return pending;
+ }
+
+ if (!hasImmortalHandles()) {
+ Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+ handleContext->types[HandleTypeContact].requestsInFlight++;
+ }
+
+ Client::ConnectionInterfaceContactsInterface *contactsInterface =
+ conn->interface<Client::ConnectionInterfaceContactsInterface>();
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(contactsInterface->GetContactAttributes(handles, interfaces,
+ reference));
+ pending->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onCallFinished(QDBusPendingCallWatcher*)));
+ return pending;
+}
+
+QStringList ConnectionLowlevel::contactAttributeInterfaces() const
+{
+ if (!isValid()) {
+ warning() << "ConnectionLowlevel::contactAttributeInterfaces() called for a destroyed Connection";
+ return QStringList();
+ }
+
+ ConnectionPtr conn(mPriv->conn);
+
+ if (conn->mPriv->pendingStatus != ConnectionStatusConnected) {
+ warning() << "ConnectionLowlevel::contactAttributeInterfaces() used with status"
+ << conn->status() << "!= ConnectionStatusConnected";
+ } else if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) {
+ warning() << "ConnectionLowlevel::contactAttributeInterfaces() used without the remote object supporting"
+ << "the Contacts interface";
+ }
+
+ return conn->mPriv->contactAttributeInterfaces;
+}
+
+void ConnectionLowlevel::injectContactIds(const HandleIdentifierMap &contactIds)
+{
+ if (!hasImmortalHandles()) {
+ return;
+ }
+
+ for (HandleIdentifierMap::const_iterator i = contactIds.constBegin();
+ i != contactIds.constEnd(); ++i) {
+ uint handle = i.key();
+ QString id = i.value();
+
+ if (!id.isEmpty()) {
+ QString currentId = mPriv->contactsIds.value(handle);
+
+ if (!currentId.isEmpty() && id != currentId) {
+ warning() << "Trying to overwrite contact id from" << currentId << "to" << id
+ << "for the same handle" << handle << ", ignoring";
+ } else {
+ mPriv->contactsIds.insert(handle, id);
+ }
+ }
+ }
+}
+
+void ConnectionLowlevel::injectContactId(uint handle, const QString &contactId)
+{
+ HandleIdentifierMap contactIds;
+ contactIds.insert(handle, contactId);
+ injectContactIds(contactIds);
+}
+
+bool ConnectionLowlevel::hasContactId(uint handle) const
+{
+ return mPriv->contactsIds.contains(handle);
+}
+
+QString ConnectionLowlevel::contactId(uint handle) const
+{
+ return mPriv->contactsIds.value(handle);
+}
+
+/**
+ * Return whether the handles last for the whole lifetime of the connection.
+ *
+ * \return \c true if handles are immortal, \c false otherwise.
+ */
+bool ConnectionLowlevel::hasImmortalHandles() const
+{
+ ConnectionPtr conn(mPriv->conn);
+
+ return conn->mPriv->immortalHandles;
+}
+
+/**
+ * Return the ContactManager object for this connection.
+ *
+ * The contact manager is responsible for all contact handling in this
+ * connection, including adding, removing, authorizing, etc.
+ *
+ * \return A pointer to the ContactManager object.
+ */
+ContactManagerPtr Connection::contactManager() const
+{
+ return mPriv->contactManager;
+}
+
+ConnectionLowlevelPtr Connection::lowlevel()
+{
+ return mPriv->lowlevel;
+}
+
+ConnectionLowlevelConstPtr Connection::lowlevel() const
+{
+ return mPriv->lowlevel;
+}
+
+void Connection::refHandle(HandleType handleType, uint handle)
+{
+ if (mPriv->immortalHandles) {
+ return;
+ }
+
+ Private::HandleContext *handleContext = mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+
+ if (handleContext->types[handleType].toRelease.contains(handle)) {
+ handleContext->types[handleType].toRelease.remove(handle);
+ }
+
+ handleContext->types[handleType].refcounts[handle]++;
+}
+
+void Connection::unrefHandle(HandleType handleType, uint handle)
+{
+ if (mPriv->immortalHandles) {
+ return;
+ }
+
+ Private::HandleContext *handleContext = mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+
+ Q_ASSERT(handleContext->types.contains(handleType));
+ Q_ASSERT(handleContext->types[handleType].refcounts.contains(handle));
+
+ if (!--handleContext->types[handleType].refcounts[handle]) {
+ handleContext->types[handleType].refcounts.remove(handle);
+ handleContext->types[handleType].toRelease.insert(handle);
+
+ if (!handleContext->types[handleType].releaseScheduled) {
+ if (!handleContext->types[handleType].requestsInFlight) {
+ debug() << "Lost last reference to at least one handle of type" <<
+ handleType <<
+ "and no requests in flight for that type - scheduling a release sweep";
+ QMetaObject::invokeMethod(this, "doReleaseSweep",
+ Qt::QueuedConnection, Q_ARG(uint, handleType));
+ handleContext->types[handleType].releaseScheduled = true;
+ }
+ }
+ }
+}
+
+void Connection::doReleaseSweep(uint handleType)
+{
+ if (mPriv->immortalHandles) {
+ return;
+ }
+
+ Private::HandleContext *handleContext = mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+
+ Q_ASSERT(handleContext->types.contains(handleType));
+ Q_ASSERT(handleContext->types[handleType].releaseScheduled);
+
+ debug() << "Entering handle release sweep for type" << handleType;
+ handleContext->types[handleType].releaseScheduled = false;
+
+ if (handleContext->types[handleType].requestsInFlight > 0) {
+ debug() << " There are requests in flight, deferring sweep to when they have been completed";
+ return;
+ }
+
+ if (handleContext->types[handleType].toRelease.isEmpty()) {
+ debug() << " No handles to release - every one has been resurrected";
+ return;
+ }
+
+ debug() << " Releasing" << handleContext->types[handleType].toRelease.size() << "handles";
+
+ mPriv->baseInterface->ReleaseHandles(handleType, handleContext->types[handleType].toRelease.toList());
+ handleContext->types[handleType].toRelease.clear();
+}
+
+void Connection::handleRequestLanded(HandleType handleType)
+{
+ if (mPriv->immortalHandles) {
+ return;
+ }
+
+ Private::HandleContext *handleContext = mPriv->handleContext;
+ QMutexLocker locker(&handleContext->lock);
+
+ Q_ASSERT(handleContext->types.contains(handleType));
+ Q_ASSERT(handleContext->types[handleType].requestsInFlight > 0);
+
+ if (!--handleContext->types[handleType].requestsInFlight &&
+ !handleContext->types[handleType].toRelease.isEmpty() &&
+ !handleContext->types[handleType].releaseScheduled) {
+ debug() << "All handle requests for type" << handleType <<
+ "landed and there are handles of that type to release - scheduling a release sweep";
+ QMetaObject::invokeMethod(this, "doReleaseSweep", Qt::QueuedConnection, Q_ARG(uint, handleType));
+ handleContext->types[handleType].releaseScheduled = true;
+ }
+}
+
+void Connection::onSelfHandleChanged(uint handle)
+{
+ if (mPriv->selfHandle == handle) {
+ return;
+ }
+
+ if (mPriv->pendingStatus != ConnectionStatusConnected || !mPriv->selfHandle) {
+ debug() << "Got a self handle change before we have the initial self handle, ignoring";
+ return;
+ }
+
+ debug() << "Connection self handle changed to" << handle;
+ mPriv->selfHandle = handle;
+ emit selfHandleChanged(handle);
+
+ if (mPriv->introspectingSelfContact) {
+ // We're currently introspecting the SelfContact feature, but have started building the
+ // contact with the old handle, so we need to do it again with the new handle.
+
+ debug() << "The self contact is being built, will rebuild with the new handle shortly";
+ mPriv->reintrospectSelfContactRequired = true;
+ } else if (isReady(FeatureSelfContact)) {
+ // We've already introspected the SelfContact feature, so we can reinvoke the introspection
+ // logic directly to rebuild with the new handle.
+
+ debug() << "Re-building self contact for handle" << handle;
+ Private::introspectSelfContact(mPriv);
+ }
+
+ // If ReadinessHelper hasn't started introspecting SelfContact yet for the Connected state, we
+ // don't need to do anything. When it does start the introspection, it will do so using the
+ // correct handle.
+}
+
+void Connection::onBalanceChanged(const Tp::CurrencyAmount &value)
+{
+ mPriv->accountBalance = value;
+ emit accountBalanceChanged(value);
+}
+
+QString ConnectionHelper::statusReasonToErrorName(Tp::ConnectionStatusReason reason,
+ Tp::ConnectionStatus oldStatus)
+{
+ const char *errorName;
+
+ switch (reason) {
+ case ConnectionStatusReasonNoneSpecified:
+ errorName = TELEPATHY_ERROR_DISCONNECTED;
+ break;
+
+ case ConnectionStatusReasonRequested:
+ errorName = TELEPATHY_ERROR_CANCELLED;
+ break;
+
+ case ConnectionStatusReasonNetworkError:
+ errorName = TELEPATHY_ERROR_NETWORK_ERROR;
+ break;
+
+ case ConnectionStatusReasonAuthenticationFailed:
+ errorName = TELEPATHY_ERROR_AUTHENTICATION_FAILED;
+ break;
+
+ case ConnectionStatusReasonEncryptionError:
+ errorName = TELEPATHY_ERROR_ENCRYPTION_ERROR;
+ break;
+
+ case ConnectionStatusReasonNameInUse:
+ if (oldStatus == ConnectionStatusConnected) {
+ errorName = TELEPATHY_ERROR_CONNECTION_REPLACED;
+ } else {
+ errorName = TELEPATHY_ERROR_ALREADY_CONNECTED;
+ }
+ break;
+
+ case ConnectionStatusReasonCertNotProvided:
+ errorName = TELEPATHY_ERROR_CERT_NOT_PROVIDED;
+ break;
+
+ case ConnectionStatusReasonCertUntrusted:
+ errorName = TELEPATHY_ERROR_CERT_UNTRUSTED;
+ break;
+
+ case ConnectionStatusReasonCertExpired:
+ errorName = TELEPATHY_ERROR_CERT_EXPIRED;
+ break;
+
+ case ConnectionStatusReasonCertNotActivated:
+ errorName = TELEPATHY_ERROR_CERT_NOT_ACTIVATED;
+ break;
+
+ case ConnectionStatusReasonCertHostnameMismatch:
+ errorName = TELEPATHY_ERROR_CERT_HOSTNAME_MISMATCH;
+ break;
+
+ case ConnectionStatusReasonCertFingerprintMismatch:
+ errorName = TELEPATHY_ERROR_CERT_FINGERPRINT_MISMATCH;
+ break;
+
+ case ConnectionStatusReasonCertSelfSigned:
+ errorName = TELEPATHY_ERROR_CERT_SELF_SIGNED;
+ break;
+
+ case ConnectionStatusReasonCertOtherError:
+ errorName = TELEPATHY_ERROR_CERT_INVALID;
+ break;
+
+ default:
+ errorName = TELEPATHY_ERROR_DISCONNECTED;
+ break;
+ }
+
+ return QLatin1String(errorName);
+}
+
+/**
+ * \fn void Connection::statusChanged(Tp::ConnectionStatus newStatus)
+ *
+ * Indicates that the connection's status has changed and that all previously requested features are
+ * now ready to use for the new status.
+ *
+ * Legitimate uses for this signal, apart from waiting for a given connection status to be ready,
+ * include updating an animation based on the connection being in ConnectionStatusConnecting,
+ * ConnectionStatusConnected and ConnectionStatusDisconnected, and otherwise showing progress
+ * indication to the user. It should, however, NEVER be used for error handling:
+ *
+ * This signal doesn't contain the status reason as an argument, because statusChanged() shouldn't
+ * be used for error-handling. There are numerous cases in which a Connection may become unusable
+ * without there being a status change to ConnectionStatusDisconnected. All of these cases, and
+ * being disconnected itself, are signaled by invalidated() with appropriate error names. On the
+ * other hand, the reason for the status going to ConnectionStatusConnecting or
+ * ConnectionStatusConnected will always be ConnectionStatusReasonRequested, so signaling that would
+ * be useless.
+ *
+ * The status reason, as returned by statusReason(), may however be used as a fallback for error
+ * handling in slots connected to the invalidated() signal, if the client doesn't understand a
+ * particular (likely domain-specific if so) error name given by invalidateReason().
+ *
+ * \param newStatus The new status of the connection, as would be returned by status().
+ */
+
+/**
+ * \fn void Connection::selfHandleChanged(uint newHandle)
+ *
+ * Emitted when the value of selfHandle() changes.
+ *
+ * \param newHandle The new connection self handle.
+ * \sa selfHandle()
+ */
+
+/**
+ * \fn void Connection::selfContactChanged()
+ *
+ * Emitted when the value of selfContact() changes.
+ *
+ * \sa selfContact()
+ */
+
+/**
+ * \fn void Connection::accountBalanceChanged(const Tp::CurrencyAmount &accountBalance)
+ *
+ * Emitted when the value of accountBalance() changes.
+ *
+ * \param accountBalance The new user's balance of this connection.
+ * \sa accountBalance()
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/connection.h b/qt4/TelepathyQt4/connection.h
new file mode 100644
index 000000000..14278773a
--- /dev/null
+++ b/qt4/TelepathyQt4/connection.h
@@ -0,0 +1,249 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_connection_h_HEADER_GUARD_
+#define _TelepathyQt4_connection_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-connection.h>
+
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/OptionalInterfaceFactory>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/SharedPtr>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#include <QSet>
+#include <QString>
+#include <QStringList>
+
+namespace Tp
+{
+
+class Channel;
+class ConnectionCapabilities;
+class ConnectionLowlevel;
+class Contact;
+class ContactManager;
+class PendingChannel;
+class PendingContactAttributes;
+class PendingHandles;
+class PendingOperation;
+class PendingReady;
+
+class TELEPATHY_QT4_EXPORT Connection : public StatefulDBusProxy,
+ public OptionalInterfaceFactory<Connection>
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Connection)
+
+public:
+ static const Feature FeatureCore;
+ static const Feature FeatureSelfContact;
+ static const Feature FeatureSimplePresence;
+ static const Feature FeatureRoster;
+ static const Feature FeatureRosterGroups;
+ static const Feature FeatureAccountBalance; // TODO unit tests for this
+ static const Feature FeatureConnected;
+
+ static ConnectionPtr create(const QString &busName,
+ const QString &objectPath,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+ static ConnectionPtr create(const QDBusConnection &bus,
+ const QString &busName, const QString &objectPath,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory);
+
+ virtual ~Connection();
+
+ ChannelFactoryConstPtr channelFactory() const;
+ ContactFactoryConstPtr contactFactory() const;
+
+ QString cmName() const;
+ QString protocolName() const;
+
+ ConnectionStatus status() const;
+ ConnectionStatusReason statusReason() const;
+
+ class ErrorDetails
+ {
+ public:
+ ErrorDetails();
+ ErrorDetails(const QVariantMap &details);
+ ErrorDetails(const ErrorDetails &other);
+ ~ErrorDetails();
+
+ ErrorDetails &operator=(const ErrorDetails &other);
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ bool hasDebugMessage() const
+ {
+ return allDetails().contains(QLatin1String("debug-message"));
+ }
+
+ QString debugMessage() const
+ {
+ return qdbus_cast<QString>(allDetails().value(QLatin1String("debug-message")));
+ }
+
+ bool hasServerMessage() const
+ {
+ return allDetails().contains(QLatin1String("server-message"));
+ }
+
+ QString serverMessage() const
+ {
+ return qdbus_cast<QString>(allDetails().value(QLatin1String("server-message")));
+ }
+
+ bool hasUserRequested() const
+ {
+ return allDetails().contains(QLatin1String("user-requested"));
+ }
+
+ bool userRequested() const
+ {
+ return qdbus_cast<bool>(allDetails().value(QLatin1String("user-requested")));
+ }
+
+ bool hasExpectedHostname() const
+ {
+ return allDetails().contains(QLatin1String("expected-hostname"));
+ }
+
+ QString expectedHostname() const
+ {
+ return qdbus_cast<QString>(allDetails().value(QLatin1String("expected-hostname")));
+ }
+
+ bool hasCertificateHostname() const
+ {
+ return allDetails().contains(QLatin1String("certificate-hostname"));
+ }
+
+ QString certificateHostname() const
+ {
+ return qdbus_cast<QString>(allDetails().value(QLatin1String("certificate-hostname")));
+ }
+
+ QVariantMap allDetails() const;
+
+ private:
+ friend class Connection;
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ const ErrorDetails &errorDetails() const;
+
+ uint selfHandle() const;
+ ContactPtr selfContact() const;
+
+ CurrencyAmount accountBalance() const;
+
+ ConnectionCapabilities capabilities() const;
+
+ ContactManagerPtr contactManager() const;
+
+#if defined(BUILDING_TELEPATHY_QT4) || defined(TP_QT4_ENABLE_LOWLEVEL_API)
+ ConnectionLowlevelPtr lowlevel();
+ ConnectionLowlevelConstPtr lowlevel() const;
+#endif
+
+Q_SIGNALS:
+ void statusChanged(Tp::ConnectionStatus newStatus);
+
+ void selfHandleChanged(uint newHandle);
+ // FIXME: might not need this when Renaming is fixed and mapped to Contacts
+ void selfContactChanged();
+
+ void accountBalanceChanged(const Tp::CurrencyAmount &accountBalance);
+
+protected:
+ Connection(const QDBusConnection &bus, const QString &busName,
+ const QString &objectPath,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const Feature &coreFeature);
+
+ Client::ConnectionInterface *baseInterface() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onStatusReady(uint status);
+ TELEPATHY_QT4_NO_EXPORT void onStatusChanged(uint status, uint reason);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionError(const QString &error, const QVariantMap &details);
+ TELEPATHY_QT4_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotStatus(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotInterfaces(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotSelfHandle(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotCapabilities(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotContactAttributeInterfaces(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotSimpleStatuses(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotSelfContact(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onIntrospectRosterFinished(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onIntrospectRosterGroupsFinished(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void doReleaseSweep(uint handleType);
+
+ TELEPATHY_QT4_NO_EXPORT void onSelfHandleChanged(uint);
+
+ TELEPATHY_QT4_NO_EXPORT void gotBalance(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onBalanceChanged(const Tp::CurrencyAmount &);
+
+private:
+ class PendingConnect;
+ friend class ConnectionLowlevel;
+ friend class PendingChannel;
+ friend class PendingConnect;
+ friend class PendingContactAttributes;
+ friend class PendingContacts;
+ friend class PendingHandles;
+ friend class ReferencedHandles;
+
+ TELEPATHY_QT4_NO_EXPORT void refHandle(HandleType handleType, uint handle);
+ TELEPATHY_QT4_NO_EXPORT void unrefHandle(HandleType handleType, uint handle);
+ TELEPATHY_QT4_NO_EXPORT void handleRequestLanded(HandleType handleType);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::Connection::ErrorDetails);
+
+#endif
diff --git a/qt4/TelepathyQt4/connection.xml b/qt4/TelepathyQt4/connection.xml
new file mode 100644
index 000000000..bf726f582
--- /dev/null
+++ b/qt4/TelepathyQt4/connection.xml
@@ -0,0 +1,30 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Connection interfaces</tp:title>
+
+<xi:include href="../spec/Connection.xml"/>
+
+<xi:include href="../spec/Connection_Interface_Aliasing.xml"/>
+<xi:include href="../spec/Connection_Interface_Anonymity.xml"/>
+<xi:include href="../spec/Connection_Interface_Avatars.xml"/>
+<xi:include href="../spec/Connection_Interface_Balance.xml"/>
+<xi:include href="../spec/Connection_Interface_Capabilities.xml"/>
+<xi:include href="../spec/Connection_Interface_Cellular.xml"/>
+<xi:include href="../spec/Connection_Interface_Client_Types.xml"/>
+<xi:include href="../spec/Connection_Interface_Contacts.xml"/>
+<xi:include href="../spec/Connection_Interface_Contact_Blocking.xml"/>
+<xi:include href="../spec/Connection_Interface_Contact_Capabilities.xml"/>
+<xi:include href="../spec/Connection_Interface_Contact_Groups.xml"/>
+<xi:include href="../spec/Connection_Interface_Contact_Info.xml"/>
+<xi:include href="../spec/Connection_Interface_Contact_List.xml"/>
+<xi:include href="../spec/Connection_Interface_Location.xml"/>
+<xi:include href="../spec/Connection_Interface_Mail_Notification.xml"/>
+<xi:include href="../spec/Connection_Interface_Power_Saving.xml"/>
+<xi:include href="../spec/Connection_Interface_Presence.xml"/>
+<xi:include href="../spec/Connection_Interface_Requests.xml"/>
+<xi:include href="../spec/Connection_Interface_Service_Point.xml"/>
+<xi:include href="../spec/Connection_Interface_Simple_Presence.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/constants.h b/qt4/TelepathyQt4/constants.h
new file mode 100644
index 000000000..4bd9a54b0
--- /dev/null
+++ b/qt4/TelepathyQt4/constants.h
@@ -0,0 +1,285 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_constants_h_HEADER_GUARD_
+#define _TelepathyQt4_constants_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+/**
+ * \addtogroup typesconstants Types and constants
+ *
+ * Enumerated, flag, structure, list and mapping types and utility constants.
+ */
+
+/**
+ * \defgroup utilityconsts Utility string constants
+ * \ingroup typesconstants
+ *
+ * Utility constants which aren't generated from the specification but are
+ * useful for working with the Telepathy protocol.
+ * @{
+ */
+
+/**
+ * The prefix for a connection manager's bus name, to which the CM's name (e.g.
+ * "gabble") should be appended.
+ */
+#define TELEPATHY_CONNECTION_MANAGER_BUS_NAME_BASE "org.freedesktop.Telepathy.ConnectionManager."
+
+/**
+ * The prefix for a connection manager's bus name, to which the CM's name (e.g.
+ * "gabble") should be appended.
+ */
+#define TP_QT4_CONNECTION_MANAGER_BUS_NAME_BASE QLatin1String("org.freedesktop.Telepathy.ConnectionManager.")
+
+/**
+ * The prefix for a connection manager's object path, to which the CM's name
+ * (e.g. "gabble") should be appended.
+ */
+#define TELEPATHY_CONNECTION_MANAGER_OBJECT_PATH_BASE "/org/freedesktop/Telepathy/ConnectionManager/"
+
+/**
+ * The prefix for a connection manager's object path, to which the CM's name
+ * (e.g. "gabble") should be appended.
+ */
+#define TP_QT4_CONNECTION_MANAGER_OBJECT_PATH_BASE QLatin1String("/org/freedesktop/Telepathy/ConnectionManager/")
+
+/**
+ * The prefix for a connection's bus name, to which the CM's name (e.g.
+ * "gabble"), the protocol (e.g. "jabber") and an element
+ * representing the account should be appended.
+ */
+#define TELEPATHY_CONNECTION_BUS_NAME_BASE "org.freedesktop.Telepathy.Connection."
+
+/**
+ * The prefix for a connection's bus name, to which the CM's name (e.g.
+ * "gabble"), the protocol (e.g. "jabber") and an element
+ * representing the account should be appended.
+ */
+#define TP_QT4_CONNECTION_BUS_NAME_BASE QLatin1String("org.freedesktop.Telepathy.Connection.")
+
+/**
+ * The prefix for a connection's object path, to which the CM's name (e.g.
+ * "gabble"), the protocol (e.g. "jabber") and an element
+ * representing the account should be appended.
+ */
+#define TELEPATHY_CONNECTION_OBJECT_PATH_BASE "/org/freedesktop/Telepathy/Connection/"
+
+/**
+ * The well-known bus name of the Account Manager.
+ *
+ * \see Tp::AccountManager
+ */
+#define TELEPATHY_ACCOUNT_MANAGER_BUS_NAME \
+ "org.freedesktop.Telepathy.AccountManager"
+
+/**
+ * The well-known bus name of the Account Manager.
+ *
+ * \see Tp::AccountManager
+ */
+#define TP_QT4_ACCOUNT_MANAGER_BUS_NAME \
+ (QLatin1String("org.freedesktop.Telepathy.AccountManager"))
+
+/**
+ * The object path of the Account Manager object.
+ *
+ * \see Tp::AccountManager
+ */
+#define TELEPATHY_ACCOUNT_MANAGER_OBJECT_PATH \
+ "/org/freedesktop/Telepathy/AccountManager"
+
+/**
+ * The object path of the Account Manager object.
+ *
+ * \see Tp::AccountManager
+ */
+#define TP_QT4_ACCOUNT_MANAGER_OBJECT_PATH \
+ (QLatin1String("/org/freedesktop/Telepathy/AccountManager"))
+
+/**
+ * The well-known bus name of the Channel Dispatcher.
+ */
+#define TELEPATHY_CHANNEL_DISPATCHER_BUS_NAME \
+ "org.freedesktop.Telepathy.ChannelDispatcher"
+
+/**
+ * The well-known bus name of the Channel Dispatcher.
+ */
+#define TP_QT4_CHANNEL_DISPATCHER_BUS_NAME \
+ (QLatin1String("org.freedesktop.Telepathy.ChannelDispatcher"))
+
+/**
+ * The object path of the Channel Dispatcherr object.
+ */
+#define TELEPATHY_CHANNEL_DISPATCHER_OBJECT_PATH \
+ "/org/freedesktop/Telepathy/ChannelDispatcher"
+
+/**
+ * The object path of the Channel Dispatcherr object.
+ */
+#define TP_QT4_CHANNEL_DISPATCHER_OBJECT_PATH \
+ (QLatin1String("/org/freedesktop/Telepathy/ChannelDispatcher"))
+
+/**
+ * The prefix for an Account's object path, to which the CM's name (e.g.
+ * "gabble"), the protocol (e.g. "jabber") and an element
+ * identifying the particular account should be appended.
+ *
+ * \see Tp::Account
+ */
+#define TELEPATHY_ACCOUNT_OBJECT_PATH_BASE \
+ "/org/freedesktop/Telepathy/Account"
+
+/**
+ * The prefix for an Account's object path, to which the CM's name (e.g.
+ * "gabble"), the protocol (e.g. "jabber") and an element
+ * identifying the particular account should be appended.
+ *
+ * \see Tp::Account
+ */
+#define TP_QT4_ACCOUNT_OBJECT_PATH_BASE \
+ (QLatin1String("/org/freedesktop/Telepathy/Account"))
+
+/**
+ * @}
+ */
+
+#include <TelepathyQt4/_gen/constants.h>
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.DBus.Error.NameHasNoOwner".
+ *
+ * Raised by the D-Bus daemon when looking up the owner of a well-known name,
+ * if no process owns that name.
+ *
+ * Also used by DBusProxy to indicate that the owner of a well-known name
+ * has disappeared (usually indicating that the process owning that name
+ * exited or crashed).
+ */
+#define TELEPATHY_DBUS_ERROR_NAME_HAS_NO_OWNER \
+ "org.freedesktop.DBus.Error.NameHasNoOwner"
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.DBus.Error.NameHasNoOwner" as a QLatin1String.
+ *
+ * Raised by the D-Bus daemon when looking up the owner of a well-known name,
+ * if no process owns that name.
+ *
+ * Also used by DBusProxy to indicate that the owner of a well-known name
+ * has disappeared (usually indicating that the process owning that name
+ * exited or crashed).
+ */
+#define TP_QT4_DBUS_ERROR_NAME_HAS_NO_OWNER \
+ (QLatin1String("org.freedesktop.DBus.Error.NameHasNoOwner"))
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.DBus.Error.UnknownInterface".
+ */
+#define TELEPATHY_DBUS_ERROR_UNKNOWN_INTERFACE \
+ "org.freedesktop.DBus.Error.UnknownInterface"
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.DBus.Error.UnknownInterface" as a QLatin1String.
+ */
+#define TP_QT4_DBUS_ERROR_UNKNOWN_INTERFACE \
+ (QLatin1String("org.freedesktop.DBus.Error.UnknownInterface"))
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.DBus.Error.UnknownMethod".
+ *
+ * Raised by the D-Bus daemon when the method name invoked isn't
+ * known by the object you invoked it on.
+ */
+#define TELEPATHY_DBUS_ERROR_UNKNOWN_METHOD \
+ "org.freedesktop.DBus.Error.UnknownMethod"
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.DBus.Error.UnknownMethod" as a QLatin1String.
+ *
+ * Raised by the D-Bus daemon when the method name invoked isn't
+ * known by the object you invoked it on.
+ */
+#define TP_QT4_DBUS_ERROR_UNKNOWN_METHOD \
+ (QLatin1String("org.freedesktop.DBus.Error.UnknownMethod"))
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.Telepathy.Qt4.Error.ObjectRemoved".
+ */
+#define TELEPATHY_QT4_ERROR_OBJECT_REMOVED \
+ "org.freedesktop.Telepathy.Qt4.Error.ObjectRemoved"
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.Telepathy.Qt4.Error.ObjectRemoved" as a QLatin1String.
+ */
+#define TP_QT4_ERROR_OBJECT_REMOVED \
+ (QLatin1String("org.freedesktop.Telepathy.Qt4.Error.ObjectRemoved"))
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.Telepathy.Qt4.Error.Inconsistent".
+ */
+#define TELEPATHY_QT4_ERROR_INCONSISTENT \
+ "org.freedesktop.Telepathy.Qt4.Error.Inconsistent"
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.Telepathy.Qt4.Error.Inconsistent" as a QLatin1String.
+ */
+#define TP_QT4_ERROR_INCONSISTENT \
+ (QLatin1String("org.freedesktop.Telepathy.Qt4.Error.Inconsistent"))
+
+/**
+ * \ingroup errorstrconsts
+ *
+ * The error name "org.freedesktop.Telepathy.Qt4.Error.Orphaned" as a QLatin1String.
+ *
+ * This error is used when the "parent" proxy of an object gets invalidated. For example, a Channel
+ * whose corresponding Connection is invalidated invalidates itself with this error, as do leftover
+ * StreamTube connections when their parent StreamTubeChannel is invalidated. The invalidation
+ * reason of the parent proxy might provide more information on the cause of the error.
+ */
+#define TP_QT4_ERROR_ORPHANED \
+ (QLatin1String("org.freedesktop.Telepathy.Qt4.Error.Orphaned"))
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-capabilities.cpp b/qt4/TelepathyQt4/contact-capabilities.cpp
new file mode 100644
index 000000000..118a2db34
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-capabilities.cpp
@@ -0,0 +1,127 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ContactCapabilities>
+
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+/**
+ * \class ContactCapabilities
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/contact-capabilities.h <TelepathyQt4/ContactCapabilities>
+ *
+ * \brief The ContactCapabilities class represents the capabilities of a
+ * Contact.
+ */
+
+/**
+ * Construct a new ContactCapabilities object.
+ */
+ContactCapabilities::ContactCapabilities()
+ : CapabilitiesBase()
+{
+}
+
+/**
+ * Construct a new ContactCapabilities object.
+ */
+ContactCapabilities::ContactCapabilities(bool specificToContact)
+ : CapabilitiesBase(specificToContact)
+{
+}
+
+/**
+ * Construct a new ContactCapabilities object using the give \a rccs.
+ *
+ * \param rccs RequestableChannelClassList representing the capabilities of a
+ * contact.
+ */
+ContactCapabilities::ContactCapabilities(const RequestableChannelClassList &rccs,
+ bool specificToContact)
+ : CapabilitiesBase(rccs, specificToContact)
+{
+}
+
+/**
+ * Construct a new ContactCapabilities object using the give \a rccSpecs.
+ *
+ * \param rccSpecs RequestableChannelClassList representing the capabilities of a
+ * contact.
+ */
+ContactCapabilities::ContactCapabilities(const RequestableChannelClassSpecList &rccSpecs,
+ bool specificToContact)
+ : CapabilitiesBase(rccSpecs, specificToContact)
+{
+}
+
+/**
+ * Class destructor.
+ */
+ContactCapabilities::~ContactCapabilities()
+{
+}
+
+/**
+ * Return whether creating a StreamTube channel, using the given \a service, by providing a
+ * contact identifier is supported.
+ *
+ * \return \c true if supported, \c false otherwise.
+ */
+bool ContactCapabilities::streamTubes(const QString &service) const
+{
+ RequestableChannelClassSpec streamTubeSpec = RequestableChannelClassSpec::streamTube(service);
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.supports(streamTubeSpec)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return the supported StreamTube services.
+ *
+ * \return A list of supported StreamTube services.
+ */
+QStringList ContactCapabilities::streamTubeServices() const
+{
+ QSet<QString> ret;
+
+ RequestableChannelClassSpecList rccSpecs = allClassSpecs();
+ foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) {
+ if (rccSpec.channelType() == TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE &&
+ rccSpec.targetHandleType() == HandleTypeContact &&
+ rccSpec.hasFixedProperty(
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE + QLatin1String(".Service"))) {
+ ret << rccSpec.fixedProperty(
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE + QLatin1String(".Service")).toString();
+ }
+ }
+
+ return ret.toList();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/contact-capabilities.h b/qt4/TelepathyQt4/contact-capabilities.h
new file mode 100644
index 000000000..ae923c384
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-capabilities.h
@@ -0,0 +1,66 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_capabilities_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_capabilities_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/CapabilitiesBase>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TestBackdoors;
+
+class TELEPATHY_QT4_EXPORT ContactCapabilities : public CapabilitiesBase
+{
+public:
+ ContactCapabilities();
+ virtual ~ContactCapabilities();
+
+ bool streamTubes(const QString &service) const;
+ QStringList streamTubeServices() const;
+
+ // later:
+ // bool dbusTubes(const QString &service) const;
+ // QStringList dbusTubeServices() const;
+
+protected:
+ friend class Contact;
+ friend class TestBackdoors;
+
+ ContactCapabilities(bool specificToContact);
+ ContactCapabilities(const RequestableChannelClassList &rccs,
+ bool specificToContact);
+ ContactCapabilities(const RequestableChannelClassSpecList &rccSpecs,
+ bool specificToContact);
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ContactCapabilities);
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-factory.cpp b/qt4/TelepathyQt4/contact-factory.cpp
new file mode 100644
index 000000000..9f71e381c
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-factory.cpp
@@ -0,0 +1,146 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ContactFactory>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ContactFactory::Private
+{
+ Features features;
+};
+
+/**
+ * \class ContactFactory
+ * \ingroup utils
+ * \headerfile TelepathyQt4/contact-factory.h <TelepathyQt4/ContactFactory>
+ *
+ * \brief The ContactFactory class is responsible for constructing Contact
+ * objects according to application-defined settings.
+ */
+
+/**
+ * Creates a new ContactFactory.
+ *
+ * \param features The features to make ready on constructed contacts.
+ * \returns A pointer to the created factory.
+ */
+ContactFactoryPtr ContactFactory::create(const Features &features)
+{
+ return ContactFactoryPtr(new ContactFactory(features));
+}
+
+/**
+ * Class constructor.
+ *
+ * \param features The features to make ready on constructed contacts.
+ */
+ContactFactory::ContactFactory(const Features &features)
+ : mPriv(new Private)
+{
+ addFeatures(features);
+}
+
+/**
+ * Class destructor.
+ */
+ContactFactory::~ContactFactory()
+{
+ delete mPriv;
+}
+
+/**
+ * Gets the features this factory will make ready on constructed contacts.
+ *
+ * \return The set of features.
+ */
+Features ContactFactory::features() const
+{
+ Features features = mPriv->features;
+ // FeatureAvatarData depends on FeatureAvatarToken
+ if (features.contains(Contact::FeatureAvatarData) &&
+ !features.contains(Contact::FeatureAvatarToken)) {
+ features.insert(Contact::FeatureAvatarToken);
+ }
+
+ return features;
+}
+
+/**
+ * Adds a single feature this factory will make ready on further constructed contacts.
+ *
+ * No feature removal is provided, to guard against uncooperative modules removing features other
+ * modules have set and depend on.
+ *
+ * \param feature The feature to add.
+ */
+void ContactFactory::addFeature(const Feature &feature)
+{
+ addFeatures(Features(feature));
+}
+
+/**
+ * Adds a set of features this factory will make ready on further constructed contacts.
+ *
+ * No feature removal is provided, to guard against uncooperative modules removing features other
+ * modules have set and depend on.
+ *
+ * \param features The features to add.
+ */
+void ContactFactory::addFeatures(const Features &features)
+{
+ mPriv->features.unite(features);
+}
+
+/**
+ * Can be used by subclasses to override the Contact subclass constructed by the factory.
+ *
+ * The default implementation constructs Tp::Contact objects.
+ *
+ * \param manager The contact manager this contact belongs.
+ * \param handle The contact handle.
+ * \param features The desired contact features.
+ * \param attributes The desired contact attributes.
+ * \return A pointer to the constructed contact.
+ */
+ContactPtr ContactFactory::construct(Tp::ContactManager *manager, const ReferencedHandles &handle,
+ const Features &features, const QVariantMap &attributes) const
+{
+ ContactPtr contact = ContactPtr(new Contact(manager, handle, features, attributes));
+ return contact;
+}
+
+/**
+ * Can be used by subclasses to do arbitrary manipulation on constructed Contact objects.
+ *
+ * The default implementation does nothing.
+ *
+ * \param contact The contact to be prepared.
+ * \return A PendingOperation used to prepare the contact or NULL if there is nothing to prepare.
+ */
+PendingOperation *ContactFactory::prepare(const ContactPtr &contact) const
+{
+ return NULL;
+}
+
+}
diff --git a/qt4/TelepathyQt4/contact-factory.h b/qt4/TelepathyQt4/contact-factory.h
new file mode 100644
index 000000000..5329fab92
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-factory.h
@@ -0,0 +1,76 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/Types>
+
+#include <QSet>
+#include <QtGlobal>
+
+namespace Tp
+{
+
+class ContactManager;
+class ReferencedHandles;
+
+class TELEPATHY_QT4_EXPORT ContactFactory : public RefCounted
+{
+ Q_DISABLE_COPY(ContactFactory)
+
+public:
+ static ContactFactoryPtr create(const Features &features = Features());
+
+ virtual ~ContactFactory();
+
+ Features features() const;
+
+ void addFeature(const Feature &feature);
+ void addFeatures(const Features &features);
+
+protected:
+ ContactFactory(const Features &features);
+
+ virtual ContactPtr construct(ContactManager *manager, const ReferencedHandles &handle,
+ const Features &features, const QVariantMap &attributes) const;
+ virtual PendingOperation *prepare(const ContactPtr &contact) const;
+
+private:
+ friend class ContactManager;
+ friend class PendingContacts;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-manager-internal.h b/qt4/TelepathyQt4/contact-manager-internal.h
new file mode 100644
index 000000000..d43df756a
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-manager-internal.h
@@ -0,0 +1,389 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_manager_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_manager_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+#include <QList>
+#include <QMap>
+#include <QObject>
+#include <QQueue>
+#include <QString>
+#include <QStringList>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT ContactManager::Roster : public QObject
+{
+ Q_OBJECT
+
+public:
+ Roster(ContactManager *manager);
+ virtual ~Roster();
+
+ ContactListState state() const;
+
+ PendingOperation *introspect();
+ PendingOperation *introspectGroups();
+ void reset();
+
+ Contacts allKnownContacts() const;
+ QStringList allKnownGroups() const;
+
+ PendingOperation *addGroup(const QString &group);
+ PendingOperation *removeGroup(const QString &group);
+
+ Contacts groupContacts(const QString &group) const;
+ PendingOperation *addContactsToGroup(const QString &group,
+ const QList<ContactPtr> &contacts);
+ PendingOperation *removeContactsFromGroup(const QString &group,
+ const QList<ContactPtr> &contacts);
+
+ bool canRequestPresenceSubscription() const;
+ bool subscriptionRequestHasMessage() const;
+ PendingOperation *requestPresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message);
+ bool canRemovePresenceSubscription() const;
+ bool subscriptionRemovalHasMessage() const;
+ bool canRescindPresenceSubscriptionRequest() const;
+ bool subscriptionRescindingHasMessage() const;
+ PendingOperation *removePresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message);
+ bool canAuthorizePresencePublication() const;
+ bool publicationAuthorizationHasMessage() const;
+ PendingOperation *authorizePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message);
+ bool publicationRejectionHasMessage() const;
+ bool canRemovePresencePublication() const;
+ bool publicationRemovalHasMessage() const;
+ PendingOperation *removePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message);
+ PendingOperation *removeContacts(
+ const QList<ContactPtr> &contacts, const QString &message);
+
+ bool canBlockContacts() const;
+ bool canReportAbuse() const;
+ PendingOperation *blockContacts(const QList<ContactPtr> &contacts, bool value, bool reportAbuse);
+
+private Q_SLOTS:
+ void gotContactBlockingCapabilities(Tp::PendingOperation *op);
+ void gotContactBlockingBlockedContacts(QDBusPendingCallWatcher *watcher);
+ void onContactBlockingBlockedContactsChanged(
+ const Tp::HandleIdentifierMap &added,
+ const Tp::HandleIdentifierMap &removed);
+
+ void gotContactListProperties(Tp::PendingOperation *op);
+ void gotContactListContacts(QDBusPendingCallWatcher *watcher);
+ void setStateSuccess();
+ void onContactListStateChanged(uint state);
+ void onContactListContactsChangedWithId(const Tp::ContactSubscriptionMap &changes,
+ const Tp::HandleIdentifierMap &ids, const Tp::HandleIdentifierMap &removals);
+ void onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes,
+ const Tp::UIntList &removals);
+
+ void onContactListBlockedContactsConstructed(Tp::PendingOperation *op);
+ void onContactListNewContactsConstructed(Tp::PendingOperation *op);
+ void onContactListGroupsChanged(const Tp::UIntList &contacts,
+ const QStringList &added, const QStringList &removed);
+ void onContactListGroupsCreated(const QStringList &names);
+ void onContactListGroupRenamed(const QString &oldName, const QString &newName);
+ void onContactListGroupsRemoved(const QStringList &names);
+
+ void onModifyFinished(Tp::PendingOperation *op);
+ void onModifyFinishSignaled();
+
+ void gotContactListChannelHandle(Tp::PendingOperation *op);
+ void gotContactListChannel(Tp::PendingOperation *op);
+ void onContactListChannelReady();
+
+ void gotContactListGroupsProperties(Tp::PendingOperation *op);
+ void onContactListContactsUpgraded(Tp::PendingOperation *op);
+
+ void onNewChannels(const Tp::ChannelDetailsList &channelDetailsList);
+ void onContactListGroupChannelReady(Tp::PendingOperation *op);
+ void gotChannels(QDBusPendingCallWatcher *watcher);
+
+ void onStoredChannelMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void onSubscribeChannelMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void onPublishChannelMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void onDenyChannelMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void onContactListGroupMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void onContactListGroupRemoved(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage);
+
+private:
+ struct ChannelInfo;
+ struct BlockedContactsChangedInfo;
+ struct UpdateInfo;
+ struct GroupsUpdateInfo;
+ struct GroupRenamedInfo;
+ class ModifyFinishOp;
+ class RemoveGroupOp;
+
+ void introspectContactBlocking();
+ void introspectContactBlockingBlockedContacts();
+ void introspectContactList();
+ void introspectContactListContacts();
+ void processContactListChanges();
+ void processContactListBlockedContactsChanged();
+ void processContactListUpdates();
+ void processContactListGroupsUpdates();
+ void processContactListGroupsCreated();
+ void processContactListGroupRenamed();
+ void processContactListGroupsRemoved();
+ void processFinishedModify();
+ PendingOperation *queuedFinishVoid(const QDBusPendingCall &call);
+ void setContactListChannelsReady();
+ void updateContactsBlockState();
+ void updateContactsPresenceState();
+ void computeKnownContactsChanges(const Contacts &added,
+ const Contacts &pendingAdded, const Contacts &remotePendingAdded,
+ const Contacts &removed, const Channel::GroupMemberChangeDetails &details);
+ void checkContactListGroupsReady();
+ void setContactListGroupChannelsReady();
+ QString addContactListGroupChannel(const ChannelPtr &contactListGroupChannel);
+
+ ContactManager *contactManager;
+
+ Contacts cachedAllKnownContacts;
+
+ bool usingFallbackContactList;
+ bool hasContactBlockingInterface;
+
+ PendingOperation *introspectPendingOp;
+ PendingOperation *introspectGroupsPendingOp;
+ uint pendingContactListState;
+ uint contactListState;
+ bool canReportAbusive;
+ bool gotContactBlockingInitialBlockedContacts;
+ bool canChangeContactList;
+ bool contactListRequestUsesMessage;
+ bool gotContactListInitialContacts;
+ bool gotContactListContactsChangedWithId;
+ bool groupsReintrospectionRequired;
+ QSet<QString> cachedAllKnownGroups;
+ bool contactListGroupPropertiesReceived;
+ QQueue<void (ContactManager::Roster::*)()> contactListChangesQueue;
+ QQueue<BlockedContactsChangedInfo> contactListBlockedContactsChangedQueue;
+ QQueue<UpdateInfo> contactListUpdatesQueue;
+ QQueue<GroupsUpdateInfo> contactListGroupsUpdatesQueue;
+ QQueue<QStringList> contactListGroupsCreatedQueue;
+ QQueue<GroupRenamedInfo> contactListGroupRenamedQueue;
+ QQueue<QStringList> contactListGroupsRemovedQueue;
+ bool processingContactListChanges;
+
+ QMap<PendingOperation * /* actual */, ModifyFinishOp *> returnedModifyOps;
+ QQueue<ModifyFinishOp *> modifyFinishQueue;
+
+ // old roster API
+ uint contactListChannelsReady;
+ QMap<uint, ChannelInfo> contactListChannels;
+ ChannelPtr subscribeChannel;
+ ChannelPtr publishChannel;
+ ChannelPtr storedChannel;
+ ChannelPtr denyChannel;
+
+ // Number of things left to do before the Groups feature is ready
+ // 1 for Get("Channels") + 1 per channel not ready
+ uint featureContactListGroupsTodo;
+ QList<ChannelPtr> pendingContactListGroupChannels;
+ QMap<QString, ChannelPtr> contactListGroupChannels;
+ QList<ChannelPtr> removedContactListGroupChannels;
+
+ // If RosterGroups introspection completing should advance the ContactManager state to Success
+ bool groupsSetSuccess;
+
+ // Contact list contacts using the Conn.I.ContactList API
+ Contacts contactListContacts;
+ // Blocked contacts using the new ContactBlocking API
+ Contacts blockedContacts;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::ChannelInfo
+{
+ enum Type {
+ TypeSubscribe = 0,
+ TypePublish,
+ TypeStored,
+ TypeDeny,
+ LastType
+ };
+
+ ChannelInfo()
+ : type((Type) -1)
+ {
+ }
+
+ ChannelInfo(Type type)
+ : type(type)
+ {
+ }
+
+ static QString identifierForType(Type type);
+ static uint typeForIdentifier(const QString &identifier);
+
+ Type type;
+ ReferencedHandles handle;
+ ChannelPtr channel;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::BlockedContactsChangedInfo
+{
+ BlockedContactsChangedInfo(const HandleIdentifierMap &added,
+ const HandleIdentifierMap &removed,
+ bool continueIntrospectionWhenFinished = false)
+ : added(added),
+ removed(removed),
+ continueIntrospectionWhenFinished(continueIntrospectionWhenFinished)
+ {
+ }
+
+ HandleIdentifierMap added;
+ HandleIdentifierMap removed;
+ bool continueIntrospectionWhenFinished;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::UpdateInfo
+{
+ UpdateInfo(const ContactSubscriptionMap &changes, const HandleIdentifierMap &ids,
+ const HandleIdentifierMap &removals)
+ : changes(changes),
+ ids(ids),
+ removals(removals)
+ {
+ }
+
+ ContactSubscriptionMap changes;
+ HandleIdentifierMap ids;
+ HandleIdentifierMap removals;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::GroupsUpdateInfo
+{
+ GroupsUpdateInfo(const UIntList &contacts,
+ const QStringList &groupsAdded, const QStringList &groupsRemoved)
+ : contacts(contacts),
+ groupsAdded(groupsAdded),
+ groupsRemoved(groupsRemoved)
+ {
+ }
+
+ UIntList contacts;
+ QStringList groupsAdded;
+ QStringList groupsRemoved;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::GroupRenamedInfo
+{
+ GroupRenamedInfo(const QString &oldName, const QString &newName)
+ : oldName(oldName),
+ newName(newName)
+ {
+ }
+
+ QString oldName;
+ QString newName;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::ModifyFinishOp : public PendingOperation
+{
+ Q_OBJECT
+
+public:
+ ModifyFinishOp(const ConnectionPtr &conn);
+ ~ModifyFinishOp() {};
+
+ void setError(const QString &errorName, const QString &errorMessage);
+ void finish();
+
+private:
+ QString errorName, errorMessage;
+};
+
+class TELEPATHY_QT4_NO_EXPORT ContactManager::Roster::RemoveGroupOp : public PendingOperation
+{
+ Q_OBJECT
+
+public:
+ RemoveGroupOp(const ChannelPtr &channel);
+ ~RemoveGroupOp() {};
+
+private Q_SLOTS:
+ void onContactsRemoved(Tp::PendingOperation *);
+ void onChannelClosed(Tp::PendingOperation *);
+};
+
+class TELEPATHY_QT4_NO_EXPORT ContactManager::PendingRefreshContactInfo : public PendingOperation
+{
+ Q_OBJECT
+
+public:
+ PendingRefreshContactInfo(const ConnectionPtr &conn);
+ ~PendingRefreshContactInfo();
+
+ void addContact(Contact *contact);
+
+ void refreshInfo();
+
+private Q_SLOTS:
+ void onRefreshInfoFinished(Tp::PendingOperation *op);
+
+private:
+ ConnectionPtr mConn;
+ QSet<uint> mToRequest;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-manager-roster.cpp b/qt4/TelepathyQt4/contact-manager-roster.cpp
new file mode 100644
index 000000000..5c1dbda58
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-manager-roster.cpp
@@ -0,0 +1,2217 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "TelepathyQt4/contact-manager-internal.h"
+
+#include "TelepathyQt4/_gen/contact-manager-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReferencedHandles>
+
+namespace Tp
+{
+
+ContactManager::Roster::Roster(ContactManager *contactManager)
+ : QObject(),
+ contactManager(contactManager),
+ usingFallbackContactList(false),
+ hasContactBlockingInterface(false),
+ introspectPendingOp(0),
+ introspectGroupsPendingOp(0),
+ pendingContactListState((uint) -1),
+ contactListState((uint) -1),
+ canReportAbusive(false),
+ gotContactBlockingInitialBlockedContacts(false),
+ canChangeContactList(false),
+ contactListRequestUsesMessage(false),
+ gotContactListInitialContacts(false),
+ gotContactListContactsChangedWithId(false),
+ groupsReintrospectionRequired(false),
+ contactListGroupPropertiesReceived(false),
+ processingContactListChanges(false),
+ contactListChannelsReady(0),
+ featureContactListGroupsTodo(0),
+ groupsSetSuccess(false)
+{
+}
+
+ContactManager::Roster::~Roster()
+{
+}
+
+ContactListState ContactManager::Roster::state() const
+{
+ return (Tp::ContactListState) contactListState;
+}
+
+PendingOperation *ContactManager::Roster::introspect()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) {
+ debug() << "Connection.ContactList found, using it";
+
+ usingFallbackContactList = false;
+
+ if (conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING)) {
+ debug() << "Connection.ContactBlocking found. using it";
+ hasContactBlockingInterface = true;
+ introspectContactBlocking();
+ } else {
+ debug() << "Connection.ContactBlocking not found, falling back "
+ "to contact list deny channel";
+
+ debug() << "Requesting handle for deny channel";
+
+ contactListChannels.insert(ChannelInfo::TypeDeny,
+ ChannelInfo(ChannelInfo::TypeDeny));
+
+ PendingHandles *ph = conn->lowlevel()->requestHandles(HandleTypeList,
+ QStringList() << ChannelInfo::identifierForType(
+ ChannelInfo::TypeDeny));
+ connect(ph,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListChannelHandle(Tp::PendingOperation*)));
+ }
+ } else {
+ debug() << "Connection.ContactList not found, falling back to contact list channels";
+
+ usingFallbackContactList = true;
+
+ for (uint i = 0; i < ChannelInfo::LastType; ++i) {
+ QString channelId = ChannelInfo::identifierForType(
+ (ChannelInfo::Type) i);
+
+ debug() << "Requesting handle for" << channelId << "channel";
+
+ contactListChannels.insert(i,
+ ChannelInfo((ChannelInfo::Type) i));
+
+ PendingHandles *ph = conn->lowlevel()->requestHandles(HandleTypeList,
+ QStringList() << channelId);
+ connect(ph,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListChannelHandle(Tp::PendingOperation*)));
+ }
+ }
+
+ Q_ASSERT(!introspectPendingOp);
+ introspectPendingOp = new PendingOperation(conn);
+ return introspectPendingOp;
+}
+
+PendingOperation *ContactManager::Roster::introspectGroups()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ Q_ASSERT(!introspectGroupsPendingOp);
+
+ if (conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) {
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(TP_QT4_ERROR_NOT_IMPLEMENTED,
+ QLatin1String("Roster groups not supported"), conn);
+ }
+
+ debug() << "Connection.ContactGroups found, using it";
+
+ if (!gotContactListInitialContacts) {
+ debug() << "Initial ContactList contacts not retrieved. Postponing introspection";
+ groupsReintrospectionRequired = true;
+ return new PendingSuccess(conn);
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+
+ connect(iface,
+ SIGNAL(GroupsChanged(Tp::UIntList,QStringList,QStringList)),
+ SLOT(onContactListGroupsChanged(Tp::UIntList,QStringList,QStringList)));
+ connect(iface,
+ SIGNAL(GroupsCreated(QStringList)),
+ SLOT(onContactListGroupsCreated(QStringList)));
+ connect(iface,
+ SIGNAL(GroupRenamed(QString,QString)),
+ SLOT(onContactListGroupRenamed(QString,QString)));
+ connect(iface,
+ SIGNAL(GroupsRemoved(QStringList)),
+ SLOT(onContactListGroupsRemoved(QStringList)));
+
+ PendingVariantMap *pvm = iface->requestAllProperties();
+ connect(pvm,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListGroupsProperties(Tp::PendingOperation*)));
+ } else {
+ debug() << "Connection.ContactGroups not found, falling back to contact list group channels";
+
+ ++featureContactListGroupsTodo; // decremented in gotChannels
+
+ // we already checked if requests interface exists, so bypass requests
+ // interface checking
+ Client::ConnectionInterfaceRequestsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceRequestsInterface>();
+
+ debug() << "Connecting to Requests.NewChannels";
+ connect(iface,
+ SIGNAL(NewChannels(Tp::ChannelDetailsList)),
+ SLOT(onNewChannels(Tp::ChannelDetailsList)));
+
+ debug() << "Retrieving channels";
+ Client::DBus::PropertiesInterface *properties =
+ contactManager->connection()->interface<Client::DBus::PropertiesInterface>();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS),
+ QLatin1String("Channels")), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotChannels(QDBusPendingCallWatcher*)));
+ }
+
+ if (groupsReintrospectionRequired) {
+ return NULL;
+ }
+
+ Q_ASSERT(!introspectGroupsPendingOp);
+ introspectGroupsPendingOp = new PendingOperation(conn);
+ return introspectGroupsPendingOp;
+}
+
+void ContactManager::Roster::reset()
+{
+ contactListChannels.clear();
+ subscribeChannel.reset();
+ publishChannel.reset();
+ storedChannel.reset();
+ denyChannel.reset();
+ contactListGroupChannels.clear();
+ removedContactListGroupChannels.clear();
+}
+
+Contacts ContactManager::Roster::allKnownContacts() const
+{
+ return cachedAllKnownContacts;
+}
+
+QStringList ContactManager::Roster::allKnownGroups() const
+{
+ if (usingFallbackContactList) {
+ return contactListGroupChannels.keys();
+ }
+
+ return cachedAllKnownGroups.toList();
+}
+
+PendingOperation *ContactManager::Roster::addGroup(const QString &group)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeGroup);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ group);
+ return conn->lowlevel()->ensureChannel(request);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->AddToGroup(group, UIntList()));
+}
+
+PendingOperation *ContactManager::Roster::removeGroup(const QString &group)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid group"),
+ conn);
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return new RemoveGroupOp(channel);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RemoveGroup(group));
+}
+
+Contacts ContactManager::Roster::groupContacts(const QString &group) const
+{
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return Contacts();
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return channel->groupContacts();
+ }
+
+ Contacts ret;
+ foreach (const ContactPtr &contact, allKnownContacts()) {
+ if (contact->groups().contains(group))
+ ret << contact;
+ }
+ return ret;
+}
+
+PendingOperation *ContactManager::Roster::addContactsToGroup(const QString &group,
+ const QList<ContactPtr> &contacts)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid group"),
+ conn);
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return channel->groupAddContacts(contacts);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->AddToGroup(group, handles));
+}
+
+PendingOperation *ContactManager::Roster::removeContactsFromGroup(const QString &group,
+ const QList<ContactPtr> &contacts)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!contactListGroupChannels.contains(group)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid group"),
+ conn);
+ }
+
+ ChannelPtr channel = contactListGroupChannels[group];
+ return channel->groupRemoveContacts(contacts);
+ }
+
+ if (!conn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Not implemented"),
+ conn);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactGroupsInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactGroupsInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RemoveFromGroup(group, handles));
+}
+
+bool ContactManager::Roster::canRequestPresenceSubscription() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel && subscribeChannel->groupCanAddContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::subscriptionRequestHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageAdd);
+ }
+
+ return contactListRequestUsesMessage;
+}
+
+PendingOperation *ContactManager::Roster::requestPresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!subscribeChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot subscribe to contacts' presence on this protocol"),
+ conn);
+ }
+
+ return subscribeChannel->groupAddContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RequestSubscription(handles, message));
+}
+
+bool ContactManager::Roster::canRemovePresenceSubscription() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel && subscribeChannel->groupCanRemoveContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::subscriptionRemovalHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRemove);
+ }
+
+ return false;
+}
+
+bool ContactManager::Roster::canRescindPresenceSubscriptionRequest() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel && subscribeChannel->groupCanRescindContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::subscriptionRescindingHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRescind);
+ }
+
+ return false;
+}
+
+PendingOperation *ContactManager::Roster::removePresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!subscribeChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot subscribe to contacts' presence on this protocol"),
+ conn);
+ }
+
+ return subscribeChannel->groupRemoveContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->Unsubscribe(handles));
+}
+
+bool ContactManager::Roster::canAuthorizePresencePublication() const
+{
+ if (usingFallbackContactList) {
+ // do not check for Channel::groupCanAddContacts as all contacts in local
+ // pending can be added, even if the Channel::groupFlags() does not contain
+ // the flag CanAdd
+ return (bool) publishChannel;
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::publicationAuthorizationHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageAccept);
+ }
+
+ return false;
+}
+
+PendingOperation *ContactManager::Roster::authorizePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!publishChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot control publication of presence on this protocol"),
+ conn);
+ }
+
+ return publishChannel->groupAddContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->AuthorizePublication(handles));
+}
+
+bool ContactManager::Roster::publicationRejectionHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageReject);
+ }
+
+ return false;
+}
+
+bool ContactManager::Roster::canRemovePresencePublication() const
+{
+ if (usingFallbackContactList) {
+ return publishChannel && publishChannel->groupCanRemoveContacts();
+ }
+
+ return canChangeContactList;
+}
+
+bool ContactManager::Roster::publicationRemovalHasMessage() const
+{
+ if (usingFallbackContactList) {
+ return subscribeChannel &&
+ (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRemove);
+ }
+
+ return false;
+}
+
+PendingOperation *ContactManager::Roster::removePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ if (!publishChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot control publication of presence on this protocol"),
+ conn);
+ }
+
+ return publishChannel->groupRemoveContacts(contacts, message);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->Unpublish(handles));
+}
+
+PendingOperation *ContactManager::Roster::removeContacts(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ if (usingFallbackContactList) {
+ /* If the CM implements stored channel correctly, it should have the
+ * wanted behaviour. Otherwise we have to to remove from publish
+ * and subscribe channels.
+ */
+
+ if (storedChannel && storedChannel->groupCanRemoveContacts()) {
+ debug() << "Removing contacts from stored list";
+ return storedChannel->groupRemoveContacts(contacts, message);
+ }
+
+ QList<PendingOperation*> operations;
+
+ if (canRemovePresenceSubscription()) {
+ debug() << "Removing contacts from subscribe list";
+ operations << removePresenceSubscription(contacts, message);
+ }
+
+ if (canRemovePresencePublication()) {
+ debug() << "Removing contacts from publish list";
+ operations << removePresencePublication(contacts, message);
+ }
+
+ if (operations.isEmpty()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot remove contacts on this protocol"),
+ conn);
+ }
+
+ return new PendingComposite(operations, conn);
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+ Q_ASSERT(iface);
+ return queuedFinishVoid(iface->RemoveContacts(handles));
+}
+
+bool ContactManager::Roster::canBlockContacts() const
+{
+ if (!usingFallbackContactList && hasContactBlockingInterface) {
+ return true;
+ } else {
+ return (bool) denyChannel;
+ }
+}
+
+bool ContactManager::Roster::canReportAbuse() const
+{
+ return canReportAbusive;
+}
+
+PendingOperation *ContactManager::Roster::blockContacts(
+ const QList<ContactPtr> &contacts, bool value, bool reportAbuse)
+{
+ if (!contactManager->connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ contactManager->connection());
+ } else if (!contactManager->connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ contactManager->connection());
+ }
+
+ if (!usingFallbackContactList && hasContactBlockingInterface) {
+ ConnectionPtr conn(contactManager->connection());
+ Client::ConnectionInterfaceContactBlockingInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactBlockingInterface>();
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles << contact->handle()[0];
+ }
+
+ Q_ASSERT(iface);
+ if(value) {
+ return queuedFinishVoid(iface->BlockContacts(handles, reportAbuse));
+ } else {
+ return queuedFinishVoid(iface->UnblockContacts(handles));
+ }
+
+ } else {
+ ConnectionPtr conn(contactManager->connection());
+
+ if (!denyChannel) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("Cannot block contacts on this protocol"),
+ conn);
+ }
+
+ if (value) {
+ return denyChannel->groupAddContacts(contacts);
+ } else {
+ return denyChannel->groupRemoveContacts(contacts);
+ }
+ }
+}
+
+void ContactManager::Roster::gotContactBlockingCapabilities(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Getting ContactBlockingCapabilities property failed with" <<
+ op->errorName() << ":" << op->errorMessage();
+ introspectContactList();
+ return;
+ }
+
+ debug() << "Got ContactBlockingCapabilities property";
+
+ PendingVariant *pv = qobject_cast<PendingVariant*>(op);
+
+ uint contactBlockingCaps = pv->result().toUInt();
+ canReportAbusive =
+ contactBlockingCaps & ContactBlockingCapabilityCanReportAbusive;
+
+ introspectContactBlockingBlockedContacts();
+}
+
+void ContactManager::Roster::gotContactBlockingBlockedContacts(
+ QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<HandleIdentifierMap> reply = *watcher;
+
+ if (watcher->isError()) {
+ warning() << "Getting initial ContactBlocking blocked "
+ "contacts failed with" <<
+ watcher->error().name() << ":" << watcher->error().message();
+ introspectContactList();
+ return;
+ }
+
+ debug() << "Got initial ContactBlocking blocked contacts";
+
+ gotContactBlockingInitialBlockedContacts = true;
+
+ ConnectionPtr conn(contactManager->connection());
+ HandleIdentifierMap contactIds = reply.value();
+
+ if (!contactIds.isEmpty()) {
+ conn->lowlevel()->injectContactIds(contactIds);
+
+ //fake change event where all the contacts are added
+ contactListBlockedContactsChangedQueue.enqueue(
+ BlockedContactsChangedInfo(contactIds, HandleIdentifierMap(), true));
+ contactListChangesQueue.enqueue(
+ &ContactManager::Roster::processContactListBlockedContactsChanged);
+ processContactListChanges();
+ } else {
+ introspectContactList();
+ }
+}
+
+void ContactManager::Roster::onContactBlockingBlockedContactsChanged(
+ const HandleIdentifierMap &added,
+ const HandleIdentifierMap &removed)
+{
+ if (!gotContactBlockingInitialBlockedContacts) {
+ return;
+ }
+
+ ConnectionPtr conn(contactManager->connection());
+ conn->lowlevel()->injectContactIds(added);
+ conn->lowlevel()->injectContactIds(removed);
+
+ contactListBlockedContactsChangedQueue.enqueue(
+ BlockedContactsChangedInfo(added, removed));
+ contactListChangesQueue.enqueue(
+ &ContactManager::Roster::processContactListBlockedContactsChanged);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::gotContactListProperties(PendingOperation *op)
+{
+ if (op->isError()) {
+ // We may have been in state Failure and then Success, and FeatureRoster is already ready
+ if (introspectPendingOp) {
+ introspectPendingOp->setFinishedWithError(
+ op->errorName(), op->errorMessage());
+ introspectPendingOp = 0;
+ }
+ return;
+ }
+
+ debug() << "Got ContactList properties";
+
+ PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op);
+
+ QVariantMap props = pvm->result();
+
+ canChangeContactList = qdbus_cast<uint>(props[QLatin1String("CanChangeContactList")]);
+ contactListRequestUsesMessage = qdbus_cast<uint>(props[QLatin1String("RequestUsesMessage")]);
+
+ // only update the status if we did not get it from ContactListStateChanged
+ if (pendingContactListState == (uint) -1) {
+ uint state = qdbus_cast<uint>(props[QLatin1String("ContactListState")]);
+ onContactListStateChanged(state);
+ }
+}
+
+void ContactManager::Roster::gotContactListContacts(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<ContactAttributesMap> reply = *watcher;
+
+ if (watcher->isError()) {
+ warning() << "Failed introspecting ContactList contacts";
+
+ contactListState = ContactListStateFailure;
+ debug() << "Setting state to failure";
+ emit contactManager->stateChanged((Tp::ContactListState) contactListState);
+
+ // We may have been in state Failure and then Success, and FeatureRoster is already ready
+ if (introspectPendingOp) {
+ introspectPendingOp->setFinishedWithError(
+ reply.error());
+ introspectPendingOp = 0;
+ }
+ return;
+ }
+
+ debug() << "Got initial ContactList contacts";
+
+ gotContactListInitialContacts = true;
+
+ ConnectionPtr conn(contactManager->connection());
+ ContactAttributesMap attrsMap = reply.value();
+ ContactAttributesMap::const_iterator begin = attrsMap.constBegin();
+ ContactAttributesMap::const_iterator end = attrsMap.constEnd();
+ for (ContactAttributesMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ QVariantMap attrs = i.value();
+
+ ContactPtr contact = contactManager->ensureContact(ReferencedHandles(conn,
+ HandleTypeContact, UIntList() << bareHandle),
+ conn->contactFactory()->features(), attrs);
+ cachedAllKnownContacts.insert(contact);
+ contactListContacts.insert(contact);
+ }
+
+ if (contactManager->connection()->requestedFeatures().contains(
+ Connection::FeatureRosterGroups)) {
+ groupsSetSuccess = true;
+ }
+
+ // We may have been in state Failure and then Success, and FeatureRoster is already ready
+ // In any case, if we're going to reintrospect Groups, we only advance to state success once
+ // that is finished. We connect to the op finishing already here to catch all the failure finish
+ // cases as well.
+ if (introspectPendingOp) {
+ if (!groupsSetSuccess) {
+ // Will emit stateChanged() signal when the op is finished in idle
+ // callback. This is to ensure FeatureRoster (and Groups) is marked ready.
+ connect(introspectPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ }
+
+ introspectPendingOp->setFinished();
+ introspectPendingOp = 0;
+ } else if (!groupsSetSuccess) {
+ setStateSuccess();
+ } else {
+ // Verify that Groups is actually going to set the state
+ // As far as I can see, this will always be the case.
+ Q_ASSERT(groupsReintrospectionRequired);
+ }
+
+ if (groupsReintrospectionRequired) {
+ introspectGroups();
+ }
+}
+
+void ContactManager::Roster::setStateSuccess()
+{
+ if (contactManager->connection()->isValid()) {
+ debug() << "State is now success";
+ contactListState = ContactListStateSuccess;
+ emit contactManager->stateChanged((Tp::ContactListState) contactListState);
+ }
+}
+
+void ContactManager::Roster::onContactListStateChanged(uint state)
+{
+ if (pendingContactListState == state) {
+ // ignore redundant state changes
+ return;
+ }
+
+ pendingContactListState = state;
+
+ if (state == ContactListStateSuccess) {
+ introspectContactListContacts();
+ return;
+ }
+
+ contactListState = state;
+
+ if (state == ContactListStateFailure) {
+ debug() << "State changed to failure, finishing roster introspection";
+ }
+
+ emit contactManager->stateChanged((Tp::ContactListState) state);
+
+ if (state == ContactListStateFailure) {
+ // Consider it done here as the state may go from Failure to Success afterwards, in which
+ // case the contacts will appear.
+ Q_ASSERT(introspectPendingOp);
+ introspectPendingOp->setFinished();
+ introspectPendingOp = 0;
+ }
+}
+
+void ContactManager::Roster::onContactListContactsChangedWithId(const Tp::ContactSubscriptionMap &changes,
+ const Tp::HandleIdentifierMap &ids, const Tp::HandleIdentifierMap &removals)
+{
+ debug() << "Got ContactList.ContactsChangedWithID with" << changes.size() <<
+ "changes and" << removals.size() << "removals";
+
+ gotContactListContactsChangedWithId = true;
+
+ if (!gotContactListInitialContacts) {
+ debug() << "Ignoring ContactList changes until initial contacts are retrieved";
+ return;
+ }
+
+ ConnectionPtr conn(contactManager->connection());
+ conn->lowlevel()->injectContactIds(ids);
+
+ contactListUpdatesQueue.enqueue(UpdateInfo(changes, ids, removals));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListUpdates);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes,
+ const Tp::UIntList &removals)
+{
+ if (gotContactListContactsChangedWithId) {
+ return;
+ }
+
+ debug() << "Got ContactList.ContactsChanged with" << changes.size() <<
+ "changes and" << removals.size() << "removals";
+
+ if (!gotContactListInitialContacts) {
+ debug() << "Ignoring ContactList changes until initial contacts are retrieved";
+ return;
+ }
+
+ HandleIdentifierMap removalsMap;
+ foreach (uint handle, removals) {
+ removalsMap.insert(handle, QString());
+ }
+
+ contactListUpdatesQueue.enqueue(UpdateInfo(changes, HandleIdentifierMap(), removalsMap));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListUpdates);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListBlockedContactsConstructed(Tp::PendingOperation *op)
+{
+ BlockedContactsChangedInfo info = contactListBlockedContactsChangedQueue.dequeue();
+
+ if (op->isError()) {
+ if (info.continueIntrospectionWhenFinished) {
+ introspectContactList();
+ }
+ processingContactListChanges = false;
+ processContactListChanges();
+ return;
+ }
+
+ Contacts newBlockedContacts;
+ Contacts unblockedContacts;
+
+ HandleIdentifierMap::const_iterator begin = info.added.constBegin();
+ HandleIdentifierMap::const_iterator end = info.added.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to construct contact for handle" << bareHandle;
+ continue;
+ }
+
+ debug() << "Contact" << contact->id() << "is now blocked";
+ blockedContacts.insert(contact);
+ newBlockedContacts.insert(contact);
+ contact->setBlocked(true);
+ }
+
+ begin = info.removed.constBegin();
+ end = info.removed.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to construct contact for handle" << bareHandle;
+ continue;
+ }
+
+ debug() << "Contact" << contact->id() << "is now unblocked";
+ blockedContacts.remove(contact);
+ unblockedContacts.insert(contact);
+ contact->setBlocked(false);
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(newBlockedContacts, Contacts(),
+ Contacts(), unblockedContacts, Channel::GroupMemberChangeDetails());
+
+ if (info.continueIntrospectionWhenFinished) {
+ introspectContactList();
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListNewContactsConstructed(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ contactListUpdatesQueue.dequeue();
+ processingContactListChanges = false;
+ processContactListChanges();
+ return;
+ }
+
+ UpdateInfo info = contactListUpdatesQueue.dequeue();
+
+ Tp::Contacts added;
+ Tp::Contacts removed;
+
+ Tp::Contacts publishRequested;
+
+ ContactSubscriptionMap::const_iterator begin = info.changes.constBegin();
+ ContactSubscriptionMap::const_iterator end = info.changes.constEnd();
+ for (ContactSubscriptionMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ ContactSubscriptions subscriptions = i.value();
+
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to construct contact for handle" << bareHandle;
+ continue;
+ }
+
+ contactListContacts.insert(contact);
+ added << contact;
+
+ Contact::PresenceState oldContactPublishState = contact->publishState();
+ QString oldContactPublishStateMessage = contact->publishStateMessage();
+ contact->setSubscriptionState((SubscriptionState) subscriptions.subscribe);
+ contact->setPublishState((SubscriptionState) subscriptions.publish,
+ subscriptions.publishRequest);
+ if (subscriptions.publish == SubscriptionStateAsk &&
+ (oldContactPublishState != Contact::PresenceStateAsk ||
+ oldContactPublishStateMessage != contact->publishStateMessage())) {
+ Channel::GroupMemberChangeDetails publishRequestDetails;
+ QVariantMap detailsMap;
+ detailsMap.insert(QLatin1String("message"), subscriptions.publishRequest);
+ publishRequestDetails = Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap);
+ // FIXME (API/ABI break) remove both of these signals
+ emit contactManager->presencePublicationRequested(Contacts() << contact,
+ publishRequestDetails);
+ emit contactManager->presencePublicationRequested(Contacts() << contact,
+ subscriptions.publishRequest);
+
+ publishRequested.insert(contact);
+ }
+ }
+
+ if (!publishRequested.empty()) {
+ emit contactManager->presencePublicationRequested(publishRequested);
+ }
+
+ foreach (uint bareHandle, info.removals.keys()) {
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "Unable to find removed contact with handle" << bareHandle;
+ continue;
+ }
+
+ if (!contactListContacts.contains(contact)) {
+ warning() << "Contact" << contact->id() << "removed from ContactList "
+ "but it wasn't present, ignoring.";
+ continue;
+ }
+
+ contactListContacts.remove(contact);
+ removed << contact;
+ }
+
+ computeKnownContactsChanges(added, Contacts(), Contacts(),
+ removed, Channel::GroupMemberChangeDetails());
+
+ foreach (const Tp::ContactPtr &contact, removed) {
+ contact->setSubscriptionState(SubscriptionStateNo);
+ contact->setPublishState(SubscriptionStateNo);
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupsChanged(const Tp::UIntList &contacts,
+ const QStringList &added, const QStringList &removed)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupsUpdatesQueue.enqueue(GroupsUpdateInfo(contacts,
+ added, removed));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsUpdates);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupsCreated(const QStringList &names)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupsCreatedQueue.enqueue(names);
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsCreated);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupRenamed(const QString &oldName, const QString &newName)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupRenamedQueue.enqueue(GroupRenamedInfo(oldName, newName));
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupRenamed);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onContactListGroupsRemoved(const QStringList &names)
+{
+ Q_ASSERT(usingFallbackContactList == false);
+
+ if (!contactListGroupPropertiesReceived) {
+ return;
+ }
+
+ contactListGroupsRemovedQueue.enqueue(names);
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsRemoved);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onModifyFinished(Tp::PendingOperation *op)
+{
+ ModifyFinishOp *returned = returnedModifyOps.take(op);
+
+ // Finished twice, or we didn't add the returned op at all?
+ Q_ASSERT(returned);
+
+ if (op->isError()) {
+ returned->setError(op->errorName(), op->errorMessage());
+ }
+
+ modifyFinishQueue.enqueue(returned);
+ contactListChangesQueue.enqueue(&ContactManager::Roster::processFinishedModify);
+ processContactListChanges();
+}
+
+void ContactManager::Roster::gotContactListChannelHandle(PendingOperation *op)
+{
+ PendingHandles *ph = qobject_cast<PendingHandles*>(op);
+ Q_ASSERT(ph->namesRequested().size() == 1);
+ QString channelId = ph->namesRequested().first();
+ uint type = ChannelInfo::typeForIdentifier(channelId);
+
+ if (op->isError()) {
+ // let's not fail, because the contact lists are not supported
+ debug() << "Unable to retrieve handle for" << channelId << "channel, ignoring";
+ contactListChannels.remove(type);
+ onContactListChannelReady();
+ return;
+ }
+
+ if (ph->invalidNames().size() == 1) {
+ // let's not fail, because the contact lists are not supported
+ debug() << "Unable to retrieve handle for" << channelId << "channel, ignoring";
+ contactListChannels.remove(type);
+ onContactListChannelReady();
+ return;
+ }
+
+ Q_ASSERT(ph->handles().size() == 1);
+
+ debug() << "Got handle for" << channelId << "channel";
+
+ if (!usingFallbackContactList) {
+ Q_ASSERT(type == ChannelInfo::TypeDeny);
+ } else {
+ Q_ASSERT(type != (uint) -1 && type < ChannelInfo::LastType);
+ }
+
+ ReferencedHandles handle = ph->handles();
+ contactListChannels[type].handle = handle;
+
+ debug() << "Requesting channel for" << channelId << "channel";
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) HandleTypeList);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ handle[0]);
+ ConnectionPtr conn(contactManager->connection());
+ /* Request the channel passing INT_MAX as timeout (meaning no timeout), as
+ * some CMs may take too long to return from ensureChannel when still
+ * loading the contact list */
+ connect(conn->lowlevel()->ensureChannel(request, INT_MAX),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListChannel(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::gotContactListChannel(PendingOperation *op)
+{
+ if (op->isError()) {
+ debug() << "Unable to create channel, ignoring";
+ onContactListChannelReady();
+ return;
+ }
+
+ PendingChannel *pc = qobject_cast<PendingChannel*>(op);
+ ChannelPtr channel = pc->channel();
+ Q_ASSERT(channel);
+ uint handle = pc->targetHandle();
+ Q_ASSERT(handle);
+
+ for (uint i = 0; i < ChannelInfo::LastType; ++i) {
+ if (contactListChannels.contains(i) &&
+ contactListChannels[i].handle.size() > 0 &&
+ contactListChannels[i].handle[0] == handle) {
+ Q_ASSERT(!contactListChannels[i].channel);
+ contactListChannels[i].channel = channel;
+
+ // deref connection refcount here as connection will keep a ref to channel and we don't
+ // want a contact list channel keeping a ref of connection, otherwise connection will
+ // leak, thus the channels.
+ channel->connection()->deref();
+
+ connect(channel->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListChannelReady()));
+ }
+ }
+}
+
+void ContactManager::Roster::onContactListChannelReady()
+{
+ if (!usingFallbackContactList) {
+ setContactListChannelsReady();
+
+ updateContactsBlockState();
+
+ if (denyChannel) {
+ cachedAllKnownContacts.unite(denyChannel->groupContacts());
+ }
+
+ introspectContactList();
+ } else if (++contactListChannelsReady == ChannelInfo::LastType) {
+ if (contactListChannels.isEmpty()) {
+ contactListState = ContactListStateFailure;
+ debug() << "State is failure, roster not supported";
+ emit contactManager->stateChanged((Tp::ContactListState) contactListState);
+
+ Q_ASSERT(introspectPendingOp);
+ introspectPendingOp->setFinishedWithError(TP_QT4_ERROR_NOT_IMPLEMENTED,
+ QLatin1String("Roster not supported"));
+ introspectPendingOp = 0;
+ return;
+ }
+
+ setContactListChannelsReady();
+
+ updateContactsBlockState();
+
+ // Refresh the cache for the current known contacts
+ foreach (const ChannelInfo &contactListChannel, contactListChannels) {
+ ChannelPtr channel = contactListChannel.channel;
+ if (!channel) {
+ continue;
+ }
+ cachedAllKnownContacts.unite(channel->groupContacts());
+ cachedAllKnownContacts.unite(channel->groupLocalPendingContacts());
+ cachedAllKnownContacts.unite(channel->groupRemotePendingContacts());
+ }
+
+ updateContactsPresenceState();
+
+ Q_ASSERT(introspectPendingOp);
+
+ if (!contactManager->connection()->requestedFeatures().contains(
+ Connection::FeatureRosterGroups)) {
+ // Will emit stateChanged() signal when the op is finished in idle
+ // callback. This is to ensure FeatureRoster is marked ready.
+ connect(introspectPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ } else {
+ Q_ASSERT(!groupsSetSuccess);
+ groupsSetSuccess = true;
+ }
+
+ introspectPendingOp->setFinished();
+ introspectPendingOp = 0;
+ }
+}
+
+void ContactManager::Roster::gotContactListGroupsProperties(PendingOperation *op)
+{
+ Q_ASSERT(introspectGroupsPendingOp != NULL);
+
+ if (groupsSetSuccess) {
+ // Connect here, so we catch the following and the other failure cases
+ connect(introspectGroupsPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ }
+
+ if (op->isError()) {
+ warning() << "Getting contact list groups properties failed:" << op->errorName() << '-'
+ << op->errorMessage();
+
+ introspectGroupsPendingOp->setFinishedWithError(
+ op->errorName(), op->errorMessage());
+ introspectGroupsPendingOp = 0;
+
+ return;
+ }
+
+ debug() << "Got contact list groups properties";
+ PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op);
+
+ QVariantMap props = pvm->result();
+
+ cachedAllKnownGroups = qdbus_cast<QStringList>(props[QLatin1String("Groups")]).toSet();
+ contactListGroupPropertiesReceived = true;
+
+ processingContactListChanges = true;
+ PendingContacts *pc = contactManager->upgradeContacts(
+ contactManager->allKnownContacts().toList(),
+ Contact::FeatureRosterGroups);
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListContactsUpgraded(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::onContactListContactsUpgraded(PendingOperation *op)
+{
+ Q_ASSERT(processingContactListChanges);
+ processingContactListChanges = false;
+
+ Q_ASSERT(introspectGroupsPendingOp != NULL);
+
+ if (op->isError()) {
+ warning() << "Upgrading contacts with group membership failed:" << op->errorName() << '-'
+ << op->errorMessage();
+
+ introspectGroupsPendingOp->setFinishedWithError(
+ op->errorName(), op->errorMessage());
+ introspectGroupsPendingOp = 0;
+ processContactListChanges();
+ return;
+ }
+
+ introspectGroupsPendingOp->setFinished();
+ introspectGroupsPendingOp = 0;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::onNewChannels(const Tp::ChannelDetailsList &channelDetailsList)
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ QString channelType;
+ uint handleType;
+ foreach (const ChannelDetails &channelDetails, channelDetailsList) {
+ channelType = channelDetails.properties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ if (channelType != QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST)) {
+ continue;
+ }
+
+ handleType = channelDetails.properties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
+ if (handleType != Tp::HandleTypeGroup) {
+ continue;
+ }
+
+ ++featureContactListGroupsTodo; // decremented in onContactListGroupChannelReady
+ ChannelPtr channel = Channel::create(conn,
+ channelDetails.channel.path(), channelDetails.properties);
+ pendingContactListGroupChannels.append(channel);
+
+ // deref connection refcount here as connection will keep a ref to channel and we don't
+ // want a contact list group channel keeping a ref of connection, otherwise connection will
+ // leak, thus the channels.
+ channel->connection()->deref();
+
+ connect(channel->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListGroupChannelReady(Tp::PendingOperation*)));
+ }
+}
+
+void ContactManager::Roster::onContactListGroupChannelReady(PendingOperation *op)
+{
+ --featureContactListGroupsTodo; // incremented in onNewChannels
+
+ ConnectionPtr conn(contactManager->connection());
+
+ if (introspectGroupsPendingOp) {
+ checkContactListGroupsReady();
+ } else {
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ ChannelPtr channel = ChannelPtr::qObjectCast(pr->proxy());
+ QString id = addContactListGroupChannel(channel);
+ emit contactManager->groupAdded(id);
+ pendingContactListGroupChannels.removeOne(channel);
+ }
+}
+
+void ContactManager::Roster::gotChannels(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got channels";
+ onNewChannels(qdbus_cast<ChannelDetailsList>(reply.value()));
+ } else {
+ warning().nospace() << "Getting channels failed with " <<
+ reply.error().name() << ":" << reply.error().message();
+ }
+
+ --featureContactListGroupsTodo; // incremented in introspectRosterGroups
+
+ checkContactListGroupsReady();
+
+ watcher->deleteLater();
+}
+
+void ContactManager::Roster::onStoredChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ warning() << "Found local pending contacts on stored list";
+ }
+
+ if (!groupRemotePendingMembersAdded.isEmpty()) {
+ warning() << "Found remote pending contacts on stored list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "on stored list";
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from stored list";
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded,
+ groupLocalPendingMembersAdded, groupRemotePendingMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onSubscribeChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ warning() << "Found local pending contacts on subscribe list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "on subscribe list";
+ contact->setSubscriptionState(SubscriptionStateYes);
+ }
+
+ foreach (ContactPtr contact, groupRemotePendingMembersAdded) {
+ debug() << "Contact" << contact->id() << "added to subscribe list";
+ contact->setSubscriptionState(SubscriptionStateAsk);
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from subscribe list";
+ contact->setSubscriptionState(SubscriptionStateNo);
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded,
+ groupLocalPendingMembersAdded, groupRemotePendingMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onPublishChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupRemotePendingMembersAdded.isEmpty()) {
+ warning() << "Found remote pending contacts on publish list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "on publish list";
+ contact->setPublishState(SubscriptionStateYes);
+ }
+
+ foreach (ContactPtr contact, groupLocalPendingMembersAdded) {
+ debug() << "Contact" << contact->id() << "added to publish list";
+ contact->setPublishState(SubscriptionStateAsk, details.message());
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from publish list";
+ contact->setPublishState(SubscriptionStateNo);
+ }
+
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded);
+ // FIXME (API/ABI break) remove both of these signals
+ emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded,
+ details);
+ emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded,
+ details.message());
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded,
+ groupLocalPendingMembersAdded, groupRemotePendingMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onDenyChannelMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ if (!groupLocalPendingMembersAdded.isEmpty()) {
+ warning() << "Found local pending contacts on deny list";
+ }
+
+ if (!groupRemotePendingMembersAdded.isEmpty()) {
+ warning() << "Found remote pending contacts on deny list";
+ }
+
+ foreach (ContactPtr contact, groupMembersAdded) {
+ debug() << "Contact" << contact->id() << "added to deny list";
+ contact->setBlocked(true);
+ }
+
+ foreach (ContactPtr contact, groupMembersRemoved) {
+ debug() << "Contact" << contact->id() << "removed from deny list";
+ contact->setBlocked(false);
+ }
+
+ // Perform the needed computation for allKnownContactsChanged
+ computeKnownContactsChanges(groupMembersAdded, Contacts(),
+ Contacts(), groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onContactListGroupMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ ChannelPtr contactListGroupChannel = ChannelPtr(
+ qobject_cast<Channel*>(sender()));
+ QString id = contactListGroupChannel->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
+
+ foreach (const ContactPtr &contact, groupMembersAdded) {
+ contact->setAddedToGroup(id);
+ }
+ foreach (const ContactPtr &contact, groupMembersRemoved) {
+ contact->setRemovedFromGroup(id);
+ }
+
+ emit contactManager->groupMembersChanged(id, groupMembersAdded,
+ groupMembersRemoved, details);
+}
+
+void ContactManager::Roster::onContactListGroupRemoved(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ Q_UNUSED(errorName);
+ Q_UNUSED(errorMessage);
+
+ // Is it correct to assume that if an user-defined contact list
+ // gets invalidated it means it was removed? Spec states that if a
+ // user-defined contact list gets closed it was removed, and Channel
+ // invalidates itself when it gets closed.
+ ChannelPtr contactListGroupChannel = ChannelPtr(qobject_cast<Channel*>(proxy));
+ QString id = contactListGroupChannel->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
+ contactListGroupChannels.remove(id);
+ removedContactListGroupChannels.append(contactListGroupChannel);
+ disconnect(contactListGroupChannel.data(), 0, 0, 0);
+ emit contactManager->groupRemoved(id);
+}
+
+void ContactManager::Roster::introspectContactBlocking()
+{
+ debug() << "Requesting ContactBlockingCapabilities property";
+
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactBlockingInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactBlockingInterface>();
+
+ PendingVariant *pv = iface->requestPropertyContactBlockingCapabilities();
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactBlockingCapabilities(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::introspectContactBlockingBlockedContacts()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactBlockingInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactBlockingInterface>();
+
+ Q_ASSERT(iface);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ iface->RequestBlockedContacts(), contactManager);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotContactBlockingBlockedContacts(QDBusPendingCallWatcher*)));
+
+ connect(iface,
+ SIGNAL(BlockedContactsChanged(Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)),
+ SLOT(onContactBlockingBlockedContactsChanged(Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)));
+}
+
+void ContactManager::Roster::introspectContactList()
+{
+ debug() << "Requesting ContactList properties";
+
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+
+ connect(iface,
+ SIGNAL(ContactListStateChanged(uint)),
+ SLOT(onContactListStateChanged(uint)));
+ connect(iface,
+ SIGNAL(ContactsChangedWithID(Tp::ContactSubscriptionMap,Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)),
+ SLOT(onContactListContactsChangedWithId(Tp::ContactSubscriptionMap,Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)));
+ connect(iface,
+ SIGNAL(ContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList)),
+ SLOT(onContactListContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList)));
+
+ PendingVariantMap *pvm = iface->requestAllProperties();
+ connect(pvm,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContactListProperties(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::introspectContactListContacts()
+{
+ ConnectionPtr conn(contactManager->connection());
+
+ Client::ConnectionInterfaceContactListInterface *iface =
+ conn->interface<Client::ConnectionInterfaceContactListInterface>();
+
+ Features features(conn->contactFactory()->features());
+ Features supportedFeatures(contactManager->supportedFeatures());
+ QSet<QString> interfaces;
+ foreach (const Feature &feature, features) {
+ contactManager->ensureTracking(feature);
+
+ if (supportedFeatures.contains(feature)) {
+ // Only query interfaces which are reported as supported to not get an error
+ interfaces.insert(contactManager->featureToInterface(feature));
+ }
+ }
+ interfaces.insert(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ iface->GetContactListAttributes(interfaces.toList(), true), contactManager);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotContactListContacts(QDBusPendingCallWatcher*)));
+}
+
+void ContactManager::Roster::processContactListChanges()
+{
+ if (processingContactListChanges || contactListChangesQueue.isEmpty()) {
+ return;
+ }
+
+ processingContactListChanges = true;
+ (this->*(contactListChangesQueue.dequeue()))();
+}
+
+void ContactManager::Roster::processContactListBlockedContactsChanged()
+{
+ BlockedContactsChangedInfo info = contactListBlockedContactsChangedQueue.head();
+
+ UIntList contacts;
+ HandleIdentifierMap::const_iterator begin = info.added.constBegin();
+ HandleIdentifierMap::const_iterator end = info.added.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ contacts << bareHandle;
+ }
+
+ begin = info.removed.constBegin();
+ end = info.removed.constEnd();
+ for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ contacts << bareHandle;
+ }
+
+ Features features;
+ if (contactManager->connection()->isReady(Connection::FeatureRosterGroups)) {
+ features << Contact::FeatureRosterGroups;
+ }
+ PendingContacts *pc = contactManager->contactsForHandles(contacts, features);
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListBlockedContactsConstructed(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::processContactListUpdates()
+{
+ UpdateInfo info = contactListUpdatesQueue.head();
+
+ // construct Contact objects for all contacts in added to the contact list
+ UIntList contacts;
+ ContactSubscriptionMap::const_iterator begin = info.changes.constBegin();
+ ContactSubscriptionMap::const_iterator end = info.changes.constEnd();
+ for (ContactSubscriptionMap::const_iterator i = begin; i != end; ++i) {
+ uint bareHandle = i.key();
+ contacts << bareHandle;
+ }
+
+ Features features;
+ if (contactManager->connection()->isReady(Connection::FeatureRosterGroups)) {
+ features << Contact::FeatureRosterGroups;
+ }
+ PendingContacts *pc = contactManager->contactsForHandles(contacts, features);
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactListNewContactsConstructed(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::processContactListGroupsUpdates()
+{
+ GroupsUpdateInfo info = contactListGroupsUpdatesQueue.dequeue();
+
+ foreach (const QString &group, info.groupsAdded) {
+ Contacts contacts;
+ foreach (uint bareHandle, info.contacts) {
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "contact with handle" << bareHandle << "was added to a group but "
+ "never added to the contact list, ignoring";
+ continue;
+ }
+ contacts << contact;
+ contact->setAddedToGroup(group);
+ }
+
+ emit contactManager->groupMembersChanged(group, contacts,
+ Contacts(), Channel::GroupMemberChangeDetails());
+ }
+
+ foreach (const QString &group, info.groupsRemoved) {
+ Contacts contacts;
+ foreach (uint bareHandle, info.contacts) {
+ ContactPtr contact = contactManager->lookupContactByHandle(bareHandle);
+ if (!contact) {
+ warning() << "contact with handle" << bareHandle << "was removed from a group but "
+ "never added to the contact list, ignoring";
+ continue;
+ }
+ contacts << contact;
+ contact->setRemovedFromGroup(group);
+ }
+
+ emit contactManager->groupMembersChanged(group, Contacts(),
+ contacts, Channel::GroupMemberChangeDetails());
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processContactListGroupsCreated()
+{
+ QStringList names = contactListGroupsCreatedQueue.dequeue();
+ foreach (const QString &name, names) {
+ cachedAllKnownGroups.insert(name);
+ emit contactManager->groupAdded(name);
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processContactListGroupRenamed()
+{
+ GroupRenamedInfo info = contactListGroupRenamedQueue.dequeue();
+ cachedAllKnownGroups.remove(info.oldName);
+ cachedAllKnownGroups.insert(info.newName);
+ emit contactManager->groupRenamed(info.oldName, info.newName);
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processContactListGroupsRemoved()
+{
+ QStringList names = contactListGroupsRemovedQueue.dequeue();
+ foreach (const QString &name, names) {
+ cachedAllKnownGroups.remove(name);
+ emit contactManager->groupRemoved(name);
+ }
+
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::processFinishedModify()
+{
+ ModifyFinishOp *op = modifyFinishQueue.dequeue();
+ // Only continue processing changes (and thus, emitting change signals) when the op has signaled
+ // finish (it'll only do this after we've returned to the mainloop)
+ connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onModifyFinishSignaled()));
+ op->finish();
+}
+
+PendingOperation *ContactManager::Roster::queuedFinishVoid(const QDBusPendingCall &call)
+{
+ PendingOperation *actual = new PendingVoid(call, contactManager->connection());
+ connect(actual,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onModifyFinished(Tp::PendingOperation*)));
+ ModifyFinishOp *toReturn = new ModifyFinishOp(contactManager->connection());
+ returnedModifyOps.insert(actual, toReturn);
+ return toReturn;
+}
+
+void ContactManager::Roster::onModifyFinishSignaled()
+{
+ processingContactListChanges = false;
+ processContactListChanges();
+}
+
+void ContactManager::Roster::setContactListChannelsReady()
+{
+ if (!usingFallbackContactList) {
+ Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypeSubscribe));
+ Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypePublish));
+ Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypeStored));
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypeSubscribe)) {
+ subscribeChannel = contactListChannels[ChannelInfo::TypeSubscribe].channel;
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypePublish)) {
+ publishChannel = contactListChannels[ChannelInfo::TypePublish].channel;
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypeStored)) {
+ storedChannel = contactListChannels[ChannelInfo::TypeStored].channel;
+ }
+
+ if (contactListChannels.contains(ChannelInfo::TypeDeny)) {
+ denyChannel = contactListChannels[ChannelInfo::TypeDeny].channel;
+ }
+
+ uint type;
+ ChannelPtr channel;
+ const char *method;
+ for (QMap<uint, ChannelInfo>::const_iterator i = contactListChannels.constBegin();
+ i != contactListChannels.constEnd(); ++i) {
+ type = i.key();
+ channel = i.value().channel;
+ if (!channel) {
+ continue;
+ }
+
+ if (type == ChannelInfo::TypeStored) {
+ method = SLOT(onStoredChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else if (type == ChannelInfo::TypeSubscribe) {
+ method = SLOT(onSubscribeChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else if (type == ChannelInfo::TypePublish) {
+ method = SLOT(onPublishChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else if (type == ChannelInfo::TypeDeny) {
+ method = SLOT(onDenyChannelMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails));
+ } else {
+ continue;
+ }
+
+ connect(channel.data(),
+ SIGNAL(groupMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ method);
+ }
+}
+
+void ContactManager::Roster::updateContactsBlockState()
+{
+ Q_ASSERT(!hasContactBlockingInterface);
+
+ if (!denyChannel) {
+ return;
+ }
+
+ Contacts denyContacts = denyChannel->groupContacts();
+ foreach (const ContactPtr &contact, denyContacts) {
+ contact->setBlocked(true);
+ }
+}
+
+void ContactManager::Roster::updateContactsPresenceState()
+{
+ if (!subscribeChannel && !publishChannel) {
+ return;
+ }
+
+ Contacts subscribeContacts;
+ Contacts subscribeContactsRP;
+
+ if (subscribeChannel) {
+ subscribeContacts = subscribeChannel->groupContacts();
+ subscribeContactsRP = subscribeChannel->groupRemotePendingContacts();
+ }
+
+ Contacts publishContacts;
+ Contacts publishContactsLP;
+ if (publishChannel) {
+ publishContacts = publishChannel->groupContacts();
+ publishContactsLP = publishChannel->groupLocalPendingContacts();
+ }
+
+ Contacts contacts = cachedAllKnownContacts;
+ foreach (ContactPtr contact, contacts) {
+ if (subscribeChannel) {
+ // not in "subscribe" -> No, in "subscribe" lp -> Ask, in "subscribe" current -> Yes
+ if (subscribeContacts.contains(contact)) {
+ contact->setSubscriptionState(SubscriptionStateYes);
+ } else if (subscribeContactsRP.contains(contact)) {
+ contact->setSubscriptionState(SubscriptionStateAsk);
+ } else {
+ contact->setSubscriptionState(SubscriptionStateNo);
+ }
+ }
+
+ if (publishChannel) {
+ // not in "publish" -> No, in "subscribe" rp -> Ask, in "publish" current -> Yes
+ if (publishContacts.contains(contact)) {
+ contact->setPublishState(SubscriptionStateYes);
+ } else if (publishContactsLP.contains(contact)) {
+ contact->setPublishState(SubscriptionStateAsk,
+ publishChannel->groupLocalPendingContactChangeInfo(contact).message());
+ } else {
+ contact->setPublishState(SubscriptionStateNo);
+ }
+ }
+ }
+}
+
+void ContactManager::Roster::computeKnownContactsChanges(const Tp::Contacts& added,
+ const Tp::Contacts& pendingAdded, const Tp::Contacts& remotePendingAdded,
+ const Tp::Contacts& removed, const Channel::GroupMemberChangeDetails &details)
+{
+ // First of all, compute the real additions/removals based upon our cache
+ Tp::Contacts realAdded;
+ realAdded.unite(added);
+ realAdded.unite(pendingAdded);
+ realAdded.unite(remotePendingAdded);
+ realAdded.subtract(cachedAllKnownContacts);
+ Tp::Contacts realRemoved = removed;
+ realRemoved.intersect(cachedAllKnownContacts);
+
+ // Check if realRemoved have been _really_ removed from all lists
+ foreach (const ChannelInfo &contactListChannel, contactListChannels) {
+ ChannelPtr channel = contactListChannel.channel;
+ if (!channel) {
+ continue;
+ }
+ realRemoved.subtract(channel->groupContacts());
+ realRemoved.subtract(channel->groupLocalPendingContacts());
+ realRemoved.subtract(channel->groupRemotePendingContacts());
+ }
+
+ // ...and from the Conn.I.ContactList / Conn.I.ContactBlocking contacts
+ realRemoved.subtract(contactListContacts);
+ realRemoved.subtract(blockedContacts);
+
+ // Are there any real changes?
+ if (!realAdded.isEmpty() || !realRemoved.isEmpty()) {
+ // Yes, update our "cache" and emit the signal
+ cachedAllKnownContacts.unite(realAdded);
+ cachedAllKnownContacts.subtract(realRemoved);
+ emit contactManager->allKnownContactsChanged(realAdded, realRemoved, details);
+ }
+}
+
+void ContactManager::Roster::checkContactListGroupsReady()
+{
+ if (featureContactListGroupsTodo != 0) {
+ return;
+ }
+
+ if (groupsSetSuccess) {
+ Q_ASSERT(contactManager->state() != ContactListStateSuccess);
+
+ if (introspectGroupsPendingOp) {
+ // Will emit stateChanged() signal when the op is finished in idle
+ // callback. This is to ensure FeatureRosterGroups is marked ready.
+ connect(introspectGroupsPendingOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(setStateSuccess()));
+ } else {
+ setStateSuccess();
+ }
+
+ groupsSetSuccess = false;
+ }
+
+ setContactListGroupChannelsReady();
+ if (introspectGroupsPendingOp) {
+ introspectGroupsPendingOp->setFinished();
+ introspectGroupsPendingOp = 0;
+ }
+ pendingContactListGroupChannels.clear();
+}
+
+void ContactManager::Roster::setContactListGroupChannelsReady()
+{
+ Q_ASSERT(usingFallbackContactList == true);
+ Q_ASSERT(contactListGroupChannels.isEmpty());
+
+ foreach (const ChannelPtr &contactListGroupChannel, pendingContactListGroupChannels) {
+ addContactListGroupChannel(contactListGroupChannel);
+ }
+}
+
+QString ContactManager::Roster::addContactListGroupChannel(const ChannelPtr &contactListGroupChannel)
+{
+ QString id = contactListGroupChannel->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
+ contactListGroupChannels.insert(id, contactListGroupChannel);
+ connect(contactListGroupChannel.data(),
+ SIGNAL(groupMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(onContactListGroupMembersChanged(
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)));
+ connect(contactListGroupChannel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onContactListGroupRemoved(Tp::DBusProxy*,QString,QString)));
+
+ foreach (const ContactPtr &contact, contactListGroupChannel->groupContacts()) {
+ contact->setAddedToGroup(id);
+ }
+
+ return id;
+}
+
+/**** ContactManager::Roster::ChannelInfo ****/
+QString ContactManager::Roster::ChannelInfo::identifierForType(Type type)
+{
+ static QString identifiers[LastType] = {
+ QLatin1String("subscribe"),
+ QLatin1String("publish"),
+ QLatin1String("stored"),
+ QLatin1String("deny"),
+ };
+ return identifiers[type];
+}
+
+uint ContactManager::Roster::ChannelInfo::typeForIdentifier(const QString &identifier)
+{
+ static QHash<QString, uint> types;
+ if (types.isEmpty()) {
+ types.insert(QLatin1String("subscribe"), TypeSubscribe);
+ types.insert(QLatin1String("publish"), TypePublish);
+ types.insert(QLatin1String("stored"), TypeStored);
+ types.insert(QLatin1String("deny"), TypeDeny);
+ }
+ if (types.contains(identifier)) {
+ return types[identifier];
+ }
+ return (uint) -1;
+}
+
+/**** ContactManager::Roster::ModifyFinishOp ****/
+ContactManager::Roster::ModifyFinishOp::ModifyFinishOp(const ConnectionPtr &conn)
+ : PendingOperation(conn)
+{
+}
+
+void ContactManager::Roster::ModifyFinishOp::setError(const QString &errorName, const QString &errorMessage)
+{
+ Q_ASSERT(this->errorName.isEmpty());
+ Q_ASSERT(this->errorMessage.isEmpty());
+
+ Q_ASSERT(!errorName.isEmpty());
+
+ this->errorName = errorName;
+ this->errorMessage = errorMessage;
+}
+
+void ContactManager::Roster::ModifyFinishOp::finish()
+{
+ if (errorName.isEmpty()) {
+ setFinished();
+ } else {
+ setFinishedWithError(errorName, errorMessage);
+ }
+}
+
+/**** ContactManager::Roster::RemoveGroupOp ****/
+ContactManager::Roster::RemoveGroupOp::RemoveGroupOp(const ChannelPtr &channel)
+ : PendingOperation(channel)
+{
+ Contacts contacts = channel->groupContacts();
+ if (!contacts.isEmpty()) {
+ connect(channel->groupRemoveContacts(contacts.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactsRemoved(Tp::PendingOperation*)));
+ } else {
+ connect(channel->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelClosed(Tp::PendingOperation*)));
+ }
+}
+
+void ContactManager::Roster::RemoveGroupOp::onContactsRemoved(PendingOperation *op)
+{
+ if (op->isError()) {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ // Let's ignore possible errors and try to remove the group
+ ChannelPtr channel = ChannelPtr(qobject_cast<Channel*>((Channel *) _object().data()));
+ connect(channel->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelClosed(Tp::PendingOperation*)));
+}
+
+void ContactManager::Roster::RemoveGroupOp::onChannelClosed(PendingOperation *op)
+{
+ if (!op->isError()) {
+ setFinished();
+ } else {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/contact-manager.cpp b/qt4/TelepathyQt4/contact-manager.cpp
new file mode 100644
index 000000000..89041c353
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-manager.cpp
@@ -0,0 +1,1592 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ContactManager>
+#include "TelepathyQt4/contact-manager-internal.h"
+
+#include "TelepathyQt4/_gen/contact-manager.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/AvatarData>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContactAttributes>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/Utils>
+
+#include <QMap>
+#include <QWeakPointer>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ContactManager::Private
+{
+ Private(ContactManager *parent, Connection *connection);
+ ~Private();
+
+ // avatar specific methods
+ bool buildAvatarFileName(QString token, bool createDir,
+ QString &avatarFileName, QString &mimeTypeFileName);
+
+ ContactManager *parent;
+ QWeakPointer<Connection> connection;
+ ContactManager::Roster *roster;
+
+ QMap<uint, QWeakPointer<Contact> > contacts;
+
+ QMap<Feature, bool> tracking;
+ Features supportedFeatures;
+
+ // avatar
+ QSet<ContactPtr> requestAvatarsQueue;
+ bool requestAvatarsIdle;
+
+ // contact info
+ PendingRefreshContactInfo *refreshInfoOp;
+};
+
+ContactManager::Private::Private(ContactManager *parent, Connection *connection)
+ : parent(parent),
+ connection(connection),
+ roster(new ContactManager::Roster(parent)),
+ requestAvatarsIdle(false),
+ refreshInfoOp(0)
+{
+}
+
+ContactManager::Private::~Private()
+{
+ delete refreshInfoOp;
+ delete roster;
+}
+
+bool ContactManager::Private::buildAvatarFileName(QString token, bool createDir,
+ QString &avatarFileName, QString &mimeTypeFileName)
+{
+ QString cacheDir = QString(QLatin1String(qgetenv("XDG_CACHE_HOME")));
+ if (cacheDir.isEmpty()) {
+ cacheDir = QString(QLatin1String("%1/.cache")).arg(QLatin1String(qgetenv("HOME")));
+ }
+
+ ConnectionPtr conn(parent->connection());
+ QString path = QString(QLatin1String("%1/telepathy/avatars/%2/%3")).
+ arg(cacheDir).arg(conn->cmName()).arg(conn->protocolName());
+
+ if (createDir && !QDir().mkpath(path)) {
+ return false;
+ }
+
+ avatarFileName = QString(QLatin1String("%1/%2")).arg(path).arg(escapeAsIdentifier(token));
+ mimeTypeFileName = QString(QLatin1String("%1.mime")).arg(avatarFileName);
+
+ return true;
+}
+
+ContactManager::PendingRefreshContactInfo::PendingRefreshContactInfo(const ConnectionPtr &conn)
+ : PendingOperation(conn),
+ mConn(conn)
+{
+}
+
+ContactManager::PendingRefreshContactInfo::~PendingRefreshContactInfo()
+{
+}
+
+void ContactManager::PendingRefreshContactInfo::addContact(Contact *contact)
+{
+ mToRequest.insert(contact->handle()[0]);
+}
+
+void ContactManager::PendingRefreshContactInfo::refreshInfo()
+{
+ Q_ASSERT(!mToRequest.isEmpty());
+
+ if (!mConn->isValid()) {
+ setFinishedWithError(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("Connection is invalid"));
+ return;
+ }
+
+ if (!mConn->hasInterface(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_INFO)) {
+ setFinishedWithError(TP_QT4_ERROR_NOT_IMPLEMENTED,
+ QLatin1String("Connection does not support ContactInfo interface"));
+ return;
+ }
+
+ debug() << "Calling ContactInfo.RefreshContactInfo for" << mToRequest.size() << "handles";
+ Client::ConnectionInterfaceContactInfoInterface *contactInfoInterface =
+ mConn->interface<Client::ConnectionInterfaceContactInfoInterface>();
+ Q_ASSERT(contactInfoInterface);
+ PendingVoid *nested = new PendingVoid(
+ contactInfoInterface->RefreshContactInfo(mToRequest.toList()),
+ mConn);
+ connect(nested,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onRefreshInfoFinished(Tp::PendingOperation*)));
+}
+
+void ContactManager::PendingRefreshContactInfo::onRefreshInfoFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "ContactInfo.RefreshContactInfo failed with" <<
+ op->errorName() << "-" << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ } else {
+ debug() << "Got reply to ContactInfo.RefreshContactInfo";
+ setFinished();
+ }
+}
+
+/**
+ * \class ContactManager
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/contact-manager.h <TelepathyQt4/ContactManager>
+ *
+ * \brief The ContactManager class is responsible for managing contacts.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Construct a new ContactManager object.
+ *
+ * \param connection The connection owning this ContactManager.
+ */
+ContactManager::ContactManager(Connection *connection)
+ : Object(),
+ mPriv(new Private(this, connection))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ContactManager::~ContactManager()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection owning this ContactManager.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr ContactManager::connection() const
+{
+ return ConnectionPtr(mPriv->connection);
+}
+
+/**
+ * Return the features that are expected to work on contacts on this ContactManager connection.
+ *
+ * This method requires Connection::FeatureCore to be ready.
+ *
+ * \return The supported features as a set of Feature objects.
+ */
+Features ContactManager::supportedFeatures() const
+{
+ if (mPriv->supportedFeatures.isEmpty() &&
+ connection()->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) {
+ Features allFeatures = Features()
+ << Contact::FeatureAlias
+ << Contact::FeatureAvatarToken
+ << Contact::FeatureAvatarData
+ << Contact::FeatureSimplePresence
+ << Contact::FeatureCapabilities
+ << Contact::FeatureLocation
+ << Contact::FeatureInfo
+ << Contact::FeatureRosterGroups;
+ QStringList interfaces = connection()->lowlevel()->contactAttributeInterfaces();
+ foreach (const Feature &feature, allFeatures) {
+ if (interfaces.contains(featureToInterface(feature))) {
+ mPriv->supportedFeatures.insert(feature);
+ }
+ }
+
+ debug() << mPriv->supportedFeatures.size() << "contact features supported using" << this;
+ }
+
+ return mPriv->supportedFeatures;
+}
+
+/**
+ * Return the progress made in retrieving the contact list.
+ *
+ * Change notification is via the stateChanged() signal.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return The contact list state as #ContactListState.
+ * \sa stateChanged()
+ */
+ContactListState ContactManager::state() const
+{
+ return mPriv->roster->state();
+}
+
+/**
+ * Return a list of relevant contacts (a reasonable guess as to what should
+ * be displayed as "the contact list").
+ *
+ * This may include any or all of: contacts whose presence the user receives,
+ * contacts who are allowed to see the user's presence, contacts stored in
+ * some persistent contact list on the server, contacts who the user
+ * has blocked from communicating with them, or contacts who are relevant
+ * in some other way.
+ *
+ * User interfaces displaying a contact list will probably want to filter this
+ * list and display some suitable subset of it.
+ *
+ * On protocols where there is no concept of presence or a centrally-stored
+ * contact list (like IRC), this method may return an empty list.
+ *
+ * Change notification is via the allKnownContactsChanged() signal.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A set of pointers to the Contact objects.
+ * \sa allKnownContactsChanged()
+ */
+Contacts ContactManager::allKnownContacts() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ warning() << "Calling allKnownContacts() before FeatureRoster is ready";
+ return Contacts();
+ }
+
+ return mPriv->roster->allKnownContacts();
+}
+
+/**
+ * Return a list of user-defined contact list groups' names.
+ *
+ * Change notification is via the groupAdded(), groupRemoved() and groupRenamed() signals.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \return The list of user-defined contact list groups names.
+ * \sa groupMembersChanged(), groupAdded(), groupRemoved(), groupRenamed()
+ */
+QStringList ContactManager::allKnownGroups() const
+{
+ if (!connection()->isReady(Connection::FeatureRosterGroups)) {
+ return QStringList();
+ }
+
+ return mPriv->roster->allKnownGroups();
+}
+
+/**
+ * Attempt to add an user-defined contact list group named \a group.
+ *
+ * On some protocols (e.g. XMPP) empty groups are not represented on the server,
+ * so disconnecting from the server and reconnecting might cause empty groups to
+ * vanish.
+ *
+ * The returned pending operation will finish successfully if the group already
+ * exists.
+ *
+ * Change notification is via the groupAdded() signal.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to add an user-defined contact list group.
+ * \sa allKnownGroups(), groupAdded(), addContactsToGroup()
+ */
+PendingOperation *ContactManager::addGroup(const QString &group)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRosterGroups)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRosterGroups is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->addGroup(group);
+}
+
+/**
+ * Attempt to remove an user-defined contact list group named \a group.
+ *
+ * Change notification is via the groupRemoved() signal.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \return A PendingOperation which will emit PendingOperation::finished()
+ * when an attempt has been made to remove an user-defined contact list group.
+ * \sa allKnownGroups(), groupRemoved(), removeContactsFromGroup()
+ */
+PendingOperation *ContactManager::removeGroup(const QString &group)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRosterGroups)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRosterGroups is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->removeGroup(group);
+}
+
+/**
+ * Return the contacts in the given user-defined contact list group
+ * named \a group.
+ *
+ * Change notification is via the groupMembersChanged() signal.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \return A set of pointers to the Contact objects, or an empty set if the group does not exist.
+ * \sa allKnownGroups(), groupMembersChanged()
+ */
+Contacts ContactManager::groupContacts(const QString &group) const
+{
+ if (!connection()->isReady(Connection::FeatureRosterGroups)) {
+ return Contacts();
+ }
+
+ return mPriv->roster->groupContacts(group);
+}
+
+/**
+ * Attempt to add the given \a contacts to the user-defined contact list
+ * group named \a group.
+ *
+ * Change notification is via the groupMembersChanged() signal.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \param contacts Contacts to add.
+ * \return A PendingOperation which will emit PendingOperation::finished()
+ * when an attempt has been made to add the contacts to the user-defined
+ * contact list group.
+ * \sa groupMembersChanged(), groupContacts()
+ */
+PendingOperation *ContactManager::addContactsToGroup(const QString &group,
+ const QList<ContactPtr> &contacts)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRosterGroups)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRosterGroups is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->addContactsToGroup(group, contacts);
+}
+
+/**
+ * Attempt to remove the given \a contacts from the user-defined contact list
+ * group named \a group.
+ *
+ * Change notification is via the groupMembersChanged() signal.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \param contacts Contacts to remove.
+ * \return A PendingOperation which will PendingOperation::finished
+ * when an attempt has been made to remove the contacts from the user-defined
+ * contact list group.
+ * \sa groupMembersChanged(), groupContacts()
+ */
+PendingOperation *ContactManager::removeContactsFromGroup(const QString &group,
+ const QList<ContactPtr> &contacts)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRosterGroups)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRosterGroups is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->removeContactsFromGroup(group, contacts);
+}
+
+/**
+ * Return whether subscribing to additional contacts' presence is supported.
+ *
+ * In some protocols, the list of contacts whose presence can be seen is
+ * fixed, so we can't subscribe to the presence of additional contacts.
+ *
+ * Notably, in link-local XMPP, you can see the presence of everyone on the
+ * local network, and trying to add more subscriptions would be meaningless.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if Contact::requestPresenceSubscription() and
+ * requestPresenceSubscription() are likely to succeed, \c false otherwise.
+ * \sa requestPresenceSubscription(), subscriptionRequestHasMessage()
+ */
+bool ContactManager::canRequestPresenceSubscription() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canRequestPresenceSubscription();
+}
+
+/**
+ * Return whether a message can be sent when subscribing to contacts'
+ * presence.
+ *
+ * If no message will actually be sent, user interfaces should avoid prompting
+ * the user for a message, and use an empty string for the message argument.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if the message argument to Contact::requestPresenceSubscription() and
+ * requestPresenceSubscription() is actually used, \c false otherwise.
+ * \sa canRemovePresenceSubscription(), requestPresenceSubscription()
+ */
+bool ContactManager::subscriptionRequestHasMessage() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->subscriptionRequestHasMessage();
+}
+
+/**
+ * Attempt to subscribe to the presence of the given contacts.
+ *
+ * This operation is sometimes called "adding contacts to the buddy
+ * list" or "requesting authorization".
+ *
+ * On most protocols, the contacts will need to give permission
+ * before the user will be able to receive their presence: if so, they will
+ * be in presence state Contact::PresenceStateAsk until they authorize
+ * or deny the request.
+ *
+ * The returned PendingOperation will return successfully when a request to
+ * subscribe to the contacts' presence has been submitted, or fail if this
+ * cannot happen. In particular, it does not wait for the contacts to give
+ * permission for the presence subscription.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts whose presence is desired
+ * \param message A message from the user which is either transmitted to the
+ * contacts, or ignored, depending on the protocol
+ * \return A PendingOperation which will PendingOperation::finished()
+ * when an attempt has been made to subscribe to the contacts' presence.
+ * \sa canRequestPresenceSubscription(), subscriptionRequestHasMessage()
+ */
+PendingOperation *ContactManager::requestPresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->requestPresenceSubscription(contacts, message);
+}
+
+/**
+ * Return whether the user can stop receiving the presence of a contact
+ * whose presence they have subscribed to.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if Contact::removePresenceSubscription() and
+ * removePresenceSubscription() are likely to succeed
+ * for contacts with subscription state Contact::PresenceStateYes,
+ * \c false otherwise.
+ * \sa removePresenceSubscription(), subscriptionRemovalHasMessage()
+ */
+bool ContactManager::canRemovePresenceSubscription() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canRemovePresenceSubscription();
+}
+
+/**
+ * Return whether a message can be sent when removing an existing subscription
+ * to the presence of a contact.
+ *
+ * If no message will actually be sent, user interfaces should avoid prompting
+ * the user for a message, and use an empty string for the message argument.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if the message argument to Contact::removePresenceSubscription() and
+ * removePresenceSubscription() is actually used,
+ * for contacts with subscription state Contact::PresenceStateYes,
+ * \c false otherwise.
+ * \sa canRemovePresencePublication(), removePresenceSubscription()
+ */
+bool ContactManager::subscriptionRemovalHasMessage() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->subscriptionRemovalHasMessage();
+}
+
+/**
+ * Return whether the user can cancel a request to subscribe to a contact's
+ * presence before that contact has responded.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if Contact::removePresenceSubscription() and
+ * removePresenceSubscription() are likely to succeed
+ * for contacts with subscription state Contact::PresenceStateAsk,
+ * \c false otherwise.
+ * \sa removePresenceSubscription(), subscriptionRescindingHasMessage()
+ */
+bool ContactManager::canRescindPresenceSubscriptionRequest() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canRescindPresenceSubscriptionRequest();
+}
+
+/**
+ * Return whether a message can be sent when cancelling a request to
+ * subscribe to the presence of a contact.
+ *
+ * If no message will actually be sent, user interfaces should avoid prompting
+ * the user for a message, and use an empty string for the message argument.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if the message argument to Contact::removePresenceSubscription() and
+ * removePresenceSubscription() is actually used,
+ * for contacts with subscription state Contact::PresenceStateAsk,
+ * \c false otherwise.
+ * \sa canRescindPresenceSubscriptionRequest(), removePresenceSubscription()
+ */
+bool ContactManager::subscriptionRescindingHasMessage() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->subscriptionRescindingHasMessage();
+}
+
+/**
+ * Attempt to stop receiving the presence of the given contacts, or cancel
+ * a request to subscribe to their presence that was previously sent.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts whose presence is no longer required
+ * \message A message from the user which is either transmitted to the
+ * contacts, or ignored, depending on the protocol
+ * \return A PendingOperation which will PendingOperation::finished()
+ * when an attempt has been made to remove any subscription to the contacts' presence.
+ * \sa canRemovePresenceSubscription(), canRescindPresenceSubscriptionRequest(),
+ * subscriptionRemovalHasMessage(), subscriptionRescindingHasMessage()
+ */
+PendingOperation *ContactManager::removePresenceSubscription(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->removePresenceSubscription(contacts, message);
+}
+
+/**
+ * Return true if the publication of the user's presence to contacts can be
+ * authorized.
+ *
+ * This is always true, unless the protocol has no concept of authorizing
+ * publication (in which case contacts' publication status can never be
+ * Contact::PresenceStateAsk).
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if Contact::authorizePresencePublication() and
+ * authorizePresencePublication() are likely to succeed
+ * for contacts with subscription state Contact::PresenceStateAsk,
+ * \c false otherwise.
+ * \sa publicationAuthorizationHasMessage(), authorizePresencePublication()
+ */
+bool ContactManager::canAuthorizePresencePublication() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canAuthorizePresencePublication();
+}
+
+/**
+ * Return whether a message can be sent when authorizing a request from a
+ * contact that the user's presence is published to them.
+ *
+ * If no message will actually be sent, user interfaces should avoid prompting
+ * the user for a message, and use an empty string for the message argument.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if the message argument to Contact::authorizePresencePublication() and
+ * authorizePresencePublication() is actually used,
+ * for contacts with subscription state Contact::PresenceStateAsk,
+ * \c false otherwise.
+ * \sa canAuthorizePresencePublication(), authorizePresencePublication()
+ */
+bool ContactManager::publicationAuthorizationHasMessage() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->publicationAuthorizationHasMessage();
+}
+
+/**
+ * If the given contacts have asked the user to publish presence to them,
+ * grant permission for this publication to take place.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts who should be allowed to receive the user's
+ * presence
+ * \message A message from the user which is either transmitted to the
+ * contacts, or ignored, depending on the protocol
+ * \return A PendingOperation which will emit PendingOperation::fininshed
+ * when an attempt has been made to authorize publication of the user's presence
+ * to the contacts.
+ * \sa canAuthorizePresencePublication(), publicationAuthorizationHasMessage()
+ */
+PendingOperation *ContactManager::authorizePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->authorizePresencePublication(contacts, message);
+}
+
+/**
+ * Return whether a message can be sent when rejecting a request from a
+ * contact that the user's presence is published to them.
+ *
+ * If no message will actually be sent, user interfaces should avoid prompting
+ * the user for a message, and use an empty string for the message argument.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if the message argument to Contact::removePresencePublication() and
+ * removePresencePublication() is actually used,
+ * for contacts with subscription state Contact::PresenceStateAsk,
+ * \c false otherwise.
+ * \sa canRemovePresencePublication(), removePresencePublication()
+ */
+bool ContactManager::publicationRejectionHasMessage() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->publicationRejectionHasMessage();
+}
+
+/**
+ * Return true if the publication of the user's presence to contacts can be
+ * removed, even after permission has been given.
+ *
+ * (Rejecting requests for presence to be published is always allowed.)
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if Contact::removePresencePublication() and
+ * removePresencePublication() are likely to succeed
+ * for contacts with subscription state Contact::PresenceStateYes,
+ * \c false otherwise.
+ * \sa publicationRemovalHasMessage(), removePresencePublication()
+ */
+bool ContactManager::canRemovePresencePublication() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canRemovePresencePublication();
+}
+
+/**
+ * Return whether a message can be sent when revoking earlier permission
+ * that the user's presence is published to a contact.
+ *
+ * If no message will actually be sent, user interfaces should avoid prompting
+ * the user for a message, and use an empty string for the message argument.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if the message argument to Contact::removePresencePublication and
+ * removePresencePublication() is actually used,
+ * for contacts with subscription state Contact::PresenceStateYes,
+ * \c false otherwise.
+ * \sa canRemovePresencePublication(), removePresencePublication()
+ */
+bool ContactManager::publicationRemovalHasMessage() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->publicationRemovalHasMessage();
+}
+
+/**
+ * If the given contacts have asked the user to publish presence to them,
+ * deny this request (this should always succeed, unless a network error
+ * occurs).
+ *
+ * If the given contacts already have permission to receive
+ * the user's presence, attempt to revoke that permission (this might not
+ * be supported by the protocol - canRemovePresencePublication
+ * indicates whether it is likely to succeed).
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts who should no longer be allowed to receive the
+ * user's presence
+ * \message A message from the user which is either transmitted to the
+ * contacts, or ignored, depending on the protocol
+ * \return A PendingOperation which will emit PendingOperation::finished()
+ * when an attempt has been made to remove any publication of the user's presence to the
+ * contacts.
+ * \sa canRemovePresencePublication(), publicationRejectionHasMessage(),
+ * publicationRemovalHasMessage()
+ */
+PendingOperation *ContactManager::removePresencePublication(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->removePresencePublication(contacts, message);
+}
+
+/**
+ * Remove completely contacts from the server. It has the same effect than
+ * calling removePresencePublication() and removePresenceSubscription(),
+ * but also remove from 'stored' list if it exists.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts who should be removed
+ * \message A message from the user which is either transmitted to the
+ * contacts, or ignored, depending on the protocol
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to remove any publication of the user's presence to
+ * the contacts.
+ */
+PendingOperation *ContactManager::removeContacts(
+ const QList<ContactPtr> &contacts, const QString &message)
+{
+ if (!connection()->isValid()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"),
+ connection());
+ } else if (!connection()->isReady(Connection::FeatureRoster)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureRoster is not ready"),
+ connection());
+ }
+
+ return mPriv->roster->removeContacts(contacts, message);
+}
+
+/**
+ * Return whether this protocol has a list of blocked contacts.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if blockContacts() is likely to succeed, \c false otherwise.
+ */
+bool ContactManager::canBlockContacts() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canBlockContacts();
+}
+
+/**
+ * Return whether this protocol can report abusive contacts.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return \c true if reporting abuse when blocking contacts is supported, \c false otherwise.
+ */
+bool ContactManager::canReportAbuse() const
+{
+ if (!connection()->isReady(Connection::FeatureRoster)) {
+ return false;
+ }
+
+ return mPriv->roster->canReportAbuse();
+}
+
+/**
+ * \deprecated Use blockContacts(const QList<ContactPtr> &contacts) instead.
+ */
+PendingOperation *ContactManager::blockContacts(
+ const QList<ContactPtr> &contacts, bool value)
+{
+ return mPriv->roster->blockContacts(contacts, value, false);
+}
+
+/**
+ * Block the given contacts. Blocked contacts cannot send messages
+ * to the user; depending on the protocol, blocking a contact may
+ * have other effects.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts that should be blocked.
+ * \return A PendingOperation which will emit PendingOperation::finished()
+ * when an attempt has been made to take the requested action.
+ * \sa canBlockContacts(), unblockContacts(), blockContactsAndReportAbuse()
+ */
+PendingOperation *ContactManager::blockContacts(const QList<ContactPtr> &contacts)
+{
+ return mPriv->roster->blockContacts(contacts, true, false);
+}
+
+/**
+ * Block the given contacts and additionally report abusive behaviour
+ * to the server.
+ *
+ * If reporting abusive behaviour is not supported by the protocol,
+ * this method has the same effect as blockContacts().
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts who should be added to the list of blocked contacts.
+ * \return A PendingOperation which will emit PendingOperation::finished()
+ * when an attempt has been made to take the requested action.
+ * \sa canBlockContacts(), canReportAbuse(), blockContacts()
+ */
+PendingOperation *ContactManager::blockContactsAndReportAbuse(
+ const QList<ContactPtr> &contacts)
+{
+ return mPriv->roster->blockContacts(contacts, true, true);
+}
+
+/**
+ * Unblock the given contacts.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \param contacts Contacts that should be unblocked.
+ * \return A PendingOperation which will emit PendingOperation::finished()
+ * when an attempt has been made to take the requested action.
+ * \sa canBlockContacts(), blockContacts(), blockContactsAndReportAbuse()
+ */
+PendingOperation *ContactManager::unblockContacts(const QList<ContactPtr> &contacts)
+{
+ return mPriv->roster->blockContacts(contacts, false, false);
+}
+
+PendingContacts *ContactManager::contactsForHandles(const UIntList &handles,
+ const Features &features)
+{
+ QMap<uint, ContactPtr> satisfyingContacts;
+ QSet<uint> otherContacts;
+ Features missingFeatures;
+
+ Features realFeatures(features);
+ realFeatures.unite(connection()->contactFactory()->features());
+ // FeatureAvatarData depends on FeatureAvatarToken
+ if (realFeatures.contains(Contact::FeatureAvatarData) &&
+ !realFeatures.contains(Contact::FeatureAvatarToken)) {
+ realFeatures.insert(Contact::FeatureAvatarToken);
+ }
+
+ if (!connection()->isValid()) {
+ return new PendingContacts(ContactManagerPtr(this), handles, features, Features(),
+ QStringList(), satisfyingContacts, otherContacts,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"));
+ } else if (!connection()->isReady(Connection::FeatureCore)) {
+ return new PendingContacts(ContactManagerPtr(this), handles, features, Features(),
+ QStringList(), satisfyingContacts, otherContacts,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureCore is not ready"));
+ }
+
+ ConnectionLowlevelPtr connLowlevel = connection()->lowlevel();
+
+ if (connLowlevel->hasImmortalHandles() && realFeatures.isEmpty()) {
+ // try to avoid a roundtrip if all handles have an id set and no feature was requested
+ foreach (uint handle, handles) {
+ if (connLowlevel->hasContactId(handle)) {
+ ContactPtr contact = ensureContact(handle,
+ connLowlevel->contactId(handle), realFeatures);
+ satisfyingContacts.insert(handle, contact);
+ }
+ }
+ }
+
+ foreach (uint handle, handles) {
+ ContactPtr contact = lookupContactByHandle(handle);
+ if (contact) {
+ if ((realFeatures - contact->requestedFeatures()).isEmpty()) {
+ // Contact exists and has all the requested features
+ satisfyingContacts.insert(handle, contact);
+ } else {
+ // Contact exists but is missing features
+ otherContacts.insert(handle);
+ missingFeatures.unite(realFeatures - contact->requestedFeatures());
+ }
+ } else {
+ // Contact doesn't exist - we need to get all of the features (same as unite(features))
+ missingFeatures = realFeatures;
+ otherContacts.insert(handle);
+ }
+ }
+
+ Features supported = supportedFeatures();
+ QSet<QString> interfaces;
+ foreach (const Feature &feature, missingFeatures) {
+ ensureTracking(feature);
+
+ if (supported.contains(feature)) {
+ // Only query interfaces which are reported as supported to not get an error
+ interfaces.insert(featureToInterface(feature));
+ }
+ }
+
+ PendingContacts *contacts =
+ new PendingContacts(ContactManagerPtr(this), handles, features, missingFeatures,
+ interfaces.toList(), satisfyingContacts, otherContacts);
+ return contacts;
+}
+
+PendingContacts *ContactManager::contactsForHandles(const ReferencedHandles &handles,
+ const Features &features)
+{
+ return contactsForHandles(handles.toList(), features);
+}
+
+PendingContacts *ContactManager::contactsForHandles(const HandleIdentifierMap &handles,
+ const Features &features)
+{
+ connection()->lowlevel()->injectContactIds(handles);
+
+ return contactsForHandles(handles.keys(), features);
+}
+
+PendingContacts *ContactManager::contactsForIdentifiers(const QStringList &identifiers,
+ const Features &features)
+{
+ if (!connection()->isValid()) {
+ return new PendingContacts(ContactManagerPtr(this), identifiers, features,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"));
+ } else if (!connection()->isReady(Connection::FeatureCore)) {
+ return new PendingContacts(ContactManagerPtr(this), identifiers, features,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureCore is not ready"));
+ }
+
+ Features realFeatures(features);
+ realFeatures.unite(connection()->contactFactory()->features());
+ PendingContacts *contacts = new PendingContacts(ContactManagerPtr(this), identifiers,
+ realFeatures);
+ return contacts;
+}
+
+PendingContacts *ContactManager::upgradeContacts(const QList<ContactPtr> &contacts,
+ const Features &features)
+{
+ if (!connection()->isValid()) {
+ return new PendingContacts(ContactManagerPtr(this), contacts, features,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection is invalid"));
+ } else if (!connection()->isReady(Connection::FeatureCore)) {
+ return new PendingContacts(ContactManagerPtr(this), contacts, features,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Connection::FeatureCore is not ready"));
+ }
+
+ return new PendingContacts(ContactManagerPtr(this), contacts, features);
+}
+
+ContactPtr ContactManager::lookupContactByHandle(uint handle)
+{
+ ContactPtr contact;
+
+ if (mPriv->contacts.contains(handle)) {
+ contact = ContactPtr(mPriv->contacts.value(handle));
+ if (!contact) {
+ // Dangling weak pointer, remove it
+ mPriv->contacts.remove(handle);
+ }
+ }
+
+ return contact;
+}
+
+/**
+ * Start a request to retrieve the avatar for the given \a contacts.
+ *
+ * Force the request of the avatar data. This method returns directly, emitting
+ * Contact::avatarTokenChanged() and Contact::avatarDataChanged() signals once the token
+ * and data are fetched from the server.
+ *
+ * This is only useful if the avatar token is unknown; see Contact::isAvatarTokenKnown().
+ * It happens in the case of offline XMPP contacts, because the server does not
+ * send the token for them and an explicit request of the avatar data is needed.
+ *
+ * This method requires Contact::FeatureAvatarData to be ready.
+ *
+ * \sa Contact::avatarData(), Contact::avatarDataChanged(),
+ * Contact::avatarToken(), Contact::avatarTokenChanged()
+ */
+void ContactManager::requestContactAvatars(const QList<ContactPtr> &contacts)
+{
+ if (contacts.isEmpty()) {
+ return;
+ }
+
+ if (!mPriv->requestAvatarsIdle) {
+ mPriv->requestAvatarsIdle = true;
+ QTimer::singleShot(0, this, SLOT(doRequestAvatars()));
+ }
+
+ mPriv->requestAvatarsQueue.unite(contacts.toSet());
+}
+
+/**
+ * \deprecated
+ *
+ * This was never intended to be public API.
+ * Use Contact::requestAvatarData() or ContactManager::requestContactAvatars() instead.
+ */
+void ContactManager::requestContactAvatar(Contact *contact)
+{
+ requestContactAvatars(QList<ContactPtr>() << ContactPtr(contact));
+}
+
+/**
+ * Refresh information for the given contact.
+ *
+ * Once the information is retrieved infoFieldsChanged() will be emitted.
+ *
+ * This method requires Contact::FeatureInfo to be ready.
+ *
+ * \return A PendingOperation, which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa infoFieldsChanged()
+ */
+PendingOperation *ContactManager::refreshContactInfo(const QList<ContactPtr> &contacts)
+{
+ if (!mPriv->refreshInfoOp) {
+ mPriv->refreshInfoOp = new PendingRefreshContactInfo(connection());
+ QTimer::singleShot(0, this, SLOT(doRefreshInfo()));
+ }
+
+ foreach (const ContactPtr &contact, contacts) {
+ mPriv->refreshInfoOp->addContact(contact.data());
+ }
+
+ return mPriv->refreshInfoOp;
+}
+
+void ContactManager::onAliasesChanged(const AliasPairList &aliases)
+{
+ debug() << "Got AliasesChanged for" << aliases.size() << "contacts";
+
+ foreach (AliasPair pair, aliases) {
+ ContactPtr contact = lookupContactByHandle(pair.handle);
+
+ if (contact) {
+ contact->receiveAlias(pair.alias);
+ }
+ }
+}
+
+void ContactManager::doRequestAvatars()
+{
+ Q_ASSERT(mPriv->requestAvatarsIdle);
+ QSet<ContactPtr> contacts = mPriv->requestAvatarsQueue;
+ Q_ASSERT(contacts.size() > 0);
+
+ mPriv->requestAvatarsQueue.clear();
+ mPriv->requestAvatarsIdle = false;
+
+ int found = 0;
+ UIntList notFound;
+ foreach (const ContactPtr &contact, contacts) {
+ if (!contact) {
+ continue;
+ }
+
+ QString avatarFileName;
+ QString mimeTypeFileName;
+ bool success = (contact->isAvatarTokenKnown() &&
+ mPriv->buildAvatarFileName(contact->avatarToken(), false,
+ avatarFileName, mimeTypeFileName));
+
+ /* Check if the avatar is already in the cache */
+ if (success && QFile::exists(avatarFileName)) {
+ QFile mimeTypeFile(mimeTypeFileName);
+ mimeTypeFile.open(QIODevice::ReadOnly);
+ QString mimeType = QString(QLatin1String(mimeTypeFile.readAll()));
+ mimeTypeFile.close();
+
+ found++;
+
+ contact->receiveAvatarData(AvatarData(avatarFileName, mimeType));
+
+ continue;
+ }
+
+ notFound << contact->handle()[0];
+ }
+
+ if (found > 0) {
+ debug() << "Avatar(s) found in cache for" << found << "contact(s)";
+ }
+
+ if (found == contacts.size()) {
+ return;
+ }
+
+ debug() << "Requesting avatar(s) for" << contacts.size() - found << "contact(s)";
+
+ Client::ConnectionInterfaceAvatarsInterface *avatarsInterface =
+ connection()->interface<Client::ConnectionInterfaceAvatarsInterface>();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ avatarsInterface->RequestAvatars(notFound),
+ this);
+ connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), watcher,
+ SLOT(deleteLater()));
+}
+
+void ContactManager::onAvatarUpdated(uint handle, const QString &token)
+{
+ debug() << "Got AvatarUpdate for contact with handle" << handle;
+
+ ContactPtr contact = lookupContactByHandle(handle);
+ if (contact) {
+ contact->receiveAvatarToken(token);
+ }
+}
+
+void ContactManager::onAvatarRetrieved(uint handle, const QString &token,
+ const QByteArray &data, const QString &mimeType)
+{
+ QString avatarFileName;
+ QString mimeTypeFileName;
+
+ debug() << "Got AvatarRetrieved for contact with handle" << handle;
+
+ bool success = mPriv->buildAvatarFileName(token, true, avatarFileName,
+ mimeTypeFileName);
+
+ if (success) {
+ debug() << "Write avatar in cache for handle" << handle;
+ debug() << "Filename:" << avatarFileName;
+ debug() << "MimeType:" << mimeType;
+
+ QTemporaryFile mimeTypeFile(mimeTypeFileName);
+ mimeTypeFile.open();
+ mimeTypeFile.write(mimeType.toLatin1());
+ mimeTypeFile.setAutoRemove(false);
+ mimeTypeFile.rename(mimeTypeFileName);
+
+ QTemporaryFile avatarFile(avatarFileName);
+ avatarFile.open();
+ avatarFile.write(data);
+ avatarFile.setAutoRemove(false);
+ avatarFile.rename(avatarFileName);
+ }
+
+ ContactPtr contact = lookupContactByHandle(handle);
+ if (contact) {
+ contact->setAvatarToken(token);
+ contact->receiveAvatarData(AvatarData(avatarFileName, mimeType));
+ }
+}
+
+void ContactManager::onPresencesChanged(const SimpleContactPresences &presences)
+{
+ debug() << "Got PresencesChanged for" << presences.size() << "contacts";
+
+ foreach (uint handle, presences.keys()) {
+ ContactPtr contact = lookupContactByHandle(handle);
+
+ if (contact) {
+ contact->receiveSimplePresence(presences[handle]);
+ }
+ }
+}
+
+void ContactManager::onCapabilitiesChanged(const ContactCapabilitiesMap &caps)
+{
+ debug() << "Got ContactCapabilitiesChanged for" << caps.size() << "contacts";
+
+ foreach (uint handle, caps.keys()) {
+ ContactPtr contact = lookupContactByHandle(handle);
+
+ if (contact) {
+ contact->receiveCapabilities(caps[handle]);
+ }
+ }
+}
+
+void ContactManager::onLocationUpdated(uint handle, const QVariantMap &location)
+{
+ debug() << "Got LocationUpdated for contact with handle" << handle;
+
+ ContactPtr contact = lookupContactByHandle(handle);
+
+ if (contact) {
+ contact->receiveLocation(location);
+ }
+}
+
+void ContactManager::onContactInfoChanged(uint handle, const Tp::ContactInfoFieldList &info)
+{
+ debug() << "Got ContactInfoChanged for contact with handle" << handle;
+
+ ContactPtr contact = lookupContactByHandle(handle);
+
+ if (contact) {
+ contact->receiveInfo(info);
+ }
+}
+
+void ContactManager::doRefreshInfo()
+{
+ PendingRefreshContactInfo *op = mPriv->refreshInfoOp;
+ Q_ASSERT(op);
+ mPriv->refreshInfoOp = 0;
+ op->refreshInfo();
+}
+
+ContactPtr ContactManager::ensureContact(const ReferencedHandles &handle,
+ const Features &features, const QVariantMap &attributes)
+{
+ uint bareHandle = handle[0];
+ ContactPtr contact = lookupContactByHandle(bareHandle);
+
+ if (!contact) {
+ contact = connection()->contactFactory()->construct(this, handle, features, attributes);
+ mPriv->contacts.insert(bareHandle, contact.data());
+ }
+
+ contact->augment(features, attributes);
+
+ return contact;
+}
+
+ContactPtr ContactManager::ensureContact(uint bareHandle, const QString &id,
+ const Features &features)
+{
+ ContactPtr contact = lookupContactByHandle(bareHandle);
+
+ if (!contact) {
+ QVariantMap attributes;
+ attributes.insert(QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id"), id);
+
+ contact = connection()->contactFactory()->construct(this,
+ ReferencedHandles(connection(), HandleTypeContact, UIntList() << bareHandle),
+ features, attributes);
+ mPriv->contacts.insert(bareHandle, contact.data());
+
+ // do not call augment here as this is a fake contact
+ }
+
+ return contact;
+}
+
+QString ContactManager::featureToInterface(const Feature &feature)
+{
+ if (feature == Contact::FeatureAlias) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_ALIASING;
+ } else if (feature == Contact::FeatureAvatarToken) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_AVATARS;
+ } else if (feature == Contact::FeatureAvatarData) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_AVATARS;
+ } else if (feature == Contact::FeatureSimplePresence) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE;
+ } else if (feature == Contact::FeatureCapabilities) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES;
+ } else if (feature == Contact::FeatureLocation) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_LOCATION;
+ } else if (feature == Contact::FeatureInfo) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_INFO;
+ } else if (feature == Contact::FeatureRosterGroups) {
+ return TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS;
+ } else {
+ warning() << "ContactManager doesn't know which interface corresponds to feature"
+ << feature;
+ return QString();
+ }
+}
+
+void ContactManager::ensureTracking(const Feature &feature)
+{
+ if (mPriv->tracking[feature]) {
+ return;
+ }
+
+ ConnectionPtr conn(connection());
+
+ if (feature == Contact::FeatureAlias) {
+ Client::ConnectionInterfaceAliasingInterface *aliasingInterface =
+ conn->interface<Client::ConnectionInterfaceAliasingInterface>();
+
+ connect(aliasingInterface,
+ SIGNAL(AliasesChanged(Tp::AliasPairList)),
+ SLOT(onAliasesChanged(Tp::AliasPairList)));
+ } else if (feature == Contact::FeatureAvatarData) {
+ Client::ConnectionInterfaceAvatarsInterface *avatarsInterface =
+ conn->interface<Client::ConnectionInterfaceAvatarsInterface>();
+
+ connect(avatarsInterface,
+ SIGNAL(AvatarRetrieved(uint,QString,QByteArray,QString)),
+ SLOT(onAvatarRetrieved(uint,QString,QByteArray,QString)));
+ } else if (feature == Contact::FeatureAvatarToken) {
+ Client::ConnectionInterfaceAvatarsInterface *avatarsInterface =
+ conn->interface<Client::ConnectionInterfaceAvatarsInterface>();
+
+ connect(avatarsInterface,
+ SIGNAL(AvatarUpdated(uint,QString)),
+ SLOT(onAvatarUpdated(uint,QString)));
+ } else if (feature == Contact::FeatureCapabilities) {
+ Client::ConnectionInterfaceContactCapabilitiesInterface *contactCapabilitiesInterface =
+ conn->interface<Client::ConnectionInterfaceContactCapabilitiesInterface>();
+
+ connect(contactCapabilitiesInterface,
+ SIGNAL(ContactCapabilitiesChanged(Tp::ContactCapabilitiesMap)),
+ SLOT(onCapabilitiesChanged(Tp::ContactCapabilitiesMap)));
+ } else if (feature == Contact::FeatureInfo) {
+ Client::ConnectionInterfaceContactInfoInterface *contactInfoInterface =
+ conn->interface<Client::ConnectionInterfaceContactInfoInterface>();
+
+ connect(contactInfoInterface,
+ SIGNAL(ContactInfoChanged(uint,Tp::ContactInfoFieldList)),
+ SLOT(onContactInfoChanged(uint,Tp::ContactInfoFieldList)));
+ } else if (feature == Contact::FeatureLocation) {
+ Client::ConnectionInterfaceLocationInterface *locationInterface =
+ conn->interface<Client::ConnectionInterfaceLocationInterface>();
+
+ connect(locationInterface,
+ SIGNAL(LocationUpdated(uint,QVariantMap)),
+ SLOT(onLocationUpdated(uint,QVariantMap)));
+ } else if (feature == Contact::FeatureSimplePresence) {
+ Client::ConnectionInterfaceSimplePresenceInterface *simplePresenceInterface =
+ conn->interface<Client::ConnectionInterfaceSimplePresenceInterface>();
+
+ connect(simplePresenceInterface,
+ SIGNAL(PresencesChanged(Tp::SimpleContactPresences)),
+ SLOT(onPresencesChanged(Tp::SimpleContactPresences)));
+ } else if (feature == Contact::FeatureRosterGroups) {
+ // nothing to do here, but we don't want to warn
+ ;
+ } else {
+ warning() << " Unknown feature" << feature
+ << "when trying to figure out how to connect change notification!";
+ }
+
+ mPriv->tracking[feature] = true;
+}
+
+PendingOperation *ContactManager::introspectRoster()
+{
+ return mPriv->roster->introspect();
+}
+
+PendingOperation *ContactManager::introspectRosterGroups()
+{
+ return mPriv->roster->introspectGroups();
+}
+
+void ContactManager::resetRoster()
+{
+ mPriv->roster->reset();
+}
+
+/**
+ * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts)
+ *
+ * Emitted whenever some contacts request for presence publication.
+ *
+ * \param contacts A set of contacts which requested presence publication.
+ */
+
+/**
+ * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts,
+ * const QString &message)
+ *
+ * \deprecated Turned out this didn't make sense at all. There can be multiple contacts, but this
+ * signal carries just a single message.
+ * Use presencePublicationRequested(const Tp::Contacts &contacts) instead,
+ * and extract the messages from the individual Tp::Contact objects.
+ */
+
+/**
+ * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * \deprecated Turned out this didn't make sense at all. There can be multiple contacts, but this
+ * signal carries just a single details.
+ * Use presencePublicationRequested(const Tp::Contacts &contacts) instead,
+ * and extract the details (message) from the individual Tp::Contact objects.
+ */
+
+/**
+ * \fn void ContactManager::groupAdded(const QString &group)
+ *
+ * Emitted when a new contact list group is created.
+ *
+ * \param group The group name.
+ * \sa allKnownGroups()
+ */
+
+/**
+ * \fn void ContactManager::groupRenamed(const QString &oldGroup, const QString &newGroup)
+ *
+ * Emitted when a new contact list group is renamed.
+ *
+ * \param oldGroup The old group name.
+ * \param newGroup The new group name.
+ * \sa allKnownGroups()
+ */
+
+/**
+ * \fn void ContactManager::groupRemoved(const QString &group)
+ *
+ * Emitted when a contact list group is removed.
+ *
+ * \param group The group name.
+ * \sa allKnownGroups()
+ */
+
+/**
+ * \fn void ContactManager::groupMembersChanged(const QString &group,
+ * const Tp::Contacts &groupMembersAdded,
+ * const Tp::Contacts &groupMembersRemoved,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * Emitted whenever some contacts got removed or added from
+ * a group.
+ *
+ * \param group The name of the group that changed.
+ * \param groupMembersAdded A set of contacts which were added to the group \a group.
+ * \param groupMembersRemoved A set of contacts which were removed from the group \a group.
+ * \param details The change details.
+ * \sa groupContacts()
+ */
+
+/**
+ * \fn void ContactManager::allKnownContactsChanged(const Tp::Contacts &contactsAdded,
+ * const Tp::Contacts &contactsRemoved,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * Emitted whenever some contacts got removed or added from
+ * ContactManager's known contact list. It is useful for monitoring which contacts
+ * are currently known by ContactManager.
+ *
+ * Note that, in some protocols, this signal could stream newly added contacts
+ * with both presence subscription and publication state set to No. Be sure to watch
+ * over publication and/or subscription state changes if that is the case.
+ *
+ * \param contactsAdded A set of contacts which were added to the known contact list.
+ * \param contactsRemoved A set of contacts which were removed from the known contact list.
+ * \param details The change details.
+ * \sa allKnownContacts()
+ */
+
+void ContactManager::connectNotify(const char *signalName)
+{
+ if (qstrcmp(signalName, SIGNAL(presencePublicationRequested(Tp::Contacts,Tp::Channel::GroupMemberChangeDetails))) == 0) {
+ warning() << "Connecting to deprecated signal presencePublicationRequested(Tp::Contacts,Tp::Channel::GroupMemberChangeDetails)";
+ } else if (qstrcmp(signalName, SIGNAL(presencePublicationRequested(Tp::Contacts,QString))) == 0) {
+ warning() << "Connecting to deprecated signal presencePublicationRequested(Tp::Contacts,QString)";
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/contact-manager.h b/qt4/TelepathyQt4/contact-manager.h
new file mode 100644
index 000000000..68b7f5ca3
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-manager.h
@@ -0,0 +1,201 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_manager_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_manager_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/Types>
+
+#include <QList>
+#include <QSet>
+#include <QString>
+#include <QStringList>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class Connection;
+class PendingContacts;
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT ContactManager : public Object
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ContactManager)
+
+public:
+ virtual ~ContactManager();
+
+ ConnectionPtr connection() const;
+
+ Features supportedFeatures() const;
+
+ ContactListState state() const;
+
+ Contacts allKnownContacts() const;
+ QStringList allKnownGroups() const;
+
+ PendingOperation *addGroup(const QString &group);
+ PendingOperation *removeGroup(const QString &group);
+
+ Contacts groupContacts(const QString &group) const;
+ PendingOperation *addContactsToGroup(const QString &group,
+ const QList<ContactPtr> &contacts);
+ PendingOperation *removeContactsFromGroup(const QString &group,
+ const QList<ContactPtr> &contacts);
+
+ bool canRequestPresenceSubscription() const;
+ bool subscriptionRequestHasMessage() const;
+ PendingOperation *requestPresenceSubscription(
+ const QList<ContactPtr> &contacts,
+ const QString &message = QString());
+ bool canRemovePresenceSubscription() const;
+ bool subscriptionRemovalHasMessage() const;
+ bool canRescindPresenceSubscriptionRequest() const;
+ bool subscriptionRescindingHasMessage() const;
+ PendingOperation *removePresenceSubscription(
+ const QList<ContactPtr> &contacts,
+ const QString &message = QString());
+ bool canAuthorizePresencePublication() const;
+ bool publicationAuthorizationHasMessage() const;
+ PendingOperation *authorizePresencePublication(
+ const QList<ContactPtr> &contacts,
+ const QString &message = QString());
+ bool publicationRejectionHasMessage() const;
+ bool canRemovePresencePublication() const;
+ bool publicationRemovalHasMessage() const;
+ PendingOperation *removePresencePublication(
+ const QList<ContactPtr> &contacts,
+ const QString &message = QString());
+ PendingOperation *removeContacts(
+ const QList<ContactPtr> &contacts,
+ const QString &message = QString());
+
+ bool canBlockContacts() const;
+ bool canReportAbuse() const;
+ TELEPATHY_QT4_DEPRECATED PendingOperation *blockContacts(
+ const QList<ContactPtr> &contacts, bool value);
+ PendingOperation *blockContacts(const QList<ContactPtr> &contacts);
+ PendingOperation *blockContactsAndReportAbuse(const QList<ContactPtr> &contacts);
+ PendingOperation *unblockContacts(const QList<ContactPtr> &contacts);
+
+ PendingContacts *contactsForHandles(const UIntList &handles,
+ const Features &features = Features());
+ PendingContacts *contactsForHandles(const ReferencedHandles &handles,
+ const Features &features = Features());
+ PendingContacts *contactsForHandles(const HandleIdentifierMap &handles,
+ const Features &features = Features());
+
+ PendingContacts *contactsForIdentifiers(const QStringList &identifiers,
+ const Features &features = Features());
+
+ PendingContacts *upgradeContacts(const QList<ContactPtr> &contacts,
+ const Features &features);
+
+ ContactPtr lookupContactByHandle(uint handle);
+
+ TELEPATHY_QT4_DEPRECATED void requestContactAvatar(Contact *contact);
+ void requestContactAvatars(const QList<ContactPtr> &contacts);
+
+ PendingOperation *refreshContactInfo(const QList<ContactPtr> &contact);
+
+Q_SIGNALS:
+ void stateChanged(Tp::ContactListState state);
+
+ void presencePublicationRequested(const Tp::Contacts &contacts);
+ // deprecated - carry redundant data which can be retrieved (meaningfully) from the Contacts
+ // themselves (note: multiple contacts, but just a single message/details!)
+ void presencePublicationRequested(const Tp::Contacts &contacts, const QString &message);
+ void presencePublicationRequested(const Tp::Contacts &contacts,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void groupAdded(const QString &group);
+ void groupRenamed(const QString &oldGroup, const QString &newGroup);
+ void groupRemoved(const QString &group);
+
+ void groupMembersChanged(const QString &group,
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void allKnownContactsChanged(const Tp::Contacts &contactsAdded,
+ const Tp::Contacts &contactsRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+protected:
+ // FIXME: (API/ABI break) Remove connectNotify
+ void connectNotify(const char *);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onAliasesChanged(const Tp::AliasPairList &);
+ TELEPATHY_QT4_NO_EXPORT void doRequestAvatars();
+ TELEPATHY_QT4_NO_EXPORT void onAvatarUpdated(uint, const QString &);
+ TELEPATHY_QT4_NO_EXPORT void onAvatarRetrieved(uint, const QString &, const QByteArray &, const QString &);
+ TELEPATHY_QT4_NO_EXPORT void onPresencesChanged(const Tp::SimpleContactPresences &);
+ TELEPATHY_QT4_NO_EXPORT void onCapabilitiesChanged(const Tp::ContactCapabilitiesMap &);
+ TELEPATHY_QT4_NO_EXPORT void onLocationUpdated(uint, const QVariantMap &);
+ TELEPATHY_QT4_NO_EXPORT void onContactInfoChanged(uint, const Tp::ContactInfoFieldList &);
+ TELEPATHY_QT4_NO_EXPORT void doRefreshInfo();
+
+private:
+ class PendingRefreshContactInfo;
+ class Roster;
+ friend class Connection;
+ friend class PendingContacts;
+ friend class PendingRefreshContactInfo;
+ friend class Roster;
+
+ TELEPATHY_QT4_NO_EXPORT ContactManager(Connection *parent);
+
+ TELEPATHY_QT4_NO_EXPORT ContactPtr ensureContact(const ReferencedHandles &handle,
+ const Features &features,
+ const QVariantMap &attributes);
+ TELEPATHY_QT4_NO_EXPORT ContactPtr ensureContact(uint bareHandle,
+ const QString &id, const Features &features);
+
+ TELEPATHY_QT4_NO_EXPORT static QString featureToInterface(const Feature &feature);
+ TELEPATHY_QT4_NO_EXPORT void ensureTracking(const Feature &feature);
+
+ TELEPATHY_QT4_NO_EXPORT PendingOperation *introspectRoster();
+ TELEPATHY_QT4_NO_EXPORT PendingOperation *introspectRosterGroups();
+ TELEPATHY_QT4_NO_EXPORT void resetRoster();
+
+ TELEPATHY_QT4_NO_EXPORT PendingOperation *refreshContactInfo(Contact *contact);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-messenger.cpp b/qt4/TelepathyQt4/contact-messenger.cpp
new file mode 100644
index 000000000..ce2c1a8ac
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-messenger.cpp
@@ -0,0 +1,257 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ContactMessenger>
+
+#include "TelepathyQt4/_gen/contact-messenger.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include "TelepathyQt4/future-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/ChannelDispatcher>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/MessageContentPartList>
+#include <TelepathyQt4/PendingSendMessage>
+#include <TelepathyQt4/SimpleTextObserver>
+#include <TelepathyQt4/TextChannel>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ContactMessenger::Private
+{
+ Private(ContactMessenger *parent, const AccountPtr &account, const QString &contactIdentifier)
+ : parent(parent),
+ account(account),
+ contactIdentifier(contactIdentifier),
+ cdMessagesInterface(0)
+ {
+ }
+
+ PendingSendMessage *sendMessage(const Message &message, MessageSendingFlags flags);
+
+ ContactMessenger *parent;
+ AccountPtr account;
+ QString contactIdentifier;
+ SimpleTextObserverPtr observer;
+ TpFuture::Client::ChannelDispatcherInterfaceMessagesInterface *cdMessagesInterface;
+};
+
+PendingSendMessage *ContactMessenger::Private::sendMessage(const Message &message,
+ MessageSendingFlags flags)
+{
+ if (!cdMessagesInterface) {
+ cdMessagesInterface = new TpFuture::Client::ChannelDispatcherInterfaceMessagesInterface(
+ account->dbusConnection(),
+ TP_QT4_CHANNEL_DISPATCHER_BUS_NAME, TP_QT4_CHANNEL_DISPATCHER_OBJECT_PATH, parent);
+ }
+
+ PendingSendMessage *op = new PendingSendMessage(ContactMessengerPtr(parent), message);
+
+ // TODO: is there a way to avoid this? Ideally TpFuture classes should use Tp types.
+ TpFuture::MessagePartList parts;
+ foreach (const Tp::MessagePart &part, message.parts()) {
+ parts << static_cast<QMap<QString, QDBusVariant> >(part);
+ }
+
+ connect(new QDBusPendingCallWatcher(
+ cdMessagesInterface->SendMessage(QDBusObjectPath(account->objectPath()),
+ contactIdentifier, parts, (uint) flags)),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ op,
+ SLOT(onCDMessageSent(QDBusPendingCallWatcher*)));
+ return op;
+}
+
+/**
+ * \class ContactMessenger
+ * \ingroup clientaccount
+ * \headerfile TelepathyQt4/contact-messenger.h <TelepathyQt4/ContactMessenger>
+ *
+ * \brief The ContactMessenger class provides an easy way to send text messages to a contact
+ * and also track sent/receive text messages from the same contact.
+ */
+
+/**
+ * Create a new ContactMessenger object.
+ *
+ * \param account The account this messenger is communicating with.
+ * \param contact The contact this messenger is communicating with.
+ * \return An ContactMessengerPtr object pointing to the newly created ContactMessenger object,
+ * or a null ContactMessengerPtr if \a contact is null.
+ */
+ContactMessengerPtr ContactMessenger::create(const AccountPtr &account,
+ const ContactPtr &contact)
+{
+ if (!contact) {
+ warning() << "Contact used to create a ContactMessenger object must be "
+ "valid";
+ return ContactMessengerPtr();
+ }
+ return ContactMessengerPtr(new ContactMessenger(account, contact->id()));
+}
+
+/**
+ * Create a new ContactMessenger object.
+ *
+ * \param account The account this messenger is communicating with.
+ * \param contactIdentifier The identifier of the contact this messenger is communicating with.
+ * \return An ContactMessengerPtr object pointing to the newly created ContactMessenger object,
+ * or a null ContactMessengerPtr if \a contact is null.
+ */
+ContactMessengerPtr ContactMessenger::create(const AccountPtr &account,
+ const QString &contactIdentifier)
+{
+ if (contactIdentifier.isEmpty()) {
+ warning() << "Contact identifier used to create a ContactMessenger object must be "
+ "non-empty";
+ return ContactMessengerPtr();
+ }
+ return ContactMessengerPtr(new ContactMessenger(account, contactIdentifier));
+}
+
+/**
+ * Construct a new ContactMessenger object.
+ *
+ * \param account The account this messenger is communicating with.
+ * \param contactIdentifier The identifier of the contact this messenger is communicating with.
+ */
+ContactMessenger::ContactMessenger(const AccountPtr &account, const QString &contactIdentifier)
+ : mPriv(new Private(this, account, contactIdentifier))
+{
+ mPriv->observer = SimpleTextObserver::create(account, contactIdentifier);
+ connect(mPriv->observer.data(),
+ SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr)),
+ SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr)));
+ connect(mPriv->observer.data(),
+ SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)),
+ SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)));
+}
+
+/**
+ * Class destructor.
+ */
+ContactMessenger::~ContactMessenger()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account this messenger is communicating with.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr ContactMessenger::account() const
+{
+ return mPriv->account;
+}
+
+/**
+ * Return the identifier of the contact this messenger is communicating with.
+ *
+ * \return The identifier of the contact.
+ */
+QString ContactMessenger::contactIdentifier() const
+{
+ return mPriv->contactIdentifier;
+}
+
+/**
+ * Return the list of text chats currently being observed.
+ *
+ * \return A list of pointers to TextChannel objects.
+ */
+QList<TextChannelPtr> ContactMessenger::textChats() const
+{
+ return mPriv->observer->textChats();
+}
+
+/**
+ * Send a message to the contact identified by contactIdentifier() using account().
+ *
+ * Note that the return from this method isn't ordered in any sane way, meaning that
+ * messageSent() can be signalled either before or after the returned PendingSendMessage object
+ * finishes.
+ *
+ * \param text The message text.
+ * \param type The message type.
+ * \param flags The message flags.
+ * \return A PendingSendMessage which will emit PendingSendMessage::finished
+ * once the reply is received and that can be used to check whether sending the
+ * message succeeded or not.
+ */
+PendingSendMessage *ContactMessenger::sendMessage(const QString &text,
+ ChannelTextMessageType type,
+ MessageSendingFlags flags)
+{
+ Message message(type, text);
+ return mPriv->sendMessage(message, flags);
+}
+
+/**
+ * Send a message to the contact identified by contactIdentifier() using account().
+ *
+ * Note that the return from this method isn't ordered in any sane way, meaning that
+ * messageSent() can be signalled either before or after the returned PendingSendMessage object
+ * finishes.
+ *
+ * \param parts The message parts.
+ * \param flags The message flags.
+ * \return A PendingSendMessage which will emit PendingSendMessage::finished
+ * once the reply is received and that can be used to check whether sending the
+ * message succeeded or not.
+ */
+PendingSendMessage *ContactMessenger::sendMessage(const MessageContentPartList &parts,
+ MessageSendingFlags flags)
+{
+ Message message(parts.bareParts());
+ return mPriv->sendMessage(message, flags);
+}
+
+/**
+ * \fn void ContactMessenger::messageSent(const Tp::Message &message,
+ * Tp::MessageSendingFlags flags, const QString &sentMessageToken,
+ * const Tp::TextChannelPtr &channel);
+ *
+ * Emitted whenever a text message on account() is sent to the contact
+ * identified by contactIdentifier().
+ *
+ * \param message The message sent.
+ * \param flags The flags of the message that was sent.
+ * \param sentMessageToken The token of the message that was sent.
+ * \param channel The channel from which the message was sent.
+ */
+
+/**
+ * \fn void ContactMessenger::messageReceived(const Tp::ReceivedMessage &message,
+ * const Tp::TextChannelPtr &channel);
+ *
+ * Emitted whenever a text message on account() is received from the contact
+ * identified by contactIdentifier().
+ *
+ * \param message The message received.
+ * \param channel The channel from which the message was received.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/contact-messenger.h b/qt4/TelepathyQt4/contact-messenger.h
new file mode 100644
index 000000000..41e9578d7
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-messenger.h
@@ -0,0 +1,78 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_messenger_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_messenger_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class PendingSendMessage;
+class MessageContentPartList;
+
+class TELEPATHY_QT4_EXPORT ContactMessenger : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ContactMessenger)
+
+public:
+ static ContactMessengerPtr create(const AccountPtr &account, const ContactPtr &contact);
+ static ContactMessengerPtr create(const AccountPtr &account, const QString &contactIdentifier);
+
+ virtual ~ContactMessenger();
+
+ AccountPtr account() const;
+ QString contactIdentifier() const;
+
+ QList<TextChannelPtr> textChats() const;
+
+ PendingSendMessage *sendMessage(const QString &text,
+ ChannelTextMessageType type = ChannelTextMessageTypeNormal,
+ MessageSendingFlags flags = 0);
+ PendingSendMessage *sendMessage(const MessageContentPartList &parts,
+ MessageSendingFlags flags = 0);
+
+Q_SIGNALS:
+ void messageSent(const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken, const Tp::TextChannelPtr &channel);
+ void messageReceived(const Tp::ReceivedMessage &message, const Tp::TextChannelPtr &channel);
+
+private:
+ TELEPATHY_QT4_NO_EXPORT ContactMessenger(const AccountPtr &account,
+ const QString &contactIdentifier);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-search-channel-internal.h b/qt4/TelepathyQt4/contact-search-channel-internal.h
new file mode 100644
index 000000000..91b8e0aeb
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-search-channel-internal.h
@@ -0,0 +1,52 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_search_channel_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_search_channel_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/PendingOperation>
+
+#include <QDBusPendingCall>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT ContactSearchChannel::PendingSearch : public PendingOperation
+{
+ Q_OBJECT
+
+public:
+ PendingSearch(const ContactSearchChannelPtr &chan, QDBusPendingCall call);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onSearchStateChanged(Tp::ChannelContactSearchState state, const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details);
+ TELEPATHY_QT4_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*);
+
+private:
+ bool mFinished;
+ QDBusError mError;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/contact-search-channel.cpp b/qt4/TelepathyQt4/contact-search-channel.cpp
new file mode 100644
index 000000000..1fffe7567
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-search-channel.cpp
@@ -0,0 +1,676 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ContactSearchChannel>
+#include "TelepathyQt4/contact-search-channel-internal.h"
+
+#include "TelepathyQt4/_gen/contact-search-channel.moc.hpp"
+#include "TelepathyQt4/_gen/contact-search-channel-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ContactSearchChannel::Private
+{
+ Private(ContactSearchChannel *parent,
+ const QVariantMap &immutableProperties);
+ ~Private();
+
+ static void introspectMain(Private *self);
+
+ void extractImmutableProperties(const QVariantMap &props);
+
+ void processSignalsQueue();
+ void processSearchStateChangeQueue();
+ void processSearchResultQueue();
+
+ struct SearchStateChangeInfo
+ {
+ SearchStateChangeInfo(uint state, const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details)
+ : state(state), errorName(errorName), details(details)
+ {
+ }
+
+ uint state;
+ QString errorName;
+ ContactSearchChannel::SearchStateChangeDetails details;
+ };
+
+ // Public object
+ ContactSearchChannel *parent;
+
+ QVariantMap immutableProperties;
+
+ Client::ChannelTypeContactSearchInterface *contactSearchInterface;
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ uint searchState;
+ uint limit;
+ QStringList availableSearchKeys;
+ QString server;
+
+ QQueue<void (Private::*)()> signalsQueue;
+ QQueue<SearchStateChangeInfo> searchStateChangeQueue;
+ QQueue<ContactSearchResultMap> searchResultQueue;
+ bool processingSignalsQueue;
+};
+
+ContactSearchChannel::Private::Private(ContactSearchChannel *parent,
+ const QVariantMap &immutableProperties)
+ : parent(parent),
+ immutableProperties(immutableProperties),
+ contactSearchInterface(parent->interface<Client::ChannelTypeContactSearchInterface>()),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ searchState(ChannelContactSearchStateNotStarted),
+ limit(0),
+ processingSignalsQueue(false)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+ContactSearchChannel::Private::~Private()
+{
+}
+
+void ContactSearchChannel::Private::introspectMain(ContactSearchChannel::Private *self)
+{
+ /* we need to at least introspect SearchState here as it's not immutable */
+ self->parent->connect(self->contactSearchInterface,
+ SIGNAL(SearchStateChanged(uint,QString,QVariantMap)),
+ SLOT(onSearchStateChanged(uint,QString,QVariantMap)));
+ self->parent->connect(self->contactSearchInterface,
+ SIGNAL(SearchResultReceived(Tp::ContactSearchResultMap)),
+ SLOT(onSearchResultReceived(Tp::ContactSearchResultMap)));
+
+ QVariantMap props;
+ bool needIntrospectMainProps = false;
+ const unsigned numNames = 3;
+ const static QString names[numNames] = {
+ QLatin1String("Limit"),
+ QLatin1String("AvailableSearchKeys"),
+ QLatin1String("Server")
+ };
+ const static QString qualifiedNames[numNames] = {
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Limit"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".AvailableSearchKeys"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Server")
+ };
+ for (unsigned i = 0; i < numNames; ++i) {
+ const QString &qualified = qualifiedNames[i];
+ if (!self->immutableProperties.contains(qualified)) {
+ needIntrospectMainProps = true;
+ break;
+ }
+ props.insert(names[i], self->immutableProperties.value(qualified));
+ }
+
+ if (needIntrospectMainProps) {
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH)),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotProperties(QDBusPendingCallWatcher*)));
+ } else {
+ self->extractImmutableProperties(props);
+
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->Get(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH),
+ QLatin1String("SearchState")),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotSearchState(QDBusPendingCallWatcher*)));
+ }
+}
+
+void ContactSearchChannel::Private::extractImmutableProperties(const QVariantMap &props)
+{
+ limit = qdbus_cast<uint>(props[QLatin1String("Limit")]);
+ availableSearchKeys = qdbus_cast<QStringList>(props[QLatin1String("AvailableSearchKeys")]);
+ server = qdbus_cast<QString>(props[QLatin1String("Server")]);
+}
+
+void ContactSearchChannel::Private::processSignalsQueue()
+{
+ if (processingSignalsQueue || signalsQueue.isEmpty()) {
+ return;
+ }
+
+ processingSignalsQueue = true;
+ (this->*(signalsQueue.dequeue()))();
+}
+
+void ContactSearchChannel::Private::processSearchStateChangeQueue()
+{
+ const SearchStateChangeInfo &info = searchStateChangeQueue.dequeue();
+
+ searchState = info.state;
+ emit parent->searchStateChanged(
+ static_cast<ChannelContactSearchState>(info.state), info.errorName,
+ SearchStateChangeDetails(info.details));
+
+ processingSignalsQueue = false;
+ processSignalsQueue();
+}
+
+void ContactSearchChannel::Private::processSearchResultQueue()
+{
+ const ContactSearchResultMap &result = searchResultQueue.first();
+ if (!result.isEmpty()) {
+ ContactManagerPtr manager = parent->connection()->contactManager();
+ PendingContacts *pendingContacts = manager->contactsForIdentifiers(
+ result.keys());
+ parent->connect(pendingContacts,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotSearchResultContacts(Tp::PendingOperation*)));
+ } else {
+ searchResultQueue.dequeue();
+
+ emit parent->searchResultReceived(SearchResult());
+
+ processingSignalsQueue = false;
+ processSignalsQueue();
+ }
+}
+
+struct TELEPATHY_QT4_NO_EXPORT ContactSearchChannel::SearchStateChangeDetails::Private : public QSharedData
+{
+ Private(const QVariantMap &details)
+ : details(details) {}
+
+ QVariantMap details;
+};
+
+ContactSearchChannel::SearchStateChangeDetails::SearchStateChangeDetails(const QVariantMap &details)
+ : mPriv(new Private(details))
+{
+}
+
+ContactSearchChannel::SearchStateChangeDetails::SearchStateChangeDetails()
+{
+}
+
+ContactSearchChannel::SearchStateChangeDetails::SearchStateChangeDetails(
+ const ContactSearchChannel::SearchStateChangeDetails &other)
+ : mPriv(other.mPriv)
+{
+}
+
+ContactSearchChannel::SearchStateChangeDetails::~SearchStateChangeDetails()
+{
+}
+
+ContactSearchChannel::SearchStateChangeDetails &ContactSearchChannel::SearchStateChangeDetails::operator=(
+ const ContactSearchChannel::SearchStateChangeDetails &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+QVariantMap ContactSearchChannel::SearchStateChangeDetails::allDetails() const
+{
+ return isValid() ? mPriv->details : QVariantMap();
+}
+
+ContactSearchChannel::PendingSearch::PendingSearch(const ContactSearchChannelPtr &channel,
+ QDBusPendingCall call)
+ : PendingOperation(channel),
+ mFinished(false)
+{
+ connect(channel.data(),
+ SIGNAL(searchStateChanged(Tp::ChannelContactSearchState, const QString &,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &)),
+ SLOT(onSearchStateChanged(Tp::ChannelContactSearchState, const QString &,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &)));
+ connect(new QDBusPendingCallWatcher(call),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(watcherFinished(QDBusPendingCallWatcher*)));
+}
+
+void ContactSearchChannel::PendingSearch::onSearchStateChanged(
+ Tp::ChannelContactSearchState state, const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details)
+{
+ if (state != ChannelContactSearchStateNotStarted) {
+ if (mFinished) {
+ if (mError.isValid()) {
+ setFinishedWithError(mError);
+ } else {
+ setFinished();
+ }
+ }
+ mFinished = true;
+ }
+}
+
+void ContactSearchChannel::PendingSearch::watcherFinished(QDBusPendingCallWatcher *watcher)
+{
+ if (watcher->isError()) {
+ if (mFinished) {
+ setFinishedWithError(watcher->error());
+ } else {
+ mError = watcher->error();
+ }
+ } else {
+ if (mFinished) {
+ setFinished();
+ }
+ }
+ mFinished = true;
+
+ watcher->deleteLater();
+}
+
+/**
+ * \class ContactSearchChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/contact-search-channel.h <TelepathyQt4/ContactSearchChannel>
+ *
+ * \brief The ContactSearchChannel class represents a Telepathy channel of type
+ * ContactSearch.
+ */
+
+/**
+ * \class ContactSearchChannel::SearchStateChangeDetails
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/contact-search-channel.h <TelepathyQt4/ContactSearchChannel>
+ *
+ * \brief The ContactSearchChannel::SearchStateChangeDetails class provides
+ * a wrapper around the details for a search state change.
+ *
+ * \sa ContactSearchChannel
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * ContactSearchChannel object usable.
+ *
+ * Note that this feature must be enabled in order to use most
+ * ContactSearchChannel methods.
+ * See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature ContactSearchChannel::FeatureCore = Feature(QLatin1String(ContactSearchChannel::staticMetaObject.className()), 0);
+
+/**
+ * Create a new ContactSearchChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A ContactSearchChannelPtr object pointing to the newly created
+ * ContactSearchChannel object.
+ */
+ContactSearchChannelPtr ContactSearchChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return ContactSearchChannelPtr(new ContactSearchChannel(connection, objectPath,
+ immutableProperties, ContactSearchChannel::FeatureCore));
+}
+
+/**
+ * Construct a new ContactSearchChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on ContactSearchChannel::FeatureCore.
+ */
+ContactSearchChannel::ContactSearchChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : Channel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this, immutableProperties))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ContactSearchChannel::~ContactSearchChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the current search state of this channel.
+ *
+ * Change notification is via the searchStateChanged() signal.
+ *
+ * This method requires ContactSearchChannel::FeatureCore to be ready.
+ *
+ * \return The current search state as #ChannelContactSearchState.
+ * \sa searchStateChanged()
+ */
+ChannelContactSearchState ContactSearchChannel::searchState() const
+{
+ return static_cast<ChannelContactSearchState>(mPriv->searchState);
+}
+
+/**
+ * Return the maximum number of results that should be returned by calling search(), where
+ * 0 represents no limit.
+ *
+ * For example, if the terms passed to search() match Antonius, Bridget and Charles and
+ * this property is 2, the search service will only return Antonius and Bridget.
+ *
+ * This method requires ContactSearchChannel::FeatureCore to be ready.
+ *
+ * \return The maximum number of results, or 0 if there is no limit.
+ * \sa availableSearchKeys(), search()
+ */
+uint ContactSearchChannel::limit() const
+{
+ return mPriv->limit;
+}
+
+/**
+ * Return the set of search keys supported by this channel.
+ *
+ * Example values include [""] (for protocols where several address fields are implicitly searched)
+ * or ["x-n-given", "x-n-family", "nickname", "email"] (for XMPP XEP-0055, without extensibility via
+ * Data Forms).
+ *
+ * This method requires ContactSearchChannel::FeatureCore to be ready.
+ *
+ * \return The supported search keys.
+ * \sa limit(), search()
+ */
+QStringList ContactSearchChannel::availableSearchKeys() const
+{
+ return mPriv->availableSearchKeys;
+}
+
+/**
+ * Return the DNS name of the server being searched by this channel.
+ *
+ * This method requires ContactSearchChannel::FeatureCore to be ready.
+ *
+ * \return For protocols which support searching for contacts on multiple servers with different DNS
+ * names (like XMPP), the DNS name of the server being searched by this channel, e.g.
+ * "characters.shakespeare.lit". Otherwise, an empty string.
+ */
+QString ContactSearchChannel::server() const
+{
+ return mPriv->server;
+}
+
+/**
+ * Send a request to start a search for contacts on this connection.
+ *
+ * This may only be called while the searchState() is #ChannelContactSearchStateNotStarted;
+ * a valid search request will cause the searchStateChanged() signal to be emitted with the
+ * state #ChannelContactSearchStateInProgress.
+ *
+ * Search results are signalled by searchResultReceived().
+ *
+ * This method requires ContactSearchChannel::FeatureCore to be ready.
+ *
+ * This is an overloaded method for search(const ContactSearchMap &searchTerms).
+ *
+ * \param searchKey The search key.
+ * \param searchTerm The search term.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the search has started.
+ * \sa searchState(), searchStateChanged(), searchResultReceived()
+ */
+PendingOperation *ContactSearchChannel::search(const QString &searchKey, const QString &searchTerm)
+{
+ ContactSearchMap searchTerms;
+ searchTerms.insert(searchKey, searchTerm);
+ return search(searchTerms);
+}
+
+/**
+ * Send a request to start a search for contacts on this connection.
+ *
+ * This may only be called while the searchState() is #ChannelContactSearchStateNotStarted;
+ * a valid search request will cause the searchStateChanged() signal to be emitted with the
+ * state #ChannelContactSearchStateInProgress.
+ *
+ * Search results are signalled by searchResultReceived().
+ *
+ * This method requires ContactSearchChannel::FeatureCore to be ready.
+ *
+ * \param terms The search terms.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the search has started.
+ * \sa searchState(), searchStateChanged(), searchResultReceived()
+ */
+PendingOperation *ContactSearchChannel::search(const ContactSearchMap &terms)
+{
+ if (!isReady(FeatureCore)) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ ContactSearchChannelPtr(this));
+ }
+
+ if (searchState() != ChannelContactSearchStateNotStarted) {
+ warning() << "ContactSearchChannel::search called with "
+ "searchState() != ChannelContactSearchStateNotStarted. Doing nothing";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Search already started"),
+ ContactSearchChannelPtr(this));
+ }
+
+ return new PendingSearch(ContactSearchChannelPtr(this),
+ mPriv->contactSearchInterface->Search(terms));
+}
+
+/**
+ * Request that a search which searchState() is ChannelContactSearchStateMoreAvailable
+ * move back to state ChannelContactSearchStateInProgress and continue listing up to limit()
+ * more results.
+ */
+void ContactSearchChannel::continueSearch()
+{
+ if (!isReady(FeatureCore)) {
+ return;
+ }
+
+ if (searchState() != ChannelContactSearchStateMoreAvailable) {
+ warning() << "ContactSearchChannel::continueSearch called with "
+ "searchState() != ChannelContactSearchStateMoreAvailable. Doing nothing";
+ return;
+ }
+
+ (void) new PendingVoid(mPriv->contactSearchInterface->More(),
+ ContactSearchChannelPtr(this));
+}
+
+/**
+ * Stop the current search.
+ *
+ * This may not be called while the searchState() is #ChannelContactSearchStateNotStarted.
+ * If called while the searchState() is #ChannelContactSearchStateInProgress,
+ * searchStateChanged() will be emitted, with the state #ChannelContactSearchStateFailed and
+ * the error #TP_QT4_ERROR_CANCELLED.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa searchState(), searchStateChanged()
+ */
+void ContactSearchChannel::stopSearch()
+{
+ if (!isReady(FeatureCore)) {
+ return;
+ }
+
+ if (searchState() != ChannelContactSearchStateInProgress &&
+ searchState() != ChannelContactSearchStateMoreAvailable) {
+ warning() << "ContactSearchChannel::stopSearch called with "
+ "searchState() != ChannelContactSearchStateInProgress or "
+ "ChannelContactSearchStateMoreAvailable. Doing nothing";
+ return;
+ }
+
+ (void) new PendingVoid(mPriv->contactSearchInterface->Stop(),
+ ContactSearchChannelPtr(this));
+}
+
+void ContactSearchChannel::gotProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ QVariantMap props = reply.value();
+ mPriv->extractImmutableProperties(props);
+
+ mPriv->searchState = qdbus_cast<uint>(props[QLatin1String("SearchState")]);
+
+ debug() << "Got reply to Properties::GetAll(ContactSearchChannel)";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ warning().nospace() << "Properties::GetAll(ContactSearchChannel) failed "
+ "with " << reply.error().name() << ": " << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void ContactSearchChannel::gotSearchState(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ mPriv->searchState = qdbus_cast<uint>(reply.value());
+
+ debug() << "Got reply to Properties::Get(SearchState)";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ warning().nospace() << "Properties::Get(SearchState) failed "
+ "with " << reply.error().name() << ": " << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void ContactSearchChannel::onSearchStateChanged(uint state, const QString &error,
+ const QVariantMap &details)
+{
+ mPriv->searchStateChangeQueue.enqueue(Private::SearchStateChangeInfo(state, error, details));
+ mPriv->signalsQueue.enqueue(&Private::processSearchStateChangeQueue);
+ mPriv->processSignalsQueue();
+}
+
+void ContactSearchChannel::onSearchResultReceived(const ContactSearchResultMap &result)
+{
+ mPriv->searchResultQueue.enqueue(result);
+ mPriv->signalsQueue.enqueue(&Private::processSearchResultQueue);
+ mPriv->processSignalsQueue();
+}
+
+void ContactSearchChannel::gotSearchResultContacts(PendingOperation *op)
+{
+ PendingContacts *pc = qobject_cast<PendingContacts *>(op);
+
+ const ContactSearchResultMap &result = mPriv->searchResultQueue.dequeue();
+
+ if (!pc->isValid()) {
+ warning().nospace() << "Getting search result contacts "
+ "failed with " << pc->errorName() << ":" <<
+ pc->errorMessage() << ". Ignoring search result";
+ mPriv->processingSignalsQueue = false;
+ mPriv->processSignalsQueue();
+ return;
+ }
+
+ const QList<ContactPtr> &contacts = pc->contacts();
+ Q_ASSERT(result.count() == contacts.count());
+
+ SearchResult ret;
+ uint i = 0;
+ for (ContactSearchResultMap::const_iterator it = result.constBegin();
+ it != result.constEnd();
+ ++it, ++i) {
+ ret.insert(contacts.at(i), Contact::InfoFields(it.value()));
+ }
+ emit searchResultReceived(ret);
+
+ mPriv->processingSignalsQueue = false;
+ mPriv->processSignalsQueue();
+}
+
+/**
+ * \fn void ContactSearchChannel::searchStateChanged(Tp::ChannelContactSearchState state,
+ * const QString &errorName,
+ * const Tp::ContactSearchChannel::SearchStateChangeDetails &details)
+ *
+ * Emitted when the value of searchState() changes.
+ *
+ * \param state The new state.
+ * \param errorName The name of the error if any.
+ * \param details The details for the state change.
+ * \sa searchState()
+ */
+
+/**
+ * \fn void ContactSearchChannel::searchResultReceived(
+ * const Tp::ContactSearchChannel::SearchResult &result)
+ *
+ * Emitted when a result for a search is received. It can be emitted multiple times
+ * until the searchState() goes to #ChannelContactSearchStateCompleted or
+ * #ChannelContactSearchStateFailed.
+ *
+ * \param result The search result.
+ * \sa searchState()
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/contact-search-channel.h b/qt4/TelepathyQt4/contact-search-channel.h
new file mode 100644
index 000000000..f70f9d22a
--- /dev/null
+++ b/qt4/TelepathyQt4/contact-search-channel.h
@@ -0,0 +1,114 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_search_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_search_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT ContactSearchChannel : public Channel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ContactSearchChannel)
+
+public:
+ static const Feature FeatureCore;
+
+ class SearchStateChangeDetails
+ {
+ public:
+ SearchStateChangeDetails();
+ SearchStateChangeDetails(const SearchStateChangeDetails &other);
+ ~SearchStateChangeDetails();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ SearchStateChangeDetails &operator=(const SearchStateChangeDetails &other);
+
+ bool hasDebugMessage() const { return allDetails().contains(QLatin1String("debug-message")); }
+ QString debugMessage() const { return qdbus_cast<QString>(allDetails().value(QLatin1String("debug-message"))); }
+
+ QVariantMap allDetails() const;
+
+ private:
+ friend class ContactSearchChannel;
+
+ TELEPATHY_QT4_NO_EXPORT SearchStateChangeDetails(const QVariantMap &details);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ typedef QHash<ContactPtr, Contact::InfoFields> SearchResult;
+
+ static ContactSearchChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~ContactSearchChannel();
+
+ ChannelContactSearchState searchState() const;
+ uint limit() const;
+ QStringList availableSearchKeys() const;
+ QString server() const;
+
+ PendingOperation *search(const QString &searchKey, const QString &searchTerm);
+ PendingOperation *search(const ContactSearchMap &searchTerms);
+ void continueSearch();
+ void stopSearch();
+
+Q_SIGNALS:
+ void searchStateChanged(Tp::ChannelContactSearchState state, const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details);
+ void searchResultReceived(const Tp::ContactSearchChannel::SearchResult &result);
+
+protected:
+ ContactSearchChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties, const Feature &coreFeature);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotProperties(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void gotSearchState(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onSearchStateChanged(uint state, const QString &error, const QVariantMap &details);
+ TELEPATHY_QT4_NO_EXPORT void onSearchResultReceived(const Tp::ContactSearchResultMap &result);
+ TELEPATHY_QT4_NO_EXPORT void gotSearchResultContacts(Tp::PendingOperation *op);
+
+private:
+ class PendingSearch;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/contact.cpp b/qt4/TelepathyQt4/contact.cpp
new file mode 100644
index 000000000..e8552467f
--- /dev/null
+++ b/qt4/TelepathyQt4/contact.cpp
@@ -0,0 +1,1352 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Contact>
+
+#include "TelepathyQt4/_gen/contact.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/AvatarData>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/ContactCapabilities>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/LocationInfo>
+#include <TelepathyQt4/PendingContactInfo>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/Presence>
+#include <TelepathyQt4/ReferencedHandles>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT Contact::Private
+{
+ Private(Contact *parent, ContactManager *manager,
+ const ReferencedHandles &handle)
+ : parent(parent),
+ manager(manager),
+ handle(handle),
+ caps(manager->supportedFeatures().contains(Contact::FeatureCapabilities) ?
+ ContactCapabilities(true) : ContactCapabilities(
+ manager->connection()->capabilities().allClassSpecs(), false)),
+ isContactInfoKnown(false), isAvatarTokenKnown(false),
+ subscriptionState(SubscriptionStateUnknown),
+ publishState(SubscriptionStateUnknown),
+ blocked(false)
+ {
+ }
+
+ void updateAvatarData();
+
+ Contact *parent;
+
+ QWeakPointer<ContactManager> manager;
+ ReferencedHandles handle;
+ QString id;
+
+ Features requestedFeatures;
+ Features actualFeatures;
+
+ QString alias;
+ Presence presence;
+ ContactCapabilities caps;
+ LocationInfo location;
+
+ bool isContactInfoKnown;
+ InfoFields info;
+
+ bool isAvatarTokenKnown;
+ QString avatarToken;
+ AvatarData avatarData;
+
+ SubscriptionState subscriptionState;
+ SubscriptionState publishState;
+ QString publishStateMessage;
+ bool blocked;
+
+ QSet<QString> groups;
+};
+
+void Contact::Private::updateAvatarData()
+{
+ /* If token is NULL, it means that CM doesn't know the token. In that case we
+ * have to request the avatar data to get the token. This happens with XMPP
+ * for offline contacts. We don't want to bypass the avatar cache, so we won't
+ * update avatar. */
+ if (avatarToken.isNull()) {
+ return;
+ }
+
+ /* If token is empty (""), it means the contact has no avatar. */
+ if (avatarToken.isEmpty()) {
+ debug() << "Contact" << parent->id() << "has no avatar";
+ avatarData = AvatarData();
+ emit parent->avatarDataChanged(avatarData);
+ return;
+ }
+
+ parent->manager()->requestContactAvatars(QList<ContactPtr>() << ContactPtr(parent));
+}
+
+struct TELEPATHY_QT4_NO_EXPORT Contact::InfoFields::Private : public QSharedData
+{
+ Private(const ContactInfoFieldList &allFields)
+ : allFields(allFields) {}
+
+ ContactInfoFieldList allFields;
+};
+
+/**
+ * \class Contact::InfoFields
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/contact.h <TelepathyQt4/Contact>
+ *
+ * \brief The Contact::InfoFields class represents the information of a
+ * Telepathy contact.
+ */
+
+/**
+ * Construct a info fields instance with the given fields. The instance will indicate that
+ * it is valid.
+ */
+Contact::InfoFields::InfoFields(const ContactInfoFieldList &allFields)
+ : mPriv(new Private(allFields))
+{
+}
+
+/**
+ * Constructs a new invalid InfoFields instance.
+ */
+Contact::InfoFields::InfoFields()
+{
+}
+
+/**
+ * Copy constructor.
+ */
+Contact::InfoFields::InfoFields(const Contact::InfoFields &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+Contact::InfoFields::~InfoFields()
+{
+}
+
+/**
+ * Assignment operator.
+ */
+Contact::InfoFields &Contact::InfoFields::operator=(const Contact::InfoFields &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+/**
+ * Return a list containing all fields whose name are \a name.
+ *
+ * \param name The name used to match the fields.
+ * \return A list of ContactInfoField objects.
+ */
+ContactInfoFieldList Contact::InfoFields::fields(const QString &name) const
+{
+ if (!isValid()) {
+ return ContactInfoFieldList();
+ }
+
+ ContactInfoFieldList ret;
+ foreach (const ContactInfoField &field, mPriv->allFields) {
+ if (field.fieldName == name) {
+ ret.append(field);
+ }
+ }
+ return ret;
+}
+
+/**
+ * Return a list containing all fields describing the contact information.
+ *
+ * \return The contact information as a list of ContactInfoField objects.
+ */
+ContactInfoFieldList Contact::InfoFields::allFields() const
+{
+ return isValid() ? mPriv->allFields : ContactInfoFieldList();
+}
+
+/**
+ * \class Contact
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/contact.h <TelepathyQt4/Contact>
+ *
+ * \brief The Contact class represents a Telepathy contact.
+ *
+ * The accessor functions on this object (id(), alias(), and so on) don't make any D-Bus calls;
+ * instead, they return/use values cached from a previous introspection run.
+ * The introspection process populates their values in the most efficient way possible based on what
+ * the service implements.
+ *
+ * To avoid unnecessary D-Bus traffic, some accessors only return valid
+ * information after specific features have been enabled.
+ * For instance, to retrieve the contact avatar token, it is necessary to
+ * enable the feature Contact::FeatureAvatarToken.
+ * See the individual methods descriptions for more details.
+ *
+ * Contact features can be enabled by constructing a ContactFactory and enabling
+ * the desired features, and passing it to AccountManager, Account or ClientRegistrar
+ * when creating them as appropriate. However, if a particular
+ * feature is only ever used in a specific circumstance, such as an user opening
+ * some settings dialog separate from the general view of the application,
+ * features can be later enabled as needed by calling ContactManager::upgradeContacts() with the
+ * additional features, and waiting for the resulting PendingOperation to finish.
+ *
+ * As an addition to accessors, signals are emitted to indicate that properties have
+ * changed, for example aliasChanged(), avatarTokenChanged(), etc.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature used in order to access contact alias info.
+ *
+ * See alias specific methods' documentation for more details.
+ *
+ * \sa alias(), aliasChanged()
+ */
+const Feature Contact::FeatureAlias = Feature(QLatin1String(Contact::staticMetaObject.className()), 0, false);
+
+/**
+ * Feature used in order to access contact avatar data info.
+ *
+ * Enabling this feature will also enable FeatureAvatarToken.
+ *
+ * See avatar data specific methods' documentation for more details.
+ *
+ * \sa avatarData(), avatarDataChanged()
+ */
+const Feature Contact::FeatureAvatarData = Feature(QLatin1String(Contact::staticMetaObject.className()), 1, false);
+
+/**
+ * Feature used in order to access contact avatar token info.
+ *
+ * See avatar token specific methods' documentation for more details.
+ *
+ * \sa isAvatarTokenKnown(), avatarToken(), avatarTokenChanged()
+ */
+const Feature Contact::FeatureAvatarToken = Feature(QLatin1String(Contact::staticMetaObject.className()), 2, false);
+
+/**
+ * Feature used in order to access contact capabilities info.
+ *
+ * See capabilities specific methods' documentation for more details.
+ *
+ * \sa capabilities(), capabilitiesChanged()
+ */
+const Feature Contact::FeatureCapabilities = Feature(QLatin1String(Contact::staticMetaObject.className()), 3, false);
+
+/**
+ * Feature used in order to access contact info fields.
+ *
+ * See info fields specific methods' documentation for more details.
+ *
+ * \sa infoFields(), infoFieldsChanged()
+ */
+const Feature Contact::FeatureInfo = Feature(QLatin1String(Contact::staticMetaObject.className()), 4, false);
+
+/**
+ * Feature used in order to access contact location info.
+ *
+ * See location specific methods' documentation for more details.
+ *
+ * \sa location(), locationUpdated()
+ */
+const Feature Contact::FeatureLocation = Feature(QLatin1String(Contact::staticMetaObject.className()), 5, false);
+
+/**
+ * Feature used in order to access contact presence info.
+ *
+ * See presence specific methods' documentation for more details.
+ *
+ * \sa presence(), presenceChanged()
+ */
+const Feature Contact::FeatureSimplePresence = Feature(QLatin1String(Contact::staticMetaObject.className()), 6, false);
+
+/**
+ * Feature used in order to access contact roster groups.
+ *
+ * See roster groups specific methods' documentation for more details.
+ *
+ * \sa groups(), addedToGroup(), removedFromGroup()
+ */
+const Feature Contact::FeatureRosterGroups = Feature(QLatin1String(Contact::staticMetaObject.className()), 7, false);
+
+/**
+ * Construct a new Contact object.
+ *
+ * \param manager ContactManager owning this contact.
+ * \param handle The contact handle.
+ * \param requestedFeatures The contact requested features.
+ * \param attributes The contact attributes.
+ */
+Contact::Contact(ContactManager *manager, const ReferencedHandles &handle,
+ const Features &requestedFeatures, const QVariantMap &attributes)
+ : Object(),
+ mPriv(new Private(this, manager, handle))
+{
+ mPriv->requestedFeatures.unite(requestedFeatures);
+ mPriv->id = qdbus_cast<QString>(attributes[
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id")]);
+}
+
+/**
+ * Class destructor.
+ */
+Contact::~Contact()
+{
+ debug() << "Contact" << id() << "destroyed";
+ delete mPriv;
+}
+
+/**
+ * Return the contact nanager owning this contact.
+ *
+ * \return A pointer to the ContactManager object.
+ */
+ContactManagerPtr Contact::manager() const
+{
+ return ContactManagerPtr(mPriv->manager);
+}
+
+/**
+ * Return the handle of this contact.
+ *
+ * \return The handle as a ReferencedHandles object.
+ */
+ReferencedHandles Contact::handle() const
+{
+ return mPriv->handle;
+}
+
+/**
+ * Return the identifier of this contact.
+ *
+ * \return The identifier.
+ */
+QString Contact::id() const
+{
+ return mPriv->id;
+}
+
+/**
+ * Return the features requested on this contact.
+ *
+ * \return The requested features as a set of Feature objects.
+ */
+Features Contact::requestedFeatures() const
+{
+ return mPriv->requestedFeatures;
+}
+
+/**
+ * Return the features that are actually enabled on this contact.
+ *
+ * \return The actual features as a set of Feature objects.
+ */
+Features Contact::actualFeatures() const
+{
+ return mPriv->actualFeatures;
+}
+
+/**
+ * Return the alias of this contact.
+ *
+ * Change notification is via the aliasChanged() signal.
+ *
+ * This method requires Contact::FeatureAlias to be ready.
+ *
+ * \return The alias.
+ */
+QString Contact::alias() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAlias)) {
+ warning() << "Contact::alias() used on" << this
+ << "for which FeatureAlias hasn't been requested - returning id";
+ return id();
+ }
+
+ return mPriv->alias;
+}
+
+/**
+ * Return whether the avatar token of this contact is known.
+ *
+ * This method requires Contact::FeatureAvatarToken to be ready.
+ *
+ * \return \c true if the avatar token is known, \c false otherwise.
+ * \sa avatarToken()
+ */
+bool Contact::isAvatarTokenKnown() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAvatarToken)) {
+ warning() << "Contact::isAvatarTokenKnown() used on" << this
+ << "for which FeatureAvatarToken hasn't been requested - returning false";
+ return false;
+ }
+
+ return mPriv->isAvatarTokenKnown;
+}
+
+/**
+ * Return the avatar token for this contact.
+ *
+ * Change notification is via the avatarTokenChanged() signal.
+ *
+ * This method requires Contact::FeatureAvatarToken to be ready.
+ *
+ * \return The avatar token.
+ * \sa isAvatarTokenKnown(), avatarTokenChanged(), avatarData()
+ */
+QString Contact::avatarToken() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAvatarToken)) {
+ warning() << "Contact::avatarToken() used on" << this
+ << "for which FeatureAvatarToken hasn't been requested - returning \"\"";
+ return QString();
+ } else if (!isAvatarTokenKnown()) {
+ warning() << "Contact::avatarToken() used on" << this
+ << "for which the avatar token is not (yet) known - returning \"\"";
+ return QString();
+ }
+
+ return mPriv->avatarToken;
+}
+
+/**
+ * Return the actual avatar for this contact.
+ *
+ * Change notification is via the avatarDataChanged() signal.
+ *
+ * This method requires Contact::FeatureAvatarData to be ready.
+ *
+ * \return The avatar as an AvatarData object.
+ * \sa avatarDataChanged(), avatarToken()
+ */
+AvatarData Contact::avatarData() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAvatarData)) {
+ warning() << "Contact::avatarData() used on" << this
+ << "for which FeatureAvatarData hasn't been requested - returning \"\"";
+ return AvatarData();
+ }
+
+ return mPriv->avatarData;
+}
+
+/**
+ * Start a request to retrieve the avatar for this contact.
+ *
+ * Force the request of the avatar data. This method returns directly, emitting
+ * avatarTokenChanged() and avatarDataChanged() signals once the token and data are
+ * fetched from the server.
+ *
+ * This is only useful if the avatar token is unknown; see isAvatarTokenKnown().
+ * It happens in the case of offline XMPP contacts, because the server does not
+ * send the token for them and an explicit request of the avatar data is needed.
+ *
+ * This method requires Contact::FeatureAvatarData to be ready.
+ *
+ * \sa avatarData(), avatarDataChanged(), avatarToken(), avatarTokenChanged()
+ */
+void Contact::requestAvatarData()
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAvatarData)) {
+ warning() << "Contact::requestAvatarData() used on" << this
+ << "for which FeatureAvatarData hasn't been requested - returning \"\"";
+ return;
+ }
+
+ return manager()->requestContactAvatars(QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Return the actual presence of this contact.
+ *
+ * Change notification is via the presenceChanged() signal.
+ *
+ * This method requires Contact::FeatureSimplePresence to be ready.
+ *
+ * \return The presence as a Presence object.
+ */
+Presence Contact::presence() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureSimplePresence)) {
+ warning() << "Contact::presence() used on" << this
+ << "for which FeatureSimplePresence hasn't been requested - returning Unknown";
+ return Presence();
+ }
+
+ return mPriv->presence;
+}
+
+/**
+ * Return the capabilities for this contact.
+ *
+ * User interfaces can use this information to show or hide UI components.
+ *
+ * If ContactManager::supportedFeatures() contains Contact::FeatureCapabilities,
+ * the returned object will be a ContactCapabilities object, where
+ * CapabilitiesBase::isSpecificToContact() will be \c true; if that feature
+ * isn't present, this returned object is the subset of
+ * Contact::manager()::connection()::capabilities()
+ * and CapabilitiesBase::isSpecificToContact() will be \c false.
+ *
+ * Change notification is via the capabilitiesChanged() signal.
+ *
+ * This method requires Contact::FeatureCapabilities to be ready.
+ *
+ * @return An object representing the contact capabilities.
+ */
+ContactCapabilities Contact::capabilities() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureCapabilities)) {
+ warning() << "Contact::capabilities() used on" << this
+ << "for which FeatureCapabilities hasn't been requested - returning 0";
+ return ContactCapabilities(false);
+ }
+
+ return mPriv->caps;
+}
+
+/**
+ * Return the location for this contact.
+ *
+ * Change notification is via the locationUpdated() signal.
+ *
+ * This method requires Contact::FeatureLocation to be ready.
+ *
+ * \return The contact location as a LocationInfo object.
+ */
+LocationInfo Contact::location() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureLocation)) {
+ warning() << "Contact::location() used on" << this
+ << "for which FeatureLocation hasn't been requested - returning 0";
+ return LocationInfo();
+ }
+
+ return mPriv->location;
+}
+
+/**
+ * Return whether the info card for this contact has been received.
+ *
+ * With some protocols (notably XMPP) information is not pushed from the server
+ * and must be requested explicitely using refreshInfo() or requestInfo(). This
+ * method can be used to know if the information is received from the server
+ * or if an explicit request is needed.
+ *
+ * This method requires Contacat::FeatureInfo to be ready.
+ *
+ * \return \c true if the information is known; \c false otherwise.
+ */
+bool Contact::isContactInfoKnown() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureInfo)) {
+ warning() << "Contact::isContactInfoKnown() used on" << this
+ << "for which FeatureInfo hasn't been requested - returning false";
+ return false;
+ }
+
+ return mPriv->isContactInfoKnown;
+}
+
+/**
+ * Return the information for this contact.
+ *
+ * Note that this method only return cached information. In order to refresh the
+ * information use refreshInfo().
+ *
+ * Change notification is via the infoFieldsChanged() signal.
+ *
+ * This method requires Contact::FeatureInfo to be ready.
+ *
+ * \return The contact info as a Contact::InfoFields object.
+ */
+Contact::InfoFields Contact::infoFields() const
+{
+ if (!mPriv->requestedFeatures.contains(FeatureInfo)) {
+ warning() << "Contact::infoFields() used on" << this
+ << "for which FeatureInfo hasn't been requested - returning empty "
+ "InfoFields";
+ return InfoFields();
+ }
+
+ return mPriv->info;
+}
+
+/**
+ * Refresh information for the given contact.
+ *
+ * Once the information is retrieved infoFieldsChanged() will be emitted.
+ *
+ * This method requires Contact::FeatureInfo to be ready.
+ *
+ * \return A PendingOperation, which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa infoFieldsChanged()
+ */
+PendingOperation *Contact::refreshInfo()
+{
+ ConnectionPtr conn = manager()->connection();
+ if (!mPriv->requestedFeatures.contains(FeatureInfo)) {
+ warning() << "Contact::refreshInfo() used on" << this
+ << "for which FeatureInfo hasn't been requested - failing";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("FeatureInfo needs to be ready in order to "
+ "use this method"),
+ ContactPtr(this));
+ }
+
+ return manager()->refreshContactInfo(QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Start a request to retrieve the information for this contact.
+ *
+ * This method is useful for UIs that don't care about notification of changes
+ * in the contact information but want to show the contact information
+ * (e.g. right-click on a contact and show the contact info).
+ *
+ * \return A PendingContactInfo, which will emit PendingContactInfo::finished
+ * when the information has been retrieved or an error occurred.
+ */
+PendingContactInfo *Contact::requestInfo()
+{
+ return new PendingContactInfo(ContactPtr(this));
+}
+
+/**
+ * Return whether the presence subscription state of this contact is known.
+ *
+ * \return \c true if the presence subscription state is known, \c false otherwise.
+ * \sa subscriptionState(), isSubscriptionRejected()
+ */
+bool Contact::isSubscriptionStateKnown() const
+{
+ return mPriv->subscriptionState != SubscriptionStateUnknown;
+}
+
+/**
+ * Return whether a request to see this contact's presence was denied.
+ *
+ * \return \c if the a request to see the presence subscription was denied, \c false otherwise.
+ * \sa isSubscriptionStateKnown(), subscriptionState()
+ */
+bool Contact::isSubscriptionRejected() const
+{
+ return mPriv->subscriptionState == SubscriptionStateRemovedRemotely;
+}
+
+/**
+ * Return the presence subscription state of this contact (i.e. whether the local user can retrieve
+ * information about this contact's presence).
+ *
+ * \return The presence subscription state as Contact::PresenceState.
+ * \sa isSubscriptionStateKnown(), isSubscriptionRejected()
+ */
+Contact::PresenceState Contact::subscriptionState() const
+{
+ return subscriptionStateToPresenceState(mPriv->subscriptionState);
+}
+
+/**
+ * Return whether the presence publish state of this contact is known.
+ *
+ * \return \c true if the presence publish state is known, \c false otherwise.
+ * \sa publishState(), isPublishCancelled()
+ */
+bool Contact::isPublishStateKnown() const
+{
+ return mPriv->publishState != SubscriptionStateUnknown;
+}
+
+/**
+ * Return whether a request to publish presence information to this contact was cancelled.
+ *
+ * \return \c true if a request to publish presence information was cancelled,
+ * \c false otherwise.
+ * \sa isPublishStateKnown(), publishState()
+ */
+bool Contact::isPublishCancelled() const
+{
+ return mPriv->publishState == SubscriptionStateRemovedRemotely;
+}
+
+/**
+ * Return the presence publish state of this contact (i.e. whether this contact can retrieve
+ * information about the local user's presence).
+ *
+ * \return The presence publish state as Contact::PresenceState.
+ * \sa isSubscriptionStateKnown(), isSubscriptionRejected()
+ */
+Contact::PresenceState Contact::publishState() const
+{
+ return subscriptionStateToPresenceState(mPriv->publishState);
+}
+
+/**
+ * If the publishState() is Contact::PresenceStateAsk, return an optional message that was sent
+ * by the contact asking to receive the local user's presence; omitted if none was given.
+ *
+ * \return The message that was sent by the contact asking to receive the local user's presence.
+ * \sa publishState()
+ */
+QString Contact::publishStateMessage() const
+{
+ return mPriv->publishStateMessage;
+}
+
+/**
+ * Start a request that this contact allow the local user to subscribe to their presence (i.e. that
+ * this contact's subscribe attribute becomes Contact::PresenceStateYes)
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa subscriptionState(), removePresenceSubscription()
+ */
+PendingOperation *Contact::requestPresenceSubscription(const QString &message)
+{
+ return manager()->requestPresenceSubscription(QList<ContactPtr>() << ContactPtr(this), message);
+}
+
+/**
+ * Start a request for the local user to stop receiving presence from this contact.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa subscriptionState(), requestPresenceSubscription()
+ */
+PendingOperation *Contact::removePresenceSubscription(const QString &message)
+{
+ return manager()->removePresenceSubscription(QList<ContactPtr>() << ContactPtr(this), message);
+}
+
+/**
+ * Start a request to authorize this contact's request to see the local user presence
+ * (i.e. that this contact publish attribute becomes Contact::PresenceStateYes).
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa publishState(), removePresencePublication()
+ */
+PendingOperation *Contact::authorizePresencePublication(const QString &message)
+{
+ return manager()->authorizePresencePublication(QList<ContactPtr>() << ContactPtr(this), message);
+}
+
+/**
+ * Start a request for the local user to stop sending presence to this contact.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request has been made.
+ * \sa publishState(), authorizePresencePublication()
+ */
+PendingOperation *Contact::removePresencePublication(const QString &message)
+{
+ return manager()->removePresencePublication(QList<ContactPtr>() << ContactPtr(this), message);
+}
+
+/**
+ * Return whether this contact is blocked.
+ *
+ * Change notification is via the blockStatusChanged() signal.
+ *
+ * \return \c true if blocked, \c false otherwise.
+ * \sa block()
+ */
+bool Contact::isBlocked() const
+{
+ return mPriv->blocked;
+}
+
+/**
+ * \deprecated Use block() instead.
+ */
+PendingOperation *Contact::block(bool value)
+{
+ return value ? manager()->blockContacts(QList<ContactPtr>() << ContactPtr(this))
+ : manager()->unblockContacts(QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Block this contact. Blocked contacts cannot send messages to the user;
+ * depending on the protocol, blocking a contact may have other effects.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to take the requested action.
+ * \sa blockAndReportAbuse(), unblock()
+ */
+PendingOperation *Contact::block()
+{
+ return manager()->blockContacts(QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Block this contact and additionally report abusive behaviour
+ * to the server.
+ *
+ * If reporting abusive behaviour is not supported by the protocol,
+ * this method has the same effect as block().
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to take the requested action.
+ * \sa ContactManager::canReportAbuse(), block(), unblock()
+ */
+PendingOperation *Contact::blockAndReportAbuse()
+{
+ return manager()->blockContactsAndReportAbuse(QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Unblock this contact.
+ *
+ * This method requires Connection::FeatureRoster to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to take the requested action.
+ * \sa block(), blockAndReportAbuse()
+ */
+PendingOperation *Contact::unblock()
+{
+ return manager()->unblockContacts(QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Return the names of the user-defined roster groups to which the contact
+ * belongs.
+ *
+ * Change notification is via the addedToGroup() and removedFromGroup() signals.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \return A list of user-defined contact list groups names.
+ * \sa addToGroup(), removedFromGroup()
+ */
+QStringList Contact::groups() const
+{
+ return mPriv->groups.toList();
+}
+
+/**
+ * Attempt to add the contact to the user-defined contact list
+ * group named \a group.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to to add the contact to the user-defined contact
+ * list group.
+ * \sa groups(), removeFromGroup()
+ */
+PendingOperation *Contact::addToGroup(const QString &group)
+{
+ return manager()->addContactsToGroup(group, QList<ContactPtr>() << ContactPtr(this));
+}
+
+/**
+ * Attempt to remove the contact from the user-defined contact list
+ * group named \a group.
+ *
+ * This method requires Connection::FeatureRosterGroups to be ready.
+ *
+ * \param group The group name.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when an attempt has been made to to remote the contact to the user-defined contact
+ * list group.
+ * \sa groups(), addToGroup()
+ */
+PendingOperation *Contact::removeFromGroup(const QString &group)
+{
+ return manager()->removeContactsFromGroup(group, QList<ContactPtr>() << ContactPtr(this));
+}
+
+void Contact::augment(const Features &requestedFeatures, const QVariantMap &attributes)
+{
+ mPriv->requestedFeatures.unite(requestedFeatures);
+
+ mPriv->id = qdbus_cast<QString>(attributes[
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id")]);
+
+ if (attributes.contains(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST +
+ QLatin1String("/subscribe"))) {
+ uint subscriptionState = qdbus_cast<uint>(attributes.value(
+ TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/subscribe")));
+ setSubscriptionState((SubscriptionState) subscriptionState);
+ }
+
+ if (attributes.contains(TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST +
+ QLatin1String("/publish"))) {
+ uint publishState = qdbus_cast<uint>(attributes.value(
+ TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/publish")));
+ QString publishRequest = qdbus_cast<QString>(attributes.value(
+ TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/publish-request")));
+ setPublishState((SubscriptionState) publishState, publishRequest);
+ }
+
+ foreach (const Feature &feature, requestedFeatures) {
+ QString maybeAlias;
+ SimplePresence maybePresence;
+ RequestableChannelClassList maybeCaps;
+ QVariantMap maybeLocation;
+ ContactInfoFieldList maybeInfo;
+
+ if (feature == FeatureAlias) {
+ maybeAlias = qdbus_cast<QString>(attributes.value(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_ALIASING "/alias")));
+
+ if (!maybeAlias.isEmpty()) {
+ receiveAlias(maybeAlias);
+ } else if (mPriv->alias.isEmpty()) {
+ mPriv->alias = mPriv->id;
+ }
+ } else if (feature == FeatureAvatarData) {
+ if (manager()->supportedFeatures().contains(FeatureAvatarData)) {
+ mPriv->actualFeatures.insert(FeatureAvatarData);
+ mPriv->updateAvatarData();
+ }
+ } else if (feature == FeatureAvatarToken) {
+ if (attributes.contains(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS "/token"))) {
+ receiveAvatarToken(qdbus_cast<QString>(attributes.value(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS "/token"))));
+ } else {
+ if (manager()->supportedFeatures().contains(FeatureAvatarToken)) {
+ // AvatarToken being supported but not included in the mapping indicates
+ // that the avatar token is not known - however, the feature is working fine
+ mPriv->actualFeatures.insert(FeatureAvatarToken);
+ }
+ // In either case, the avatar token can't be known
+ mPriv->isAvatarTokenKnown = false;
+ mPriv->avatarToken = QLatin1String("");
+ }
+ } else if (feature == FeatureCapabilities) {
+ maybeCaps = qdbus_cast<RequestableChannelClassList>(attributes.value(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES "/capabilities")));
+
+ if (!maybeCaps.isEmpty()) {
+ receiveCapabilities(maybeCaps);
+ } else {
+ if (manager()->supportedFeatures().contains(FeatureCapabilities) &&
+ mPriv->requestedFeatures.contains(FeatureCapabilities)) {
+ // Capabilities being supported but not updated in the
+ // mapping indicates that the capabilities is not known -
+ // however, the feature is working fine.
+ mPriv->actualFeatures.insert(FeatureCapabilities);
+ }
+ }
+ } else if (feature == FeatureInfo) {
+ maybeInfo = qdbus_cast<ContactInfoFieldList>(attributes.value(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACT_INFO "/info")));
+
+ if (!maybeInfo.isEmpty()) {
+ receiveInfo(maybeInfo);
+ } else {
+ if (manager()->supportedFeatures().contains(FeatureInfo) &&
+ mPriv->requestedFeatures.contains(FeatureInfo)) {
+ // Info being supported but not updated in the
+ // mapping indicates that the info is not known -
+ // however, the feature is working fine
+ mPriv->actualFeatures.insert(FeatureInfo);
+ }
+ }
+ } else if (feature == FeatureLocation) {
+ maybeLocation = qdbus_cast<QVariantMap>(attributes.value(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_LOCATION "/location")));
+
+ if (!maybeLocation.isEmpty()) {
+ receiveLocation(maybeLocation);
+ } else {
+ if (manager()->supportedFeatures().contains(FeatureLocation) &&
+ mPriv->requestedFeatures.contains(FeatureLocation)) {
+ // Location being supported but not updated in the
+ // mapping indicates that the location is not known -
+ // however, the feature is working fine
+ mPriv->actualFeatures.insert(FeatureLocation);
+ }
+ }
+ } else if (feature == FeatureSimplePresence) {
+ maybePresence = qdbus_cast<SimplePresence>(attributes.value(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE "/presence")));
+
+ if (!maybePresence.status.isEmpty()) {
+ receiveSimplePresence(maybePresence);
+ } else {
+ mPriv->presence.setStatus(ConnectionPresenceTypeUnknown,
+ QLatin1String("unknown"), QLatin1String(""));
+ }
+ } else if (feature == FeatureRosterGroups) {
+ QStringList groups = qdbus_cast<QStringList>(attributes.value(
+ TP_QT4_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS + QLatin1String("/groups")));
+ mPriv->groups = groups.toSet();
+ } else {
+ warning() << "Unknown feature" << feature << "encountered when augmenting Contact";
+ }
+ }
+}
+
+void Contact::receiveAlias(const QString &alias)
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAlias)) {
+ return;
+ }
+
+ mPriv->actualFeatures.insert(FeatureAlias);
+
+ if (mPriv->alias != alias) {
+ mPriv->alias = alias;
+ emit aliasChanged(alias);
+ }
+}
+
+void Contact::receiveAvatarToken(const QString &token)
+{
+ setAvatarToken(token);
+
+ if (mPriv->actualFeatures.contains(FeatureAvatarData)) {
+ mPriv->updateAvatarData();
+ }
+}
+
+void Contact::setAvatarToken(const QString &token)
+{
+ if (!mPriv->requestedFeatures.contains(FeatureAvatarToken)) {
+ return;
+ }
+
+ mPriv->actualFeatures.insert(FeatureAvatarToken);
+
+ if (!mPriv->isAvatarTokenKnown || mPriv->avatarToken != token) {
+ mPriv->isAvatarTokenKnown = true;
+ mPriv->avatarToken = token;
+ emit avatarTokenChanged(mPriv->avatarToken);
+ }
+}
+
+void Contact::receiveAvatarData(const AvatarData &avatar)
+{
+ if (mPriv->avatarData.fileName != avatar.fileName) {
+ mPriv->avatarData = avatar;
+ emit avatarDataChanged(mPriv->avatarData);
+ }
+}
+
+void Contact::receiveSimplePresence(const SimplePresence &presence)
+{
+ if (!mPriv->requestedFeatures.contains(FeatureSimplePresence)) {
+ return;
+ }
+
+ mPriv->actualFeatures.insert(FeatureSimplePresence);
+
+ if (mPriv->presence.status() != presence.status ||
+ mPriv->presence.statusMessage() != presence.statusMessage) {
+ mPriv->presence.setStatus(presence);
+ emit presenceChanged(mPriv->presence);
+ }
+}
+
+void Contact::receiveCapabilities(const RequestableChannelClassList &caps)
+{
+ if (!mPriv->requestedFeatures.contains(FeatureCapabilities)) {
+ return;
+ }
+
+ mPriv->actualFeatures.insert(FeatureCapabilities);
+
+ if (mPriv->caps.allClassSpecs().bareClasses() != caps) {
+ mPriv->caps.updateRequestableChannelClasses(caps);
+ emit capabilitiesChanged(mPriv->caps);
+ }
+}
+
+void Contact::receiveLocation(const QVariantMap &location)
+{
+ if (!mPriv->requestedFeatures.contains(FeatureLocation)) {
+ return;
+ }
+
+ mPriv->actualFeatures.insert(FeatureLocation);
+
+ if (mPriv->location.allDetails() != location) {
+ mPriv->location.updateData(location);
+ emit locationUpdated(mPriv->location);
+ }
+}
+
+void Contact::receiveInfo(const ContactInfoFieldList &info)
+{
+ if (!mPriv->requestedFeatures.contains(FeatureInfo)) {
+ return;
+ }
+
+ mPriv->actualFeatures.insert(FeatureInfo);
+ mPriv->isContactInfoKnown = true;
+
+ if (mPriv->info.allFields() != info) {
+ mPriv->info = InfoFields(info);
+ emit infoFieldsChanged(mPriv->info);
+ }
+}
+
+Contact::PresenceState Contact::subscriptionStateToPresenceState(uint subscriptionState)
+{
+ switch (subscriptionState) {
+ case SubscriptionStateAsk:
+ return PresenceStateAsk;
+ case SubscriptionStateYes:
+ return PresenceStateYes;
+ default:
+ return PresenceStateNo;
+ }
+}
+
+void Contact::setSubscriptionState(SubscriptionState state)
+{
+ if (mPriv->subscriptionState == state) {
+ return;
+ }
+
+ mPriv->subscriptionState = state;
+
+ // FIXME (API/ABI break) remove signal with details
+ emit subscriptionStateChanged(subscriptionStateToPresenceState(state),
+ Channel::GroupMemberChangeDetails());
+
+ emit subscriptionStateChanged(subscriptionStateToPresenceState(state));
+}
+
+void Contact::setPublishState(SubscriptionState state, const QString &message)
+{
+ if (mPriv->publishState == state && mPriv->publishStateMessage == message) {
+ return;
+ }
+
+ mPriv->publishState = state;
+ mPriv->publishStateMessage = message;
+
+ // FIXME (API/ABI break) remove signal with details
+ QVariantMap detailsMap;
+ detailsMap.insert(QLatin1String("message"), message);
+ emit publishStateChanged(subscriptionStateToPresenceState(state),
+ Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap));
+
+ emit publishStateChanged(subscriptionStateToPresenceState(state), message);
+}
+
+void Contact::setBlocked(bool value)
+{
+ if (mPriv->blocked == value) {
+ return;
+ }
+
+ mPriv->blocked = value;
+
+ // FIXME (API/ABI break) remove signal with details
+ emit blockStatusChanged(value, Channel::GroupMemberChangeDetails());
+
+ emit blockStatusChanged(value);
+}
+
+void Contact::setAddedToGroup(const QString &group)
+{
+ if (!mPriv->groups.contains(group)) {
+ mPriv->groups.insert(group);
+ emit addedToGroup(group);
+ }
+}
+
+void Contact::setRemovedFromGroup(const QString &group)
+{
+ if (mPriv->groups.remove(group)) {
+ emit removedFromGroup(group);
+ }
+}
+
+/**
+ * \fn void Contact::aliasChanged(const QString &alias)
+ *
+ * Emitted when the value of alias() changes.
+ *
+ * \param alias The new alias of this contact.
+ * \sa alias()
+ */
+
+/**
+ * \fn void Contact::avatarTokenChanged(const QString &avatarToken)
+ *
+ * Emitted when the value of avatarToken() changes.
+ *
+ * \param avatarToken The new avatar token of this contact.
+ * \sa avatarToken()
+ */
+
+/**
+ * \fn void Contact::avatarDataChanged(const QString &avatarToken)
+ *
+ * Emitted when the value of avatarData() changes.
+ *
+ * \param avatarData The new avatar of this contact.
+ * \sa avatarData()
+ */
+
+/**
+ * \fn void Contact::presenceChanged(const Tp::Presence &presence)
+ *
+ * Emitted when the value of presence() changes.
+ *
+ * \param presence The new presence of this contact.
+ * \sa presence()
+ */
+
+/**
+ * \fn void Contact::capabilitiesChanged(const Tp::ContactCapabilities &caps)
+ *
+ * Emitted when the value of capabilities() changes.
+ *
+ * \param caps The new capabilities of this contact.
+ * \sa capabilities()
+ */
+
+/**
+ * \fn void Contact::locationUpdated(const Tp::LocationInfo &location)
+ *
+ * Emitted when the value of location() changes.
+ *
+ * \param caps The new location of this contact.
+ * \sa location()
+ */
+
+/**
+ * \fn void Contact::infoFieldsChanged(const Tp::Contact::InfoFields &infoFields)
+ *
+ * Emitted when the value of infoFields() changes.
+ *
+ * \param InfoFields The new info of this contact.
+ * \sa infoFields()
+ */
+
+/**
+ * \fn void Contact::subscriptionStateChanged(Tp::Contact::PresenceState state)
+ *
+ * Emitted when the value of subscriptionState() changes.
+ *
+ * \param state The new subscription state of this contact.
+ * \sa subscriptionState()
+ */
+
+/**
+ * \fn void Contact::subscriptionStateChanged(Tp::Contact::PresenceState state,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * \deprecated Use subscriptionStateChanged(Tp::Contact::PresenceState state) instead.
+ */
+
+/**
+ * \fn void Contact::publishStateChanged(Tp::Contact::PresenceState state, const QString &message)
+ *
+ * Emitted when the value of publishState() changes.
+ *
+ * \param state The new publish state of this contact.
+ * \sa publishState()
+ */
+
+/**
+ * \fn void Contact::publishStateChanged(Tp::Contact::PresenceState state,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * \deprecated Use publishStateChanged(Tp::Contact::PresenceState state, const QString &message) instead.
+ */
+
+/**
+ * \fn void Contact::blockStatusChanged(bool blocked)
+ *
+ * Emitted when the value of isBlocked() changes.
+ *
+ * \param status The new block status of this contact.
+ * \sa isBlocked()
+ */
+
+/**
+ * \fn void Contact::blockStatusChanged(bool blocked,
+ * const Tp::Channel::GroupMemberChangeDetails &details)
+ *
+ * \deprecated Use blockStatusChanged(bool blocked) instead.
+ */
+
+/**
+ * \fn void Contact::addedToGroup(const QString &group)
+ *
+ * Emitted when this contact is added to \a group of the contact list.
+ *
+ * \param group The group name.
+ * \sa groups(), removedFromGroup()
+ */
+
+/**
+ * \fn void Contact::removedFromGroup(const QString &group)
+ *
+ * Emitted when this contact is removed from \a group of the contact list.
+ *
+ * \param group The group name.
+ * \sa groups(), addedToGroup()
+ */
+
+void Contact::connectNotify(const char *signalName)
+{
+ if (qstrcmp(signalName, SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails))) == 0) {
+ warning() << "Connecting to deprecated signal subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)";
+ } else if (qstrcmp(signalName, SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails))) == 0) {
+ warning() << "Connecting to deprecated signal publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)";
+ } else if (qstrcmp(signalName, SIGNAL(blockStatusChanged(bool,Tp::Channel::GroupMemberChangeDetails))) == 0) {
+ warning() << "Connecting to deprecated signal blockStatusChanged(bool,Tp::Channel::GroupMemberChangeDetails)";
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/contact.h b/qt4/TelepathyQt4/contact.h
new file mode 100644
index 000000000..ec2e5e950
--- /dev/null
+++ b/qt4/TelepathyQt4/contact.h
@@ -0,0 +1,246 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_contact_h_HEADER_GUARD_
+#define _TelepathyQt4_contact_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/Types>
+
+#include <QSet>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+struct AvatarData;
+class ContactCapabilities;
+class LocationInfo;
+class ContactManager;
+class PendingContactInfo;
+class PendingOperation;
+class Presence;
+class ReferencedHandles;
+
+class TELEPATHY_QT4_EXPORT Contact : public Object
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Contact)
+
+public:
+ static const Feature FeatureAlias;
+ static const Feature FeatureAvatarData;
+ static const Feature FeatureAvatarToken;
+ static const Feature FeatureCapabilities;
+ static const Feature FeatureInfo;
+ static const Feature FeatureLocation;
+ static const Feature FeatureSimplePresence;
+
+ enum PresenceState {
+ PresenceStateNo,
+ PresenceStateAsk,
+ PresenceStateYes
+ };
+
+ class InfoFields
+ {
+ public:
+ InfoFields();
+ InfoFields(const ContactInfoFieldList &fields);
+ InfoFields(const InfoFields &other);
+ ~InfoFields();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ InfoFields &operator=(const InfoFields &other);
+
+ ContactInfoFieldList fields(const QString &name) const;
+
+ ContactInfoFieldList allFields() const;
+
+ private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ ~Contact();
+
+ ContactManagerPtr manager() const;
+
+ ReferencedHandles handle() const;
+
+ // TODO filter: exact, prefix, substring match
+ QString id() const;
+
+ Features requestedFeatures() const;
+ Features actualFeatures() const;
+
+ // TODO filter: exact, prefix, substring match
+ QString alias() const;
+
+ bool isAvatarTokenKnown() const;
+ QString avatarToken() const;
+ AvatarData avatarData() const;
+ void requestAvatarData();
+
+ /*
+ * TODO filter:
+ * - exact match of presence().type(), presence().status()
+ * - ANY 1 of a number of presence types/statuses
+ * - presence().type() greater or less than a set value
+ * - have/don't have presence().message() AND exact/prefix/substring
+ */
+ Presence presence() const;
+
+ // TODO filter: the same as Account filtering by caps
+ ContactCapabilities capabilities() const;
+
+ // TODO filter: is it available, how accurate, are they near me
+ LocationInfo location() const;
+
+ // TODO filter: having a specific field, having ANY field,
+ // (field: exact, contents: exact/prefix/substring)
+ bool isContactInfoKnown() const;
+ InfoFields infoFields() const;
+ PendingOperation *refreshInfo();
+ PendingContactInfo *requestInfo();
+
+ /*
+ * Filters on exact values of these, but also the "in your contact list at all or not" usecase
+ */
+ bool isSubscriptionStateKnown() const;
+ bool isSubscriptionRejected() const;
+ PresenceState subscriptionState() const;
+ bool isPublishStateKnown() const;
+ bool isPublishCancelled() const;
+ PresenceState publishState() const;
+ QString publishStateMessage() const;
+
+ PendingOperation *requestPresenceSubscription(const QString &message = QString());
+ PendingOperation *removePresenceSubscription(const QString &message = QString());
+ PendingOperation *authorizePresencePublication(const QString &message = QString());
+ PendingOperation *removePresencePublication(const QString &message = QString());
+
+ /*
+ * Filter on being blocked or not
+ */
+ bool isBlocked() const;
+ TELEPATHY_QT4_DEPRECATED PendingOperation *block(bool value);
+ PendingOperation *block();
+ PendingOperation *blockAndReportAbuse();
+ PendingOperation *unblock();
+
+ /*
+ * Filter on the groups they're in - to show a specific group only
+ *
+ * Also prefix/substring match on ANY of the groups of the contact
+ */
+ QStringList groups() const;
+ PendingOperation *addToGroup(const QString &group);
+ PendingOperation *removeFromGroup(const QString &group);
+
+Q_SIGNALS:
+ void aliasChanged(const QString &alias);
+
+ void avatarTokenChanged(const QString &avatarToken);
+ void avatarDataChanged(const Tp::AvatarData &avatarData);
+
+ void presenceChanged(const Tp::Presence &presence);
+
+ void capabilitiesChanged(const Tp::ContactCapabilities &caps);
+
+ void locationUpdated(const Tp::LocationInfo &location);
+
+ void infoFieldsChanged(const Tp::Contact::InfoFields &infoFields);
+
+ void subscriptionStateChanged(Tp::Contact::PresenceState state);
+ // deprecated
+ void subscriptionStateChanged(Tp::Contact::PresenceState state,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void publishStateChanged(Tp::Contact::PresenceState state, const QString &message);
+ // deprecated
+ void publishStateChanged(Tp::Contact::PresenceState state,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void blockStatusChanged(bool blocked);
+ // deprecated
+ void blockStatusChanged(bool blocked, const Tp::Channel::GroupMemberChangeDetails &details);
+
+ void addedToGroup(const QString &group);
+ void removedFromGroup(const QString &group);
+
+ // TODO: consider how the Renaming interface should work and map to Contacts
+ // I guess it would be something like:
+ // void renamedTo(Tp::ContactPtr)
+ // with that contact getting the same features requested as the current one. Or would we rather
+ // want to signal that change right away with a handle?
+
+protected:
+ Contact(ContactManager *manager, const ReferencedHandles &handle,
+ const Features &requestedFeatures, const QVariantMap &attributes);
+
+ virtual void augment(const Features &requestedFeatures, const QVariantMap &attributes);
+
+ // FIXME: (API/ABI break) Remove connectNotify
+ void connectNotify(const char *);
+
+private:
+ static const Feature FeatureRosterGroups;
+
+ TELEPATHY_QT4_NO_EXPORT void receiveAlias(const QString &alias);
+ TELEPATHY_QT4_NO_EXPORT void receiveAvatarToken(const QString &avatarToken);
+ TELEPATHY_QT4_NO_EXPORT void setAvatarToken(const QString &token);
+ TELEPATHY_QT4_NO_EXPORT void receiveAvatarData(const AvatarData &);
+ TELEPATHY_QT4_NO_EXPORT void receiveSimplePresence(const SimplePresence &presence);
+ TELEPATHY_QT4_NO_EXPORT void receiveCapabilities(const RequestableChannelClassList &caps);
+ TELEPATHY_QT4_NO_EXPORT void receiveLocation(const QVariantMap &location);
+ TELEPATHY_QT4_NO_EXPORT void receiveInfo(const ContactInfoFieldList &info);
+
+ TELEPATHY_QT4_NO_EXPORT static PresenceState subscriptionStateToPresenceState(uint subscriptionState);
+ TELEPATHY_QT4_NO_EXPORT void setSubscriptionState(SubscriptionState state);
+ TELEPATHY_QT4_NO_EXPORT void setPublishState(SubscriptionState state, const QString &message = QString());
+ TELEPATHY_QT4_NO_EXPORT void setBlocked(bool value);
+
+ TELEPATHY_QT4_NO_EXPORT void setAddedToGroup(const QString &group);
+ TELEPATHY_QT4_NO_EXPORT void setRemovedFromGroup(const QString &group);
+
+ struct Private;
+ friend class Connection;
+ friend class ContactFactory;
+ friend class ContactManager;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::Contact::InfoFields);
+
+#endif
diff --git a/qt4/TelepathyQt4/dbus-daemon.xml b/qt4/TelepathyQt4/dbus-daemon.xml
new file mode 100644
index 000000000..675b879c9
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-daemon.xml
@@ -0,0 +1,80 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:title>D-Bus Daemon</tp:title>
+
+ <node name="/DBus_Daemon">
+ <interface name="org.freedesktop.DBus">
+ <method name="Hello">
+ <arg direction="out" type="s"/>
+ </method>
+ <method name="RequestName">
+ <arg direction="in" type="s" name="Name"/>
+ <arg direction="in" type="u" name="Flags"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="ReleaseName">
+ <arg direction="in" type="s" name="Name"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="StartServiceByName">
+ <arg direction="in" type="s" name="Service"/>
+ <arg direction="in" type="u" name="Flags"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="NameHasOwner">
+ <arg direction="in" type="s" name="Name_To_Check"/>
+ <arg direction="out" type="b"/>
+ </method>
+ <method name="ListNames">
+ <arg direction="out" type="as"/>
+ </method>
+ <method name="ListActivatableNames">
+ <arg direction="out" type="as"/>
+ </method>
+ <method name="AddMatch">
+ <arg direction="in" type="s" name="Rule"/>
+ </method>
+ <method name="RemoveMatch">
+ <arg direction="in" type="s" name="Rule"/>
+ </method>
+ <method name="GetNameOwner">
+ <arg direction="in" type="s" name="Name"/>
+ <arg direction="out" type="s"/>
+ </method>
+ <method name="ListQueuedOwners">
+ <arg direction="in" type="s" name="Name"/>
+ <arg direction="out" type="as"/>
+ </method>
+ <method name="GetConnectionUnixUser">
+ <arg direction="in" type="s" name="Connection_Name"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="GetConnectionUnixProcessID">
+ <arg direction="in" type="s" name="Connection_Name"/>
+ <arg direction="out" type="u"/>
+ </method>
+ <method name="GetConnectionSELinuxSecurityContext">
+ <arg direction="in" type="s" name="Connection_Name"/>
+ <arg direction="out" type="ay"/>
+ </method>
+ <method name="ReloadConfig">
+ </method>
+ <method name="GetId">
+ <arg direction="out" type="s"/>
+ </method>
+ <signal name="NameOwnerChanged">
+ <arg type="s" name="Name"/>
+ <arg type="s" name="Old_Owner"/>
+ <arg type="s" name="New_Owner"/>
+ </signal>
+ <signal name="NameLost">
+ <arg type="s" name="Name"/>
+ </signal>
+ <signal name="NameAcquired">
+ <arg type="s" name="Name"/>
+ </signal>
+ </interface>
+ </node>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/dbus-introspectable.xml b/qt4/TelepathyQt4/dbus-introspectable.xml
new file mode 100644
index 000000000..4617f407c
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-introspectable.xml
@@ -0,0 +1,16 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:title>D-Bus introspectable object</tp:title>
+
+ <node name="/Introspectable">
+ <interface name="org.freedesktop.DBus.Introspectable">
+
+ <method name="Introspect">
+ <arg direction="out" type="s" name="XML_Data"/>
+ </method>
+
+ </interface>
+ </node>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/dbus-peer.xml b/qt4/TelepathyQt4/dbus-peer.xml
new file mode 100644
index 000000000..54b25a28f
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-peer.xml
@@ -0,0 +1,19 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:title>D-Bus peer</tp:title>
+
+ <node name="/Peer">
+ <interface name="org.freedesktop.DBus.Peer" tp:implement-service="no">
+
+ <method name="Ping">
+ </method>
+
+ <method name="GetMachineId">
+ <arg direction="out" type="s" name="Machine_UUID"/>
+ </method>
+
+ </interface>
+ </node>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/dbus-properties.xml b/qt4/TelepathyQt4/dbus-properties.xml
new file mode 100644
index 000000000..e76b3981f
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-properties.xml
@@ -0,0 +1,29 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:title>D-Bus Properties</tp:title>
+
+ <node name="/Properties">
+ <interface name="org.freedesktop.DBus.Properties">
+
+ <method name="Get">
+ <arg direction="in" type="s" name="Interface_Name"/>
+ <arg direction="in" type="s" name="Property_Name"/>
+ <arg direction="out" type="v" name="Value"/>
+ </method>
+
+ <method name="Set">
+ <arg direction="in" type="s" name="Interface_Name"/>
+ <arg direction="in" type="s" name="Property_Name"/>
+ <arg direction="in" type="v" name="Value"/>
+ </method>
+
+ <method name="GetAll">
+ <arg direction="in" type="s" name="Interface_Name"/>
+ <arg direction="out" type="a{sv}" name="Properties"/>
+ </method>
+
+ </interface>
+ </node>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/dbus-proxy-factory-internal.h b/qt4/TelepathyQt4/dbus-proxy-factory-internal.h
new file mode 100644
index 000000000..0d2f75f46
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-proxy-factory-internal.h
@@ -0,0 +1,58 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef BUILDING_TELEPATHY_QT4
+#error "This file is a TpQt4 internal header not to be included by applications"
+#endif
+
+#include <QObject>
+#include <QPair>
+#include <QString>
+
+#include <TelepathyQt4/SharedPtr>
+
+namespace Tp
+{
+
+class DBusProxy;
+
+class TELEPATHY_QT4_NO_EXPORT DBusProxyFactory::Cache : public QObject
+{
+ Q_OBJECT
+
+public:
+ typedef QPair<QString /* serviceName */, QString /* objectPath */> Key;
+
+ Cache();
+ ~Cache();
+
+ DBusProxyPtr get(const Key &key) const;
+ void put(const DBusProxyPtr &proxy);
+
+private Q_SLOTS:
+ void onProxyInvalidated(Tp::DBusProxy *proxy); // The error itself is not interesting
+
+private:
+ QHash<Key, QWeakPointer<DBusProxy> > proxies;
+};
+
+}
diff --git a/qt4/TelepathyQt4/dbus-proxy-factory.cpp b/qt4/TelepathyQt4/dbus-proxy-factory.cpp
new file mode 100644
index 000000000..e4562c952
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-proxy-factory.cpp
@@ -0,0 +1,295 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/DBusProxyFactory>
+#include "TelepathyQt4/dbus-proxy-factory-internal.h"
+
+#include "TelepathyQt4/_gen/dbus-proxy-factory.moc.hpp"
+#include "TelepathyQt4/_gen/dbus-proxy-factory-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/ReadyObject>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDBusConnection>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT DBusProxyFactory::Private
+{
+ Private(const QDBusConnection &bus)
+ : bus(bus),
+ cache(new Cache)
+ {
+ }
+
+ ~Private()
+ {
+ delete cache;
+ }
+
+ QDBusConnection bus;
+ Cache *cache;
+};
+
+/**
+ * \class DBusProxyFactory
+ * \ingroup utils
+ * \headerfile TelepathyQt4/dbus-proxy-factory.h <TelepathyQt4/DBusProxyFactory>
+ *
+ * \brief The DBusProxyFactory class is a base class for all D-Bus proxy factory
+ * classes. Handles proxy caching and making them ready as appropriate.
+ */
+
+/**
+ * Construct a new DBusProxyFactory object.
+ *
+ * The intention for storing the bus here is that it generally doesn't make sense to construct
+ * proxies for multiple buses in the same context. Allowing that would lead to more complex keying
+ * needs in the cache, as well.
+ *
+ * \param bus The D-Bus bus connection for the objects constructed using this factory.
+ */
+DBusProxyFactory::DBusProxyFactory(const QDBusConnection &bus)
+ : mPriv(new Private(bus))
+{
+}
+
+/**
+ * Class destructor.
+ */
+DBusProxyFactory::~DBusProxyFactory()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the D-Bus connection all of the proxies from this factory communicate with.
+ *
+ * \return A QDBusConnection object.
+ */
+const QDBusConnection &DBusProxyFactory::dbusConnection() const
+{
+ return mPriv->bus;
+}
+
+/**
+ * Return a cached proxy with the given \a busName and \a objectPath.
+ *
+ * If a proxy has not been previously put into the cache by nowHaveProxy for those identifying
+ * attributes, or a previously cached proxy has since been invalidated and/or destroyed, a \c Null
+ * shared pointer is returned instead.
+ *
+ * \param busName Bus name of the proxy to return.
+ * \param objectPath Object path of the proxy to return.
+ * \return A pointer to the DBusProxy object, if any.
+ */
+DBusProxyPtr DBusProxyFactory::cachedProxy(const QString &busName,
+ const QString &objectPath) const
+{
+ QString finalName = finalBusNameFrom(busName);
+ return mPriv->cache->get(Cache::Key(finalName, objectPath));
+}
+
+/**
+ * Should be called by subclasses when they have a proxy, be it a newly-constructed one or one from
+ * the cache.
+ *
+ * This function will then do the rest of the factory work, including caching the proxy if it's not
+ * cached already, doing any initialPrepare()/readyPrepare() work if appropriate, and making the
+ * features from featuresFor() ready if they aren't already.
+ *
+ * The returned PendingReady only finishes when the initialPrepare() and readyPrepare() operations
+ * for the proxy has completed, and the requested features have all been made ready (or found unable
+ * to be made ready). Note that this might have happened already before calling this function, if
+ * the proxy was not a newly created one, but was looked up from the cache. DBusProxyFactory handles
+ * the necessary subleties for this to work.
+ *
+ * Access to the proxy instance is allowed as soon as this method returns through
+ * PendingReady::proxy(), if the proxy is needed in a context where it's not required to be ready.
+ *
+ * \param proxy The proxy which the factory should now make sure is prepared and made ready.
+ * \return A PendingReady operation which will emit PendingReady::finished
+ * when the proxy is usable.
+ */
+PendingReady *DBusProxyFactory::nowHaveProxy(const DBusProxyPtr &proxy) const
+{
+ Q_ASSERT(!proxy.isNull());
+
+ mPriv->cache->put(proxy);
+ return new PendingReady(SharedPtr<DBusProxyFactory>((DBusProxyFactory*) this),
+ proxy, featuresFor(proxy));
+}
+
+/**
+ * \fn QString DBusProxyFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const
+ *
+ * "Normalize" a bus name according to the rules for the proxy class to construct.
+ *
+ * Should be implemented by subclasses to transform the application-specified name \a
+ * uniqueOrWellKnown to whatever the proxy constructed for that name would have in its
+ * DBusProxy::busName() in the end.
+ *
+ * For StatelessDBusProxy sub-classes this should mostly be an identity transform, while for
+ * StatefulDBusProxy sub-classes StatefulDBusProxy::uniqueNameFrom() or an equivalent thereof should
+ * be used in most cases.
+ *
+ * If this is not implemented correctly, caching won't work properly.
+ *
+ * \param uniqueOrWellKnown Any valid D-Bus service name, either unique or well-known.
+ * \return Whatever that name would turn to, when a proxy is constructed for it.
+ */
+
+/**
+ * Allows subclasses to do arbitrary manipulation on the proxy before it is attempted to be made
+ * ready.
+ *
+ * If a non-\c NULL operation is returned, the completion of that operation is waited for before
+ * starting to make the object ready whenever nowHaveProxy() is called the first time around for a
+ * given proxy.
+ *
+ * \todo FIXME actually implement this... :) Currently just a vtable placeholder.
+ * \param proxy The just-constructed proxy to be prepared.
+ * \return \c NULL ie. nothing to do.
+ */
+PendingOperation *DBusProxyFactory::initialPrepare(const DBusProxyPtr &proxy) const
+{
+ // Nothing we could think about needs doing
+ return NULL;
+}
+
+/**
+ * Allows subclasses to do arbitrary manipulation on the proxy after it has been made ready.
+ *
+ * If a non-\c NULL operation is returned, the completion of that operation is waited for before
+ * signaling that the object is ready for use after ReadyObject::becomeReady() for it has finished
+ * whenever nowHaveProxy() is called the first time around for a given proxy.
+ *
+ * \todo FIXME actually implement this... :) Currently just a vtable placeholder.
+ * \param proxy The just-readified proxy to be prepared.
+ * \return \c NULL ie. nothing to do.
+ */
+PendingOperation *DBusProxyFactory::readyPrepare(const DBusProxyPtr &proxy) const
+{
+ // Nothing we could think about needs doing
+ return NULL;
+}
+
+/**
+ * \fn Features DBusProxyFactory::featuresFor(const SharedPtr<RefCounted> &proxy) const
+ *
+ * Return the features which should be made ready on a given proxy.
+ *
+ * This can be used to implement instance-specific features based on arbitrary criteria.
+ * FixedFeatureFactory implements this as a fixed set of features independent of the instance,
+ * however.
+ *
+ * It should be noted that if an empty set of features is returned, ReadyObject::becomeReady() is
+ * not called at all. In other words, any "core feature" is not automatically added to the requested
+ * features. This is to enable setting a factory to not make proxies ready at all, which is useful
+ * eg. in the case of account editing UIs which aren't interested in the state of Connection objects
+ * for the Account objects they're editing.
+ *
+ * \param proxy The proxy on which the returned features will be made ready.
+ * \return A list of Feature objects.
+ */
+
+DBusProxyFactory::Cache::Cache()
+{
+}
+
+DBusProxyFactory::Cache::~Cache()
+{
+}
+
+DBusProxyPtr DBusProxyFactory::Cache::get(const Key &key) const
+{
+ DBusProxyPtr proxy(proxies.value(key));
+
+ if (proxy.isNull() || !proxy->isValid()) {
+ // Weak pointer invalidated or proxy invalidated during this mainloop iteration and we still
+ // haven't got the invalidated() signal for it
+ return DBusProxyPtr();
+ }
+
+ return proxy;
+}
+
+void DBusProxyFactory::Cache::put(const DBusProxyPtr &proxy)
+{
+ if (proxy->busName().isEmpty()) {
+ debug() << "Not inserting proxy" << proxy.data() << "with no bus name to factory cache";
+ return;
+ } else if (!proxy->isValid()) {
+ debug() << "Not inserting to factory cache invalid proxy - proxy is for" <<
+ proxy->busName() << ',' << proxy->objectPath();
+ return;
+ }
+
+ Key key(proxy->busName(), proxy->objectPath());
+
+ DBusProxyPtr existingProxy(proxies.value(key));
+ if (!existingProxy || existingProxy != proxy) {
+ // Disconnect the invalidated signal from the proxy we're replacing, so it won't uselessly
+ // cause the new (hopefully valid) proxy to be dropped from the cache if it arrives late.
+ //
+ // The window in which this makes a difference is very slim but existent; namely, somebody
+ // must request a proxy from the factory in the same mainloop iteration as an otherwise
+ // matching proxy has invalidated itself. The invalidation signal would be delivered and
+ // processed only during the next mainloop iteration.
+ if (existingProxy) {
+ Q_ASSERT(!existingProxy->isValid());
+ existingProxy->disconnect(
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ this,
+ SLOT(onProxyInvalidated(Tp::DBusProxy*)));
+
+ debug() << "Replacing invalidated proxy" << existingProxy.data() << "in cache for name"
+ << existingProxy->busName() << ',' << existingProxy->objectPath();
+ }
+
+ connect(proxy.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onProxyInvalidated(Tp::DBusProxy*)));
+
+ debug() << "Inserting to factory cache proxy for" << key;
+ proxies.insert(key, QWeakPointer<DBusProxy>(proxy.data()));
+ }
+}
+
+void DBusProxyFactory::Cache::onProxyInvalidated(Tp::DBusProxy *proxy)
+{
+ Key key(proxy->busName(), proxy->objectPath());
+
+ // Not having it would indicate invalidated() signaled twice for the same proxy, or us having
+ // connected to two proxies with the same key, neither of which should happen
+ Q_ASSERT(proxies.contains(key));
+
+ debug() << "Removing from factory cache invalidated proxy for" << key;
+
+ proxies.remove(key);
+}
+
+}
diff --git a/qt4/TelepathyQt4/dbus-proxy-factory.h b/qt4/TelepathyQt4/dbus-proxy-factory.h
new file mode 100644
index 000000000..77e3d4ec9
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-proxy-factory.h
@@ -0,0 +1,85 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_dbus_proxy_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_dbus_proxy_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+// For Q_DISABLE_COPY
+#include <QtGlobal>
+
+#include <QString>
+
+class QDBusConnection;
+
+namespace Tp
+{
+
+class Features;
+class PendingReady;
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT DBusProxyFactory : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(DBusProxyFactory)
+
+public:
+ virtual ~DBusProxyFactory();
+
+ const QDBusConnection &dbusConnection() const;
+
+protected:
+ DBusProxyFactory(const QDBusConnection &bus);
+
+ DBusProxyPtr cachedProxy(const QString &busName, const QString &objectPath) const;
+
+ PendingReady *nowHaveProxy(const DBusProxyPtr &proxy) const;
+
+ // I don't want this to be non-pure virtual, because I want ALL subclasses to have to think
+ // about whether or not they need to uniquefy the name or not. If a subclass doesn't implement
+ // this while it should, matching with the cache for future requests and invalidation breaks.
+ virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const = 0;
+
+ virtual PendingOperation *initialPrepare(const DBusProxyPtr &proxy) const;
+ virtual PendingOperation *readyPrepare(const DBusProxyPtr &proxy) const;
+
+ virtual Features featuresFor(const DBusProxyPtr &proxy) const = 0;
+
+private:
+ class Cache;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/dbus-proxy.cpp b/qt4/TelepathyQt4/dbus-proxy.cpp
new file mode 100644
index 000000000..8b8c9321d
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-proxy.cpp
@@ -0,0 +1,393 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <TelepathyQt4/DBusProxy>
+
+#include "TelepathyQt4/_gen/dbus-proxy.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Constants>
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDBusError>
+#include <QDBusServiceWatcher>
+#include <QTimer>
+
+namespace Tp
+{
+
+// ==== DBusProxy ======================================================
+
+// Features in TpProxy but not here:
+// * tracking which interfaces we have (in tpqt4, subclasses do that)
+// * being Introspectable, a Peer and a Properties implementation
+// * disconnecting from signals when invalidated (probably has to be in the
+// generated code)
+// * making methods always raise an error when called after invalidated
+// (has to be in the generated code)
+
+struct TELEPATHY_QT4_NO_EXPORT DBusProxy::Private
+{
+ Private(const QDBusConnection &dbusConnection, const QString &busName,
+ const QString &objectPath);
+
+ QDBusConnection dbusConnection;
+ QString busName;
+ QString objectPath;
+ QString invalidationReason;
+ QString invalidationMessage;
+};
+
+DBusProxy::Private::Private(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath)
+ : dbusConnection(dbusConnection),
+ busName(busName),
+ objectPath(objectPath)
+{
+ debug() << "Creating new DBusProxy";
+}
+
+/**
+ * \class DBusProxy
+ * \ingroup clientproxies
+ * \headerfile TelepathyQt4/dbus-proxy.h <TelepathyQt4/DBusProxy>
+ *
+ * \brief The DBusProxy class is a base class representing a remote object available over D-Bus.
+ *
+ * All Telepathy-Qt4 client convenience classes that wrap Telepathy interfaces
+ * inherit from this class in order to provide basic D-Bus interface
+ * information.
+ */
+
+/**
+ * Construct a new DBusProxy object.
+ *
+ * \param dbusConnection QDBusConnection to use.
+ * \param busName D-Bus bus name of the service that provides the remote object.
+ * \param objectPath The object path.
+ * \param featureCore The object core feature.
+ */
+DBusProxy::DBusProxy(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath, const Feature &featureCore)
+ : Object(),
+ ReadyObject(this, featureCore),
+ mPriv(new Private(dbusConnection, busName, objectPath))
+{
+ if (!dbusConnection.isConnected()) {
+ invalidate(QLatin1String(TELEPATHY_ERROR_DISCONNECTED),
+ QLatin1String("DBus connection disconnected"));
+ }
+}
+
+/**
+ * Class destructor.
+ */
+DBusProxy::~DBusProxy()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the D-Bus connection through which the remote object is
+ * accessed.
+ *
+ * \return A QDBusConnection object.
+ */
+QDBusConnection DBusProxy::dbusConnection() const
+{
+ return mPriv->dbusConnection;
+}
+
+/**
+ * Return the D-Bus object path of the remote object within the service.
+ *
+ * \return The D-Bus object path.
+ */
+QString DBusProxy::objectPath() const
+{
+ return mPriv->objectPath;
+}
+
+/**
+ * Return the D-Bus bus name (either a unique name or a well-known
+ * name) of the service that provides the remote object.
+ *
+ * \return The D-Bus bus name.
+ */
+QString DBusProxy::busName() const
+{
+ return mPriv->busName;
+}
+
+/**
+ * Sets the D-Bus bus name. This is used by subclasses after converting
+ * well-known names to unique names.
+ *
+ * \param busName The D-Bus bus name to set.
+ */
+void DBusProxy::setBusName(const QString &busName)
+{
+ mPriv->busName = busName;
+}
+
+/**
+ * Return whether this proxy is still valid (has not emitted invalidated()).
+ *
+ * \return \c true if still valid, \c false otherwise.
+ */
+bool DBusProxy::isValid() const
+{
+ return mPriv->invalidationReason.isEmpty();
+}
+
+/**
+ * Return the error name indicating the reason this proxy became invalid.
+ *
+ * \return A D-Bus error name, or QString() if this object is still valid.
+ */
+QString DBusProxy::invalidationReason() const
+{
+ return mPriv->invalidationReason;
+}
+
+/**
+ * Return a debugging message indicating the reason this proxy became invalid.
+ *
+ * \return A debugging message, or QString() if this object is still valid.
+ */
+QString DBusProxy::invalidationMessage() const
+{
+ return mPriv->invalidationMessage;
+}
+
+/**
+ * Called by subclasses when the DBusProxy should become invalid.
+ *
+ * This method takes care of setting the invalidationReason,
+ * invalidationMessage, and emitting the invalidated signal.
+ *
+ * \param reason A D-Bus error name (a string in a subset of ASCII,
+ * prefixed with a reversed domain name)
+ * \param message A debugging message associated with the error
+ */
+void DBusProxy::invalidate(const QString &reason, const QString &message)
+{
+ if (!isValid()) {
+ debug().nospace() << "Already invalidated by "
+ << mPriv->invalidationReason
+ << ", not replacing with " << reason
+ << " \"" << message << "\"";
+ return;
+ }
+
+ Q_ASSERT(!reason.isEmpty());
+
+ debug().nospace() << "proxy invalidated: " << reason
+ << ": " << message;
+
+ mPriv->invalidationReason = reason;
+ mPriv->invalidationMessage = message;
+
+ Q_ASSERT(!isValid());
+
+ // Defer emitting the invalidated signal until we next
+ // return to the mainloop.
+ QTimer::singleShot(0, this, SLOT(emitInvalidated()));
+}
+
+void DBusProxy::invalidate(const QDBusError &error)
+{
+ invalidate(error.name(), error.message());
+}
+
+void DBusProxy::emitInvalidated()
+{
+ Q_ASSERT(!isValid());
+
+ emit invalidated(this, mPriv->invalidationReason, mPriv->invalidationMessage);
+}
+
+/**
+ * \fn void DBusProxy::invalidated(Tp::DBusProxy *proxy,
+ * const QString &errorName, const QString &errorMessage)
+ *
+ * Emitted when this object is no longer usable.
+ *
+ * After this signal is emitted, any D-Bus method calls on the object
+ * will fail, but it may be possible to retrieve information that has
+ * already been retrieved and cached.
+ *
+ * \param proxy This proxy.
+ * \param errorName The name of a D-Bus error describing the reason for the invalidation.
+ * \param errorMessage A debugging message associated with the error.
+ */
+
+// ==== StatefulDBusProxy ==============================================
+
+struct TELEPATHY_QT4_NO_EXPORT StatefulDBusProxy::Private
+{
+ Private(const QString &originalName)
+ : originalName(originalName) {}
+
+ QString originalName;
+};
+
+/**
+ * \class StatefulDBusProxy
+ * \ingroup clientproxies
+ * \headerfile TelepathyQt4/dbus-proxy.h <TelepathyQt4/StatefulDBusProxy>
+ *
+ * \brief The StatefulDBusProxy class is a base class representing a remote object whose API is
+ * stateful.
+ *
+ * These objects do not remain useful if the service providing them exits or
+ * crashes, so they emit invalidated() if this happens.
+ *
+ * Examples include the Connection and Channel classes.
+ */
+
+/**
+ * Construct a new StatefulDBusProxy object.
+ *
+ * \param dbusConnection QDBusConnection to use.
+ * \param busName D-Bus bus name of the service that provides the remote object.
+ * \param objectPath The object path.
+ * \param featureCore The object core feature.
+ */
+StatefulDBusProxy::StatefulDBusProxy(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath, const Feature &featureCore)
+ : DBusProxy(dbusConnection, busName, objectPath, featureCore),
+ mPriv(new Private(busName))
+{
+ QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher(busName,
+ dbusConnection, QDBusServiceWatcher::WatchForUnregistration, this);
+ connect(serviceWatcher,
+ SIGNAL(serviceOwnerChanged(QString,QString,QString)),
+ SLOT(onServiceOwnerChanged(QString,QString,QString)));
+
+ QString error, message;
+ QString uniqueName = uniqueNameFrom(dbusConnection, busName, error, message);
+
+ if (uniqueName.isEmpty()) {
+ invalidate(error, message);
+ return;
+ }
+
+ setBusName(uniqueName);
+}
+
+/**
+ * Class destructor.
+ */
+StatefulDBusProxy::~StatefulDBusProxy()
+{
+ delete mPriv;
+}
+
+QString StatefulDBusProxy::uniqueNameFrom(const QDBusConnection &bus, const QString &name)
+{
+ QString error, message;
+ QString uniqueName = uniqueNameFrom(bus, name, error, message);
+ if (uniqueName.isEmpty()) {
+ warning() << "StatefulDBusProxy::uniqueNameFrom(): Failed to get unique name of" << name;
+ warning() << " error:" << error << "message:" << message;
+ }
+
+ return uniqueName;
+}
+
+QString StatefulDBusProxy::uniqueNameFrom(const QDBusConnection &bus, const QString &name,
+ QString &error, QString &message)
+{
+ if (name.startsWith(QLatin1String(":"))) {
+ return name;
+ }
+
+ // For a stateful interface, it makes no sense to follow name-owner
+ // changes, so we want to bind to the unique name.
+ QDBusReply<QString> reply = bus.interface()->serviceOwner(name);
+ if (reply.isValid()) {
+ return reply.value();
+ } else {
+ error = reply.error().name();
+ message = reply.error().message();
+ return QString();
+ }
+}
+
+void StatefulDBusProxy::onServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
+{
+ // We only want to invalidate this object if it is not already invalidated,
+ // and its (not any other object's) name owner changed signal is emitted.
+ if (isValid() && name == mPriv->originalName && newOwner.isEmpty()) {
+ invalidate(QLatin1String(TELEPATHY_DBUS_ERROR_NAME_HAS_NO_OWNER),
+ QLatin1String("Name owner lost (service crashed?)"));
+ }
+}
+
+// ==== StatelessDBusProxy =============================================
+
+/**
+ * \class StatelessDBusProxy
+ * \ingroup clientproxies
+ * \headerfile TelepathyQt4/dbus-proxy.h <TelepathyQt4/DBusProxy>
+ *
+ * \brief The StatelessDBusProxy class is a base class representing a remote object whose API is
+ * basically stateless.
+ *
+ * These objects can remain valid even if the service providing them exits
+ * and is restarted.
+ *
+ * Examples include the AccountManager, Account and ConnectionManager.
+ */
+
+/**
+ * Construct a new StatelessDBusProxy object.
+ *
+ * \param dbusConnection QDBusConnection to use.
+ * \param busName D-Bus bus name of the service that provides the remote object.
+ * \param objectPath The object path.
+ * \param featureCore The object core feature.
+ */
+StatelessDBusProxy::StatelessDBusProxy(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath, const Feature &featureCore)
+ : DBusProxy(dbusConnection, busName, objectPath, featureCore),
+ mPriv(0)
+{
+ if (busName.startsWith(QLatin1String(":"))) {
+ warning() <<
+ "Using StatelessDBusProxy for a unique name does not make sense";
+ }
+}
+
+/**
+ * Class destructor.
+ */
+StatelessDBusProxy::~StatelessDBusProxy()
+{
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/dbus-proxy.h b/qt4/TelepathyQt4/dbus-proxy.h
new file mode 100644
index 000000000..72d7dc29b
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus-proxy.h
@@ -0,0 +1,122 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_dbus_proxy_h_HEADER_GUARD_
+#define _TelepathyQt4_dbus_proxy_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/ReadyObject>
+
+class QDBusConnection;
+class QDBusError;
+
+namespace Tp
+{
+
+class TestBackdoors;
+
+class TELEPATHY_QT4_EXPORT DBusProxy : public Object, public ReadyObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(DBusProxy)
+
+public:
+ DBusProxy(const QDBusConnection &dbusConnection, const QString &busName,
+ const QString &objectPath, const Feature &featureCore);
+ virtual ~DBusProxy();
+
+ QDBusConnection dbusConnection() const;
+ QString busName() const;
+ QString objectPath() const;
+
+ bool isValid() const;
+ QString invalidationReason() const;
+ QString invalidationMessage() const;
+
+Q_SIGNALS:
+ void invalidated(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage);
+
+protected:
+ void setBusName(const QString &busName);
+ void invalidate(const QString &reason, const QString &message);
+ void invalidate(const QDBusError &error);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void emitInvalidated();
+
+private:
+ friend class TestBackdoors;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT StatelessDBusProxy : public DBusProxy
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StatelessDBusProxy)
+
+public:
+ StatelessDBusProxy(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath, const Feature &featureCore);
+ virtual ~StatelessDBusProxy();
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT StatefulDBusProxy : public DBusProxy
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StatefulDBusProxy)
+
+public:
+ StatefulDBusProxy(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath, const Feature &featureCore);
+ virtual ~StatefulDBusProxy();
+
+ static QString uniqueNameFrom(const QDBusConnection &bus, const QString &wellKnownOrUnique);
+ static QString uniqueNameFrom(const QDBusConnection &bus, const QString &wellKnownOrUnique,
+ QString &error, QString &message);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onServiceOwnerChanged(const QString &name, const QString &oldOwner,
+ const QString &newOwner);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/dbus.cpp b/qt4/TelepathyQt4/dbus.cpp
new file mode 100644
index 000000000..158c7c843
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/DBus>
+
+#include "TelepathyQt4/_gen/cli-dbus-body.hpp"
+#include "TelepathyQt4/_gen/cli-dbus.moc.hpp"
diff --git a/qt4/TelepathyQt4/dbus.h b/qt4/TelepathyQt4/dbus.h
new file mode 100644
index 000000000..2f7ca0431
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus.h
@@ -0,0 +1,54 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_dbus_h_HEADER_GUARD_
+#define _TelepathyQt4_dbus_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+/**
+ * \addtogroup clientsideproxies Client-side proxies
+ *
+ * Proxy objects representing remote service objects accessed via D-Bus.
+ *
+ * In addition to providing direct access to methods, signals and properties
+ * exported by the remote objects, some of these proxies offer features like
+ * automatic inspection of remote object capabilities, property tracking,
+ * backwards compatibility helpers for older services and other utilities.
+ */
+
+/**
+ * \defgroup clientdbus Generic D-Bus proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing well-known generic D-Bus interfaces on remote
+ * objects. Note that QDBus already has QDBusConnectionInterface for accessing
+ * the bus daemon, so in the parts where there is an overlap in the
+ * functionality, using the QDBus proxy should be given consideration instead
+ * of blindly using the proxy provided here.
+ */
+
+#include <TelepathyQt4/_gen/cli-dbus.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/dbus.xml b/qt4/TelepathyQt4/dbus.xml
new file mode 100644
index 000000000..99af441ce
--- /dev/null
+++ b/qt4/TelepathyQt4/dbus.xml
@@ -0,0 +1,12 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>D-Bus interfaces</tp:title>
+
+<xi:include href="dbus-daemon.xml"/>
+<xi:include href="dbus-introspectable.xml"/>
+<xi:include href="dbus-peer.xml"/>
+<xi:include href="dbus-properties.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/debug-internal.h b/qt4/TelepathyQt4/debug-internal.h
new file mode 100644
index 000000000..0f8813415
--- /dev/null
+++ b/qt4/TelepathyQt4/debug-internal.h
@@ -0,0 +1,170 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_debug_HEADER_GUARD_
+#define _TelepathyQt4_debug_HEADER_GUARD_
+
+#include <QDebug>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT Debug
+{
+public:
+ inline Debug() : debug(0) { }
+ inline Debug(QtMsgType type) : type(type), debug(new QDebug(&msg)) { }
+ inline Debug(const Debug &a) : type(a.type), debug(a.debug ? new QDebug(&msg) : 0)
+ {
+ if (debug) {
+ (*debug) << qPrintable(a.msg);
+ }
+ }
+
+ inline Debug &operator=(const Debug &a)
+ {
+ if (this != &a) {
+ type = a.type;
+ delete debug;
+ debug = 0;
+
+ if (a.debug) {
+ debug = new QDebug(&msg);
+ (*debug) << qPrintable(a.msg);
+ }
+ }
+
+ return *this;
+ }
+
+ inline ~Debug()
+ {
+ if (!msg.isEmpty()) {
+ invokeDebugCallback();
+ }
+ delete debug;
+ }
+
+ inline Debug &space()
+ {
+ if (debug) {
+ debug->space();
+ }
+
+ return *this;
+ }
+
+ inline Debug &nospace()
+ {
+ if (debug) {
+ debug->nospace();
+ }
+
+ return *this;
+ }
+
+ inline Debug &maybeSpace()
+ {
+ if (debug) {
+ debug->maybeSpace();
+ }
+
+ return *this;
+ }
+
+ template <typename T>
+ inline Debug &operator<<(T a)
+ {
+ if (debug) {
+ (*debug) << a;
+ }
+
+ return *this;
+ }
+
+private:
+
+ QString msg;
+ QtMsgType type;
+ QDebug *debug;
+
+ void invokeDebugCallback();
+};
+
+// The telepathy-farsight Qt 4 binding links to these - they're not API outside
+// this source tarball, but they *are* ABI
+TELEPATHY_QT4_EXPORT Debug enabledDebug();
+TELEPATHY_QT4_EXPORT Debug enabledWarning();
+
+#ifdef ENABLE_DEBUG
+
+inline Debug debug()
+{
+ return enabledDebug();
+}
+
+inline Debug warning()
+{
+ return enabledWarning();
+}
+
+#else /* #ifdef ENABLE_DEBUG */
+
+struct NoDebug
+{
+ template <typename T>
+ NoDebug& operator<<(const T&)
+ {
+ return *this;
+ }
+
+ NoDebug& space()
+ {
+ return *this;
+ }
+
+ NoDebug& nospace()
+ {
+ return *this;
+ }
+
+ NoDebug& maybeSpace()
+ {
+ return *this;
+ }
+};
+
+inline NoDebug debug()
+{
+ return NoDebug();
+}
+
+inline NoDebug warning()
+{
+ return NoDebug();
+}
+
+#endif /* #ifdef ENABLE_DEBUG */
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/debug.cpp b/qt4/TelepathyQt4/debug.cpp
new file mode 100644
index 000000000..f8e37171f
--- /dev/null
+++ b/qt4/TelepathyQt4/debug.cpp
@@ -0,0 +1,187 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define IN_TELEPATHY_QT4_HEADER
+#include "debug.h"
+#include "debug-internal.h"
+
+#include "config-version.h"
+
+/**
+ * \defgroup debug Common debug support
+ *
+ * TelepathyQt4 has an internal mechanism for displaying debugging output. It
+ * uses the Qt4 debugging subsystem, so if you want to redirect the messages,
+ * use qInstallMsgHandler() from &lt;QtGlobal&gt;.
+ *
+ * Debugging output is divided into two categories: normal debug output and
+ * warning messages. Normal debug output results in the normal operation of the
+ * library, warning messages are output only when something goes wrong. Each
+ * category can be invidually enabled.
+ */
+
+namespace Tp
+{
+
+/**
+ * \fn void enableDebug(bool enable)
+ * \ingroup debug
+ *
+ * Enable or disable normal debug output from the library. If the library is not
+ * compiled with debug support enabled, this has no effect; no output is
+ * produced in any case.
+ *
+ * The default is <code>false</code> ie. no debug output.
+ *
+ * \param enable Whether debug output should be enabled or not.
+ */
+
+/**
+ * \fn void enableWarnings(bool enable)
+ * \ingroup debug
+ *
+ * Enable or disable warning output from the library. If the library is not
+ * compiled with debug support enabled, this has no effect; no output is
+ * produced in any case.
+ *
+ * The default is <code>true</code> ie. warning output enabled.
+ *
+ * \param enable Whether warnings should be enabled or not.
+ */
+
+/**
+ * \typedef DebugCallback
+ * \ingroup debug
+ *
+ * \code
+ * typedef QDebug (*DebugCallback)(const QString &libraryName,
+ * const QString &libraryVersion,
+ * QtMsgType type,
+ * const QString &msg)
+ * \endcode
+ */
+
+/**
+ * \fn void setDebugCallback(DebugCallback cb)
+ * \ingroup debug
+ *
+ * Set the callback method that will handle the debug output.
+ *
+ * If \p cb is NULL this method will set the defaultDebugCallback instead.
+ * The default callback function will print the output using default Qt debug
+ * system.
+ *
+ * \param cb A function pointer to the callback method or NULL.
+ * \sa DebugCallback
+ */
+
+#ifdef ENABLE_DEBUG
+
+namespace
+{
+bool debugEnabled = false;
+bool warningsEnabled = true;
+DebugCallback debugCallback = NULL;
+}
+
+void enableDebug(bool enable)
+{
+ debugEnabled = enable;
+}
+
+void enableWarnings(bool enable)
+{
+ warningsEnabled = enable;
+}
+
+void setDebugCallback(DebugCallback cb)
+{
+ debugCallback = cb;
+}
+
+Debug enabledDebug()
+{
+ if (debugEnabled) {
+ return Debug(QtDebugMsg);
+ } else {
+ return Debug();
+ }
+}
+
+Debug enabledWarning()
+{
+ if (warningsEnabled) {
+ return Debug(QtWarningMsg);
+ } else {
+ return Debug();
+ }
+}
+
+void Debug::invokeDebugCallback()
+{
+ if (debugCallback) {
+ debugCallback(QLatin1String("tp-qt4"), QLatin1String(PACKAGE_VERSION), type, msg);
+ } else {
+ switch (type) {
+ case QtDebugMsg:
+ qDebug() << "tp-qt4 " PACKAGE_VERSION " DEBUG:" << qPrintable(msg);
+ break;
+ case QtWarningMsg:
+ qWarning() << "tp-qt4 " PACKAGE_VERSION " WARN:" << qPrintable(msg);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+#else /* !defined(ENABLE_DEBUG) */
+
+void enableDebug(bool enable)
+{
+}
+
+void enableWarnings(bool enable)
+{
+}
+
+void setDebugCallback(DebugCallback cb)
+{
+}
+
+Debug enabledDebug()
+{
+ return Debug();
+}
+
+Debug enabledWarning()
+{
+ return Debug();
+}
+
+void Debug::invokeDebugCallback()
+{
+}
+
+#endif /* !defined(ENABLE_DEBUG) */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/debug.h b/qt4/TelepathyQt4/debug.h
new file mode 100644
index 000000000..31e723178
--- /dev/null
+++ b/qt4/TelepathyQt4/debug.h
@@ -0,0 +1,46 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_debug_h_HEADER_GUARD_
+#define _TelepathyQt4_debug_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+namespace Tp
+{
+
+TELEPATHY_QT4_EXPORT void enableDebug(bool enable);
+TELEPATHY_QT4_EXPORT void enableWarnings(bool enable);
+
+typedef void (*DebugCallback)(const QString &libraryName,
+ const QString &libraryVersion,
+ QtMsgType type,
+ const QString &msg);
+TELEPATHY_QT4_EXPORT void setDebugCallback(DebugCallback cb);
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/examples.dox b/qt4/TelepathyQt4/examples.dox
new file mode 100644
index 000000000..decb3c21b
--- /dev/null
+++ b/qt4/TelepathyQt4/examples.dox
@@ -0,0 +1,154 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \page accounts_example Accounts Example
+ *
+ * \li \subpage accounts_example_account_item_cpp
+ * \li \subpage accounts_example_account_item_h
+ * \li \subpage accounts_example_accounts_window_cpp
+ * \li \subpage accounts_example_accounts_window_h
+ * \li \subpage accounts_example_main
+ */
+
+/**
+ * \page accounts_example_account_item_cpp accounts/account-item.cpp
+ * \include examples/accounts/account-item.cpp
+ */
+
+/**
+ * \page accounts_example_account_item_h accounts/account-item.h
+ * \include examples/accounts/account-item.h
+ */
+
+/**
+ * \page accounts_example_accounts_window_cpp accounts/accounts-window.cpp
+ * \include examples/accounts/accounts-window.cpp
+ */
+
+/**
+ * \page accounts_example_accounts_window_h accounts/accounts-window.h
+ * \include examples/accounts/accounts-window.h
+ */
+
+/**
+ * \page accounts_example_main accounts/main.cpp
+ * \include examples/accounts/main.cpp
+ */
+
+/**
+ * \page contact_messenger_example Contact Messenger Example
+ *
+ * \li \subpage contact_messenger_example_sender_cpp
+ * \li \subpage contact_messenger_example_sender_h
+ */
+
+/**
+ * \page contact_messenger_example_sender_cpp contact-messenger/sender.cpp
+ * \include examples/contact-messenger/sender.cpp
+ */
+
+/**
+ * \page contact_messenger_example_sender_h contact-messenger/sender.h
+ * \include examples/contact-messenger/sender.h
+ */
+
+/**
+ * \page protocols_example Protocols Example
+ *
+ * \li \subpage protocols_example_main
+ * \li \subpage protocols_example_cm_wrapper_cpp
+ * \li \subpage protocols_example_cm_wrapper_h
+ * \li \subpage protocols_example_protocols_cpp
+ * \li \subpage protocols_example_protocols_h
+ */
+
+/**
+ * \page protocols_example_main protocols/main.cpp
+ * \include examples/protocols/main.cpp
+ */
+
+/**
+ * \page protocols_example_cm_wrapper_cpp protocols/cm-wrapper.cpp
+ * \include examples/protocols/cm-wrapper.cpp
+ */
+
+/**
+ * \page protocols_example_cm_wrapper_h protocols/cm-wrapper.h
+ * \include examples/protocols/cm-wrapper.h
+ */
+
+/**
+ * \page protocols_example_protocols_cpp protocols/protocols.cpp
+ * \include examples/protocols/protocols.cpp
+ */
+
+/**
+ * \page protocols_example_protocols_h protocols/protocols.h
+ * \include examples/protocols/protocols.h
+ */
+
+/**
+ * \page roster_example Roster Example
+ *
+ * \li \subpage roster_example_main
+ * \li \subpage roster_example_roster_item_cpp
+ * \li \subpage roster_example_roster_item_h
+ * \li \subpage roster_example_roster_widget_cpp
+ * \li \subpage roster_example_roster_widget_h
+ * \li \subpage roster_example_roster_window_cpp
+ * \li \subpage roster_example_roster_window_h
+ */
+
+/**
+ * \page roster_example_main roster/main.cpp
+ * \include examples/roster/main.cpp
+ */
+
+/**
+ * \page roster_example_roster_item_cpp roster/roster-item.cpp
+ * \include examples/roster/roster-item.cpp
+ */
+
+/**
+ * \page roster_example_roster_item_h roster/roster-item.h
+ * \include examples/roster/roster-item.h
+ */
+
+/**
+ * \page roster_example_roster_widget_cpp roster/roster-widget.cpp
+ * \include examples/roster/roster-widget.cpp
+ */
+
+/**
+ * \page roster_example_roster_widget_h roster/roster-widget.h
+ * \include examples/roster/roster-widget.h
+ */
+
+/**
+ * \page roster_example_roster_window_cpp roster/roster-window.cpp
+ * \include examples/roster/roster-window.cpp
+ */
+
+/**
+ * \page roster_example_roster_window_h roster/roster-window.h
+ * \include examples/roster/roster-window.h
+ */
diff --git a/qt4/TelepathyQt4/fake-handler-manager-internal.cpp b/qt4/TelepathyQt4/fake-handler-manager-internal.cpp
new file mode 100644
index 000000000..f0c595542
--- /dev/null
+++ b/qt4/TelepathyQt4/fake-handler-manager-internal.cpp
@@ -0,0 +1,165 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "TelepathyQt4/fake-handler-manager-internal.h"
+
+#include "TelepathyQt4/_gen/fake-handler-manager-internal.moc.hpp"
+
+#include <TelepathyQt4/Channel>
+
+namespace Tp
+{
+
+FakeHandler::FakeHandler(const QDBusConnection &bus)
+ : QObject(),
+ mBus(bus)
+{
+}
+
+FakeHandler::~FakeHandler()
+{
+}
+
+ObjectPathList FakeHandler::handledChannels() const
+{
+ ObjectPathList ret;
+ foreach (const Channel *channel, mChannels) {
+ ret << QDBusObjectPath(channel->objectPath());
+ }
+ return ret;
+}
+
+void FakeHandler::registerChannel(const ChannelPtr &channel)
+{
+ if (mChannels.contains(channel.data())) {
+ return;
+ }
+
+ mChannels.insert(channel.data());
+ connect(channel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::DBusProxy*)));
+ connect(channel.data(),
+ SIGNAL(destroyed(QObject*)),
+ SLOT(onChannelDestroyed(QObject*)));
+}
+
+void FakeHandler::onChannelInvalidated(DBusProxy *channel)
+{
+ disconnect(channel, SIGNAL(destroyed(QObject*)), this, SLOT(onChannelDestroyed(QObject*)));
+ onChannelDestroyed(channel);
+}
+
+void FakeHandler::onChannelDestroyed(QObject *obj)
+{
+ Channel *channel = reinterpret_cast<Channel*>(obj);
+
+ Q_ASSERT(mChannels.contains(channel));
+
+ mChannels.remove(channel);
+
+ if (mChannels.isEmpty()) {
+ // emit invalidated here instead of relying on QObject::destroyed as FakeHandlerManager
+ // may reuse this fake handler if FakeHandlerManager::registerChannel is called before the
+ // slot from QObject::destroyed is invoked (deleteLater()).
+ emit invalidated(this);
+ deleteLater();
+ }
+}
+
+FakeHandlerManager *FakeHandlerManager::mInstance = 0;
+
+FakeHandlerManager *FakeHandlerManager::instance()
+{
+ if (!mInstance) {
+ mInstance = new FakeHandlerManager();
+ }
+ return mInstance;
+}
+
+FakeHandlerManager::FakeHandlerManager()
+ : QObject()
+{
+}
+
+FakeHandlerManager::~FakeHandlerManager()
+{
+ mInstance = 0;
+}
+
+ObjectPathList FakeHandlerManager::handledChannels(const QDBusConnection &bus) const
+{
+ QPair<QString, QString> busUniqueId(bus.name(), bus.baseService());
+ if (mFakeHandlers.contains(busUniqueId)) {
+ FakeHandler *fakeHandler = mFakeHandlers.value(busUniqueId);
+ return fakeHandler->handledChannels();
+ }
+ return ObjectPathList();
+}
+
+void FakeHandlerManager::registerClientRegistrar(const ClientRegistrarPtr &cr)
+{
+ QDBusConnection bus(cr->dbusConnection());
+ QPair<QString, QString> busUniqueId(bus.name(), bus.baseService());
+ // keep one registrar around per bus so at least the handlers registered by it will be
+ // around until all channels on that bus gets invalidated/destroyed
+ if (!mClientRegistrars.contains(busUniqueId)) {
+ mClientRegistrars.insert(busUniqueId, cr);
+ }
+}
+
+void FakeHandlerManager::registerChannels(const QList<ChannelPtr> &channels)
+{
+ foreach (const ChannelPtr &channel, channels) {
+ QDBusConnection bus(channel->dbusConnection());
+ QPair<QString, QString> busUniqueId(bus.name(), bus.baseService());
+ FakeHandler *fakeHandler = mFakeHandlers.value(busUniqueId);
+ if (!fakeHandler) {
+ fakeHandler = new FakeHandler(bus);
+ mFakeHandlers.insert(busUniqueId, fakeHandler);
+ connect(fakeHandler,
+ SIGNAL(invalidated(Tp::FakeHandler*)),
+ SLOT(onFakeHandlerInvalidated(Tp::FakeHandler*)));
+ }
+ fakeHandler->registerChannel(channel);
+ }
+}
+
+void FakeHandlerManager::onFakeHandlerInvalidated(FakeHandler *fakeHandler)
+{
+ QDBusConnection bus(fakeHandler->dbusConnection());
+ QPair<QString, QString> busUniqueId(bus.name(), bus.baseService());
+ mFakeHandlers.remove(busUniqueId);
+
+ // all channels for the bus represented by busUniqueId were already destroyed/invalidated,
+ // we can now free the CR (thus the handlers registered by it) registered for that bus
+ mClientRegistrars.remove(busUniqueId);
+
+ if (mFakeHandlers.isEmpty()) {
+ // set mInstance to 0 here as we don't want instance() to return a already
+ // deleted instance
+ mInstance = 0;
+ deleteLater();
+ }
+}
+
+}
diff --git a/qt4/TelepathyQt4/fake-handler-manager-internal.h b/qt4/TelepathyQt4/fake-handler-manager-internal.h
new file mode 100644
index 000000000..ad73dc3df
--- /dev/null
+++ b/qt4/TelepathyQt4/fake-handler-manager-internal.h
@@ -0,0 +1,90 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_fake_handler_manager_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_fake_handler_manager_internal_h_HEADER_GUARD_
+
+#include <QObject>
+
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+class FakeHandler : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FakeHandler)
+
+public:
+ FakeHandler(const QDBusConnection &bus);
+ ~FakeHandler();
+
+ QDBusConnection dbusConnection() const { return mBus; }
+ ObjectPathList handledChannels() const;
+ void registerChannel(const ChannelPtr &channel);
+
+Q_SIGNALS:
+ void invalidated(Tp::FakeHandler *self);
+
+private Q_SLOTS:
+ void onChannelInvalidated(Tp::DBusProxy *channel);
+ void onChannelDestroyed(QObject *channel);
+
+private:
+ QDBusConnection mBus;
+ QSet<Channel*> mChannels;
+};
+
+class FakeHandlerManager : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FakeHandlerManager)
+
+public:
+ static FakeHandlerManager *instance();
+
+ ~FakeHandlerManager();
+
+ ObjectPathList handledChannels(const QDBusConnection &bus) const;
+ void registerClientRegistrar(const ClientRegistrarPtr &cr);
+ void registerChannels(const QList<ChannelPtr> &channels);
+
+private Q_SLOTS:
+ void onFakeHandlerInvalidated(Tp::FakeHandler *fakeHandler);
+
+private:
+ FakeHandlerManager();
+
+ static FakeHandlerManager *mInstance;
+ QHash<QPair<QString, QString>, ClientRegistrarPtr> mClientRegistrars;
+ QHash<QPair<QString, QString>, FakeHandler *> mFakeHandlers;
+};
+
+#endif // DOXYGEN_SHOULD_SKIP_THIS
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/feature.cpp b/qt4/TelepathyQt4/feature.cpp
new file mode 100644
index 000000000..676fdad62
--- /dev/null
+++ b/qt4/TelepathyQt4/feature.cpp
@@ -0,0 +1,89 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Feature>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT Feature::Private : public QSharedData
+{
+ Private(bool critical) : critical(critical) {}
+
+ bool critical;
+};
+
+/**
+ * \class Feature
+ * \ingroup utils
+ * \headerfile TelepathyQt4/feature.h <TelepathyQt4/Feature>
+ *
+ * \brief The Feature class represents a feature that can be enabled
+ * on demand.
+ */
+
+Feature::Feature()
+ : QPair<QString, uint>()
+{
+}
+
+Feature::Feature(const QString &className, uint id, bool critical)
+ : QPair<QString, uint>(className, id),
+ mPriv(new Private(critical))
+{
+}
+
+Feature::Feature(const Feature &other)
+ : QPair<QString, uint>(other.first, other.second),
+ mPriv(other.mPriv)
+{
+}
+
+Feature::~Feature()
+{
+}
+
+Feature &Feature::operator=(const Feature &other)
+{
+ *this = other;
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool Feature::isCritical() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return mPriv->critical;
+}
+
+/**
+ * \class Features
+ * \ingroup utils
+ * \headerfile TelepathyQt4/feature.h <TelepathyQt4/Features>
+ *
+ * \brief The Features class represents a list of Feature.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/feature.h b/qt4/TelepathyQt4/feature.h
new file mode 100644
index 000000000..7a9e3cf70
--- /dev/null
+++ b/qt4/TelepathyQt4/feature.h
@@ -0,0 +1,94 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_feature_h_HEADER_GUARD_
+#define _TelepathyQt4_feature_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QMetaType>
+#include <QPair>
+#include <QSet>
+#include <QSharedDataPointer>
+#include <QString>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT Feature : public QPair<QString, uint>
+{
+public:
+ Feature();
+ Feature(const QString &className, uint id, bool critical = false);
+ Feature(const Feature &other);
+ ~Feature();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ Feature &operator=(const Feature &other);
+
+ bool isCritical() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT Features : public QSet<Feature>
+{
+public:
+ Features() { }
+ Features(const Feature &feature) { insert(feature); }
+ Features(const QSet<Feature> &s) : QSet<Feature>(s) { }
+};
+
+inline Features operator|(const Feature &feature1, const Feature &feature2)
+{
+ return Features() << feature1 << feature2;
+}
+
+inline Features operator|(const Features &features, const Feature &feature)
+{
+ return Features(features) << feature;
+}
+
+inline uint qHash(const Features &features)
+{
+ int ret = 0;
+ Q_FOREACH (const Feature &feature, features) {
+ int h = qHash(feature);
+ ret ^= h;
+ }
+ return ret;
+}
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::Feature);
+Q_DECLARE_METATYPE(Tp::Features);
+
+#endif
diff --git a/qt4/TelepathyQt4/file-transfer-channel-creation-properties.cpp b/qt4/TelepathyQt4/file-transfer-channel-creation-properties.cpp
new file mode 100644
index 000000000..fab7b4a96
--- /dev/null
+++ b/qt4/TelepathyQt4/file-transfer-channel-creation-properties.cpp
@@ -0,0 +1,433 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/FileTransferChannelCreationProperties>
+
+#include <TelepathyQt4/Global>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <QSharedData>
+#include <QFileInfo>
+#include <QUrl>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT FileTransferChannelCreationProperties::Private : public QSharedData
+{
+ Private(const QString &suggestedFileName, const QString &contentType,
+ qulonglong size)
+ : contentType(contentType),
+ size(size),
+ contentHashType(FileHashTypeNone)
+ {
+ QFileInfo fileInfo(suggestedFileName);
+ this->suggestedFileName = fileInfo.fileName();
+ }
+
+ Private(const QString &path, const QString &contentType)
+ : contentType(contentType),
+ contentHashType(FileHashTypeNone)
+ {
+ QFileInfo fileInfo(path);
+
+ if (fileInfo.exists()) {
+ // Set mandatory parameters
+ suggestedFileName = fileInfo.fileName();
+ size = fileInfo.size();
+ QUrl fileUri = QUrl::fromLocalFile(fileInfo.canonicalFilePath());
+ uri = fileUri.toString();
+
+ // Set optional parameters
+ lastModificationTime = fileInfo.lastModified();
+ } else {
+ warning() << path << "is not a local file.";
+ }
+ }
+
+ /* mandatory parameters */
+ QString suggestedFileName;
+ QString contentType;
+ qulonglong size;
+
+ /* optional parameters */
+ FileHashType contentHashType;
+ QString contentHash;
+ QString description;
+ QDateTime lastModificationTime;
+ QString uri;
+};
+
+/**
+ * \class FileTransferChannelCreationProperties
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/file-transfer-channel-creation-properties.h <TelepathyQt4/FileTransferChannelCreationProperties>
+ *
+ * \brief The FileTransferChannelCreationProperties class represents the
+ * properties of a file transfer channel request.
+ */
+
+/**
+ * Create an invalid FileTransferChannelCreationProperties.
+ */
+FileTransferChannelCreationProperties::FileTransferChannelCreationProperties()
+{
+}
+
+/**
+ * Create a FileTransferChannelCreationProperties.
+ *
+ * If \a suggestedFileName or \a contentType are empty or if \a size is equal to
+ * zero, the channel request will fail.
+ * \a suggestedFileName will be cleaned of any path.
+ *
+ * \param suggestedFileName The name of the file on the sender's side. This is
+ * therefore given as a suggested filename for the
+ * receiver.
+ * \param contentType The content type (MIME) of the file.
+ * \param size The size of the content of the file.
+ * \sa setUri()
+ */
+FileTransferChannelCreationProperties::FileTransferChannelCreationProperties(
+ const QString &suggestedFileName, const QString &contentType,
+ qulonglong size)
+ : mPriv(new Private(suggestedFileName, contentType, size))
+{
+}
+
+/**
+ * Create a FileTransferChannelCreationProperties.
+ *
+ * This constructor accepts the path to a local file and sets the properties
+ * that can be deducted from the file.
+ * If \a path is not a local file the FileTransferChannelCreationProperties
+ * will be invalid.
+ *
+ * \param path The path to the local file to be sent.
+ */
+FileTransferChannelCreationProperties::FileTransferChannelCreationProperties(
+ const QString &path, const QString &contentType)
+ : mPriv(new Private(path, contentType))
+{
+ if (mPriv->suggestedFileName.isEmpty()) {
+ mPriv = QSharedDataPointer<Private>(NULL);
+ }
+}
+
+/**
+ * Copy constructor.
+ */
+FileTransferChannelCreationProperties::FileTransferChannelCreationProperties(
+ const FileTransferChannelCreationProperties &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+FileTransferChannelCreationProperties::~FileTransferChannelCreationProperties()
+{
+}
+
+FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::operator=(
+ const FileTransferChannelCreationProperties &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool FileTransferChannelCreationProperties::operator==(
+ const FileTransferChannelCreationProperties &other) const
+{
+ return mPriv == other.mPriv;
+}
+
+/**
+ * Set the content hash of the file and its type for the request.
+ *
+ * \param contentHashType The type of content hash.
+ * \param contentHash The hash of the file, of type \a contentHashType.
+ * \return This FileTransferChannelCreationProperties.
+ * \sa hasContentHash(), contentHash(), contentHashType()
+ */
+FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setContentHash(
+ FileHashType contentHashType, const QString &contentHash)
+{
+ if (!isValid()) {
+ // there is no point in updating content hash if not valid, as we miss filename, content
+ // type and size
+ return *this;
+ }
+
+ mPriv->contentHashType = contentHashType;
+ mPriv->contentHash = contentHash;
+ return *this;
+}
+
+/**
+ * Set a description of the file for the request.
+ *
+ * \param description The description of the file.
+ * \return This FileTransferChannelCreationProperties.
+ * \sa hasDescription(), description()
+ */
+FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setDescription(
+ const QString &description)
+{
+ if (!isValid()) {
+ // there is no point in updating description if not valid, as we miss filename, content
+ // type and size
+ return *this;
+ }
+
+ mPriv->description = description;
+ return *this;
+}
+
+/**
+ * Set the last modification time of the file for the request.
+ *
+ * \param lastModificationTime The last modification time of the file.
+ * \return This FileTransferChannelCreationProperties.
+ * \sa hasLastModificationTime(), lastModificationTime()
+ */
+FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setLastModificationTime(
+ const QDateTime &lastModificationTime)
+{
+ if (!isValid()) {
+ // there is no point in updating last modification time if not valid, as we miss filename,
+ // content type and size
+ return *this;
+ }
+
+ mPriv->lastModificationTime = lastModificationTime;
+ return *this;
+}
+
+/**
+ * Set the URI of the file for the request.
+ *
+ * \param uri The URI of the file.
+ * \return This FileTransferChannelCreationProperties.
+ * \sa uri()
+ */
+FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setUri(
+ const QString &uri)
+{
+ if (!isValid()) {
+ // there is no point in updating uri if not valid, as we miss filename, content
+ // type and size
+ return *this;
+ }
+
+ mPriv->uri = uri;
+ return *this;
+}
+
+/**
+ * Return the suggested file name for the request.
+ * If the suggested file name is empty, the channel request will fail.
+ *
+ * \return The suggested file name for the request.
+ */
+QString FileTransferChannelCreationProperties::suggestedFileName() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->suggestedFileName;
+}
+
+/**
+ * Return the content type (MIME) of the file for the request.
+ * If the content type is empty, the channel request will fail.
+ *
+ * \return The content type of the file.
+ */
+QString FileTransferChannelCreationProperties::contentType() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->contentType;
+}
+
+/**
+ * Return the size of the contents of the file for the request.
+ * If size is zero, the channel request will fail.
+ *
+ * \return The size of the contents of file.
+ */
+qulonglong FileTransferChannelCreationProperties::size() const
+{
+ if (!isValid()) {
+ return 0;
+ }
+
+ return mPriv->size;
+}
+
+/**
+ * Return whether the request will have a content hash.
+ *
+ * \return \c true whether it will have a content hash, \c false otherwise.
+ * \sa contentHash(), contentHashType(), setContentHash()
+ */
+bool FileTransferChannelCreationProperties::hasContentHash() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return (mPriv->contentHashType != FileHashTypeNone);
+}
+
+/**
+ * Return the type of the content hash for the request.
+ *
+ * \return The type of the content hash.
+ * \sa hasContentHash(), contentHash(), setContentHash()
+ */
+FileHashType FileTransferChannelCreationProperties::contentHashType() const
+{
+ if (!isValid()) {
+ return FileHashTypeNone;
+ }
+
+ return mPriv->contentHashType;
+}
+
+/**
+ * Return the content hash of the file for the request.
+ *
+ * \return The hash of the contents of the file transfer, of type returned by
+ * contentHashType().
+ * \sa hasContentHash(), contentHashType(), setContentHash()
+ */
+QString FileTransferChannelCreationProperties::contentHash() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->contentHash;
+}
+
+/**
+ * Return whether the request will have a descriprion.
+ *
+ * \return \c true whether it will have description, \c false otherwise.
+ * \sa description(), setDescription()
+ */
+bool FileTransferChannelCreationProperties::hasDescription() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return (!mPriv->description.isEmpty());
+}
+
+/**
+ * Return the description of the file for the request.
+ *
+ * \return The description of the file.
+ * \sa hasDescription(), setDescription()
+ */
+QString FileTransferChannelCreationProperties::description() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->description;
+}
+
+/**
+ * Return whether the request will have a last modification time.
+ *
+ * \return \c true whether it will have a last modification time, \c false
+ * otherwise.
+ * \sa lastModificationTime(), setLastModificationTime()
+ */
+bool FileTransferChannelCreationProperties::hasLastModificationTime() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return (mPriv->lastModificationTime.isValid());
+}
+
+/**
+ * Return the last modification time of the file for the request.
+ *
+ * \return The last modification time of the file.
+ * \sa hasLastModificationTime(), setLastModificationTime()
+ */
+QDateTime FileTransferChannelCreationProperties::lastModificationTime() const
+{
+ if (!isValid()) {
+ return QDateTime();
+ }
+
+ return mPriv->lastModificationTime;
+}
+
+/**
+ * Return whether the request will have an URI.
+ *
+ * \return \c true whether it will have URI, \c false otherwise.
+ * \sa uri(), setUri()
+ */
+bool FileTransferChannelCreationProperties::hasUri() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return (!mPriv->uri.isEmpty());
+}
+
+/**
+ * Return the URI of the file for the request.
+ * If the URI property is empty and the file transfer is handled by an handler
+ * that is not this process, then it won't be able to initiate the file
+ * transfer.
+ *
+ * \return The URI of the file.
+ * \sa setUri()
+ */
+QString FileTransferChannelCreationProperties::uri() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->uri;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/file-transfer-channel-creation-properties.h b/qt4/TelepathyQt4/file-transfer-channel-creation-properties.h
new file mode 100644
index 000000000..1d759acc7
--- /dev/null
+++ b/qt4/TelepathyQt4/file-transfer-channel-creation-properties.h
@@ -0,0 +1,96 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_file_transfer_channel_creation_properties_h_HEADER_GUARD_
+#define _TelepathyQt4_file_transfer_channel_creation_properties_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Global>
+
+#include <QDateTime>
+#include <QMetaType>
+#include <QSharedDataPointer>
+#include <QString>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT FileTransferChannelCreationProperties
+{
+public:
+ FileTransferChannelCreationProperties();
+ FileTransferChannelCreationProperties(const QString &suggestedFileName,
+ const QString &contentType, qulonglong size);
+ FileTransferChannelCreationProperties(const QString &path,
+ const QString &contentType);
+ FileTransferChannelCreationProperties(
+ const FileTransferChannelCreationProperties &other);
+ ~FileTransferChannelCreationProperties();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ FileTransferChannelCreationProperties &operator=(
+ const FileTransferChannelCreationProperties &other);
+ bool operator==(const FileTransferChannelCreationProperties &other) const;
+
+ FileTransferChannelCreationProperties &setContentHash(
+ FileHashType contentHashType, const QString &contentHash);
+ FileTransferChannelCreationProperties &setDescription(
+ const QString &description);
+ FileTransferChannelCreationProperties &setLastModificationTime(
+ const QDateTime &lastModificationTime);
+ FileTransferChannelCreationProperties &setUri(const QString &uri);
+
+ /* mandatory parameters */
+ QString suggestedFileName() const;
+ QString contentType() const;
+ qulonglong size() const;
+
+ /* optional parameters */
+ bool hasContentHash() const;
+ FileHashType contentHashType() const;
+ QString contentHash() const;
+
+ bool hasDescription() const;
+ QString description() const;
+
+ bool hasLastModificationTime() const;
+ QDateTime lastModificationTime() const;
+
+ bool hasUri() const;
+ QString uri() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::FileTransferChannelCreationProperties);
+
+#endif
diff --git a/qt4/TelepathyQt4/file-transfer-channel.cpp b/qt4/TelepathyQt4/file-transfer-channel.cpp
new file mode 100644
index 000000000..28c7662ae
--- /dev/null
+++ b/qt4/TelepathyQt4/file-transfer-channel.cpp
@@ -0,0 +1,703 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/FileTransferChannel>
+
+#include "TelepathyQt4/_gen/file-transfer-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT FileTransferChannel::Private
+{
+ Private(FileTransferChannel *parent);
+ ~Private();
+
+ static void introspectProperties(Private *self);
+
+ void extractProperties(const QVariantMap &props);
+
+ // Public object
+ FileTransferChannel *parent;
+
+ Client::ChannelTypeFileTransferInterface *fileTransferInterface;
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ uint pendingState;
+ uint pendingStateReason;
+ uint state;
+ uint stateReason;
+ QString contentType;
+ QString fileName;
+ QString uri;
+ QString contentHash;
+ QString description;
+ QDateTime lastModificationTime;
+ FileHashType contentHashType;
+ qulonglong initialOffset;
+ qulonglong size;
+ qulonglong transferredBytes;
+ SupportedSocketMap availableSocketTypes;
+
+ bool connected;
+ bool finished;
+};
+
+FileTransferChannel::Private::Private(FileTransferChannel *parent)
+ : parent(parent),
+ fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ pendingState(FileTransferStateNone),
+ pendingStateReason(FileTransferStateChangeReasonNone),
+ state(pendingState),
+ stateReason(pendingStateReason),
+ contentHashType(FileHashTypeNone),
+ initialOffset(0),
+ size(0),
+ transferredBytes(0),
+ connected(false),
+ finished(false)
+{
+ parent->connect(fileTransferInterface,
+ SIGNAL(InitialOffsetDefined(qulonglong)),
+ SLOT(onInitialOffsetDefined(qulonglong)));
+ parent->connect(fileTransferInterface,
+ SIGNAL(FileTransferStateChanged(uint,uint)),
+ SLOT(onStateChanged(uint,uint)));
+ parent->connect(fileTransferInterface,
+ SIGNAL(TransferredBytesChanged(qulonglong)),
+ SLOT(onTransferredBytesChanged(qulonglong)));
+
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectProperties,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+FileTransferChannel::Private::~Private()
+{
+}
+
+void FileTransferChannel::Private::introspectProperties(
+ FileTransferChannel::Private *self)
+{
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER)),
+ self->parent);
+ self->parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotProperties(QDBusPendingCallWatcher*)));
+}
+
+void FileTransferChannel::Private::extractProperties(const QVariantMap &props)
+{
+ pendingState = state = qdbus_cast<uint>(props[QLatin1String("State")]);
+ contentType = qdbus_cast<QString>(props[QLatin1String("ContentType")]);
+ fileName = qdbus_cast<QString>(props[QLatin1String("Filename")]);
+ uri = qdbus_cast<QString>(props[QLatin1String("URI")]);
+ contentHash = qdbus_cast<QString>(props[QLatin1String("ContentHash")]);
+ description = qdbus_cast<QString>(props[QLatin1String("Description")]);
+ lastModificationTime.setTime_t((uint) qdbus_cast<qulonglong>(props[QLatin1String("Date")]));
+ contentHashType = (FileHashType) qdbus_cast<uint>(props[QLatin1String("ContentHashType")]);
+ initialOffset = qdbus_cast<qulonglong>(props[QLatin1String("InitialOffset")]);
+ size = qdbus_cast<qulonglong>(props[QLatin1String("Size")]);
+ transferredBytes = qdbus_cast<qulonglong>(props[QLatin1String("TransferredBytes")]);
+ availableSocketTypes = qdbus_cast<SupportedSocketMap>(props[QLatin1String("AvailableSocketTypes")]);
+}
+
+/**
+ * \class FileTransferChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/file-transfer-channel.h <TelepathyQt4/FileTransferChannel>
+ *
+ * \brief The FileTransferChannel class represents a Telepathy channel of type
+ * FileTransfer.
+ *
+ * For more specialized file transfer classes, please refer to
+ * OutgoingFileTransferChannel and IncomingFileTransferChannel.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * FileTransferChannel object usable.
+ *
+ * Note that this feature must be enabled in order to use most
+ * FileTransferChannel methods.
+ * See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature FileTransferChannel::FeatureCore = Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0);
+
+/**
+ * Create a new FileTransferChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A FileTransferChannelPtr object pointing to the newly created
+ * FileTransferChannel object.
+ */
+FileTransferChannelPtr FileTransferChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return FileTransferChannelPtr(new FileTransferChannel(connection, objectPath,
+ immutableProperties, FileTransferChannel::FeatureCore));
+}
+
+/**
+ * Construct a new FileTransferChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on FileTransferChannel::FeatureCore.
+ */
+FileTransferChannel::FileTransferChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : Channel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+FileTransferChannel::~FileTransferChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the state of the file transfer as described by #FileTransferState.
+ *
+ * Change notification is via the stateChanged() signal.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The state as #FileTransferState.
+ * \sa stateReason()
+ */
+FileTransferState FileTransferChannel::state() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling state";
+ }
+
+ return (FileTransferState) mPriv->state;
+}
+
+/**
+ * Return the reason for the state change as described by the #FileTransferStateChangeReason.
+ *
+ * Change notification is via the stateChanged() signal.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The state reason as #FileTransferStateChangeReason.
+ * \sa state()
+ */
+FileTransferStateChangeReason FileTransferChannel::stateReason() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling stateReason";
+ }
+
+ return (FileTransferStateChangeReason) mPriv->stateReason;
+}
+
+/**
+ * Return the name of the file on the sender's side. This is given as
+ * a suggested filename for the receiver.
+ *
+ * This property should be the basename of the file being sent. For example, if
+ * the sender sends the file /home/user/monkey.pdf then this property should be
+ * set to monkey.pdf.
+ *
+ * This property cannot change once the channel has been created.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The suggested filename for the receiver.
+ */
+QString FileTransferChannel::fileName() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling fileName";
+ }
+
+ return mPriv->fileName;
+}
+
+/**
+ * Return the file's MIME type.
+ *
+ * This property cannot change once the channel has been created.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The file's MIME type.
+ */
+QString FileTransferChannel::contentType() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling contentType";
+ }
+
+ return mPriv->contentType;
+}
+
+/**
+ * Return the size of the file.
+ *
+ * Note that the size is not guaranteed to be exactly right for
+ * incoming files. This is merely a hint and should not be used to know when the
+ * transfer finished.
+ *
+ * For unknown sizes the return value can be UINT64_MAX.
+ *
+ * This property cannot change once the channel has been created.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The file size.
+ */
+qulonglong FileTransferChannel::size() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling size";
+ }
+
+ return mPriv->size;
+}
+
+/**
+ * Return the URI of the file.
+ *
+ * On outgoing file transfers, this property cannot change after the channel
+ * is requested. For incoming file transfers, this property may be set by the
+ * channel handler before calling AcceptFile to inform observers where the
+ * incoming file will be saved. When the URI property is set, the signal
+ * IncomingFileTransferChannel::uriDefined() is emitted.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The file uri.
+ * \sa IncomingFileTransferChannel::uriDefined()
+ */
+QString FileTransferChannel::uri() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling uri";
+ }
+
+ return mPriv->uri;
+}
+
+/**
+ * Return the type of the contentHash().
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The content hash type as #FileHashType.
+ * \sa contentHash()
+ */
+FileHashType FileTransferChannel::contentHashType() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling contentHashType";
+ }
+
+ return mPriv->contentHashType;
+}
+
+/**
+ * Return the hash of the contents of the file transfer, of type described in
+ * the value of the contentHashType().
+ *
+ * Its value MUST correspond to the appropriate type of the contentHashType().
+ * If the contentHashType() is set to #FileHashTypeNone, then the
+ * returned value is an empty string.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The hash of the contents.
+ * \sa contentHashType()
+ */
+QString FileTransferChannel::contentHash() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling contentHash";
+ }
+
+ if (mPriv->contentHashType == FileHashTypeNone) {
+ return QString();
+ }
+
+ return mPriv->contentHash;
+}
+
+/**
+ * Return the description of the file transfer.
+ *
+ * This property cannot change once the channel has been created.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The description.
+ */
+QString FileTransferChannel::description() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling description";
+ }
+
+ return mPriv->description;
+}
+
+/**
+ * Return the last modification time of the file being transferred. This cannot
+ * change once the channel has been created.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The file modification time as QDateTime.
+ */
+QDateTime FileTransferChannel::lastModificationTime() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling lastModificationTime";
+ }
+
+ return mPriv->lastModificationTime;
+}
+
+/**
+ * Return the offset in bytes from which the file will be sent.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The offset in bytes.
+ * \sa initialOffsetDefined()
+ */
+qulonglong FileTransferChannel::initialOffset() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling initialOffset";
+ }
+
+ return mPriv->initialOffset;
+}
+
+/**
+ * Return the number of bytes that have been transferred.
+ *
+ * Change notification is via the transferredBytesChanged() signal.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The number of bytes.
+ * \sa transferredBytesChanged()
+ */
+qulonglong FileTransferChannel::transferredBytes() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling transferredBytes";
+ }
+
+ return mPriv->transferredBytes;
+}
+
+/**
+ * Return a mapping from address types (members of #SocketAddressType) to arrays
+ * of access-control type (members of #SocketAccessControl) that the CM
+ * supports for sockets with that address type.
+ *
+ * For simplicity, if a CM supports offering a particular type of file transfer,
+ * it is assumed to support accepting it. All CMs support at least
+ * SocketAddressTypeIPv4.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \return The available socket types as a map from address types to arrays of access-control type.
+ */
+SupportedSocketMap FileTransferChannel::availableSocketTypes() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling availableSocketTypes";
+ }
+
+ return mPriv->availableSocketTypes;
+}
+
+/**
+ * Cancel a file transfer.
+ *
+ * \return A PendingOperation object which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *FileTransferChannel::cancel()
+{
+ return requestClose();
+}
+
+/**
+ * Protected virtual method called when the state becomes
+ * #FileTransferStateOpen.
+ *
+ * Specialized classes should reimplement this method and call setConnected()
+ * when the connection is established.
+ *
+ * \sa setConnected()
+ */
+void FileTransferChannel::connectToHost()
+{
+ // do nothing
+}
+
+/**
+ * Return whether a connection has been established.
+ *
+ * \return \c true if the connections has been established, \c false otherwise.
+ * \sa setConnected()
+ */
+bool FileTransferChannel::isConnected() const
+{
+ return mPriv->connected;
+}
+
+/**
+ * Indicate whether a connection has been established.
+ *
+ * Specialized classes that reimplement connectToHost() must call this method
+ * once the connection has been established or setFinished() if an error
+ * occurred.
+ *
+ * \sa isConnected(), connectToHost(), setFinished()
+ */
+void FileTransferChannel::setConnected()
+{
+ mPriv->connected = true;
+}
+
+/**
+ * Return whether sending/receiving has finished.
+ *
+ * \return \c true if sending/receiving has finished, \c false otherwise.
+ */
+bool FileTransferChannel::isFinished() const
+{
+ return mPriv->finished;
+}
+
+/**
+ * Protected virtual method called when an error occurred and the transfer
+ * should finish.
+ *
+ * Specialized classes should reimplement this method and close the IO devices
+ * and do all the needed cleanup.
+ *
+ * Note that for specialized classes that reimplement connectToHost() and set
+ * isConnected() to true, the state will not change to
+ * #FileTransferStateCompleted once the state change is received.
+ *
+ * When finished sending/receiving the specialized class MUST call this method
+ * and then the state will change to the latest pending state.
+ */
+void FileTransferChannel::setFinished()
+{
+ mPriv->finished = true;
+
+ // do the actual state change, in case we are in
+ // FileTransferStateCompleted pendingState
+ changeState();
+}
+
+void FileTransferChannel::gotProperties(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ QVariantMap props = reply.value();
+ mPriv->extractProperties(props);
+ debug() << "Got reply to Properties::GetAll(FileTransferChannel)";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ }
+ else {
+ warning().nospace() << "Properties::GetAll(FileTransferChannel) failed "
+ "with " << reply.error().name() << ": " << reply.error().message();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ reply.error());
+ }
+}
+
+void FileTransferChannel::changeState()
+{
+ if (mPriv->state == mPriv->pendingState) {
+ return;
+ }
+
+ mPriv->state = mPriv->pendingState;
+ mPriv->stateReason = mPriv->pendingStateReason;
+ emit stateChanged((FileTransferState) mPriv->state,
+ (FileTransferStateChangeReason) mPriv->stateReason);
+}
+
+void FileTransferChannel::onStateChanged(uint state, uint stateReason)
+{
+ if (state == (uint) mPriv->pendingState) {
+ return;
+ }
+
+ debug() << "File transfer state changed to" << state <<
+ "with reason" << stateReason;
+ mPriv->pendingState = (FileTransferState) state;
+ mPriv->pendingStateReason = (FileTransferStateChangeReason) stateReason;
+
+ switch (state) {
+ case FileTransferStateOpen:
+ // try to connect to host, for handlers this
+ // connect to host, as the user called Accept/ProvideFile
+ // and have the host addr, for observers this will do nothing and
+ // everything will keep working
+ connectToHost();
+ changeState();
+ break;
+ case FileTransferStateCompleted:
+ //iIf already finished sending/receiving, just change the state,
+ // if not completed will only be set when:
+ // IncomingChannel:
+ // - The input socket closes
+ // OutgoingChannel:
+ // - Input EOF is reached or the output socket is closed
+ //
+ // we also check for connected as observers will never be connected
+ // and finished will never be set, but we need to work anyway.
+ if (mPriv->finished || !mPriv->connected) {
+ changeState();
+ }
+ break;
+ case FileTransferStateCancelled:
+ // if already finished sending/receiving, just change the state,
+ // if not finish now and change the state
+ if (!mPriv->finished) {
+ setFinished();
+ } else {
+ changeState();
+ }
+ break;
+ default:
+ changeState();
+ break;
+ }
+}
+
+void FileTransferChannel::onInitialOffsetDefined(qulonglong initialOffset)
+{
+ mPriv->initialOffset = initialOffset;
+ emit initialOffsetDefined(initialOffset);
+}
+
+void FileTransferChannel::onTransferredBytesChanged(qulonglong count)
+{
+ mPriv->transferredBytes = count;
+ emit transferredBytesChanged(count);
+}
+
+void FileTransferChannel::onUriDefined(const QString &uri)
+{
+ mPriv->uri = uri;
+ // Signal is emitted only by IncomingFileTransferChannels
+}
+
+/**
+ * \fn void FileTransferChannel::stateChanged(Tp::FileTransferState state,
+ * Tp::FileTransferStateChangeReason reason)
+ *
+ * Emitted when the value of state() changes.
+ *
+ * \param state The new state of this file transfer channel.
+ * \param reason The reason for the change of state.
+ * \sa state()
+ */
+
+/**
+ * \fn void FileTransferChannel::initialOffsetDefined(qulonglong initialOffset)
+ *
+ * Emitted when the initial offset for the file transfer is
+ * defined.
+ *
+ * \param initialOffset The new initial offset for the file transfer.
+ * \sa initialOffset()
+ */
+
+/**
+ * \fn void FileTransferChannel::transferredBytesChanged(qulonglong count);
+ *
+ * Emitted when the value of transferredBytes() changes.
+ *
+ * \param count The new number of bytes transferred.
+ * \sa transferredBytes()
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/file-transfer-channel.h b/qt4/TelepathyQt4/file-transfer-channel.h
new file mode 100644
index 000000000..1e27e5870
--- /dev/null
+++ b/qt4/TelepathyQt4/file-transfer-channel.h
@@ -0,0 +1,108 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_file_transfer_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_file_transfer_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT FileTransferChannel : public Channel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FileTransferChannel)
+
+public:
+ static const Feature FeatureCore;
+
+ static FileTransferChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~FileTransferChannel();
+
+ FileTransferState state() const;
+ FileTransferStateChangeReason stateReason() const;
+
+ QString fileName() const;
+ QString contentType() const;
+ qulonglong size() const;
+ QString uri() const;
+
+ FileHashType contentHashType() const;
+ QString contentHash() const;
+
+ QString description() const;
+
+ QDateTime lastModificationTime() const;
+
+ qulonglong initialOffset() const;
+
+ qulonglong transferredBytes() const;
+
+ PendingOperation *cancel();
+
+Q_SIGNALS:
+ void stateChanged(Tp::FileTransferState state,
+ Tp::FileTransferStateChangeReason reason);
+ void initialOffsetDefined(qulonglong initialOffset);
+ void transferredBytesChanged(qulonglong count);
+
+protected:
+ FileTransferChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = FileTransferChannel::FeatureCore);
+
+ SupportedSocketMap availableSocketTypes() const;
+
+ virtual void connectToHost();
+ bool isConnected() const;
+ void setConnected();
+
+ bool isFinished() const;
+ virtual void setFinished();
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotProperties(QDBusPendingCallWatcher *watcher);
+
+ TELEPATHY_QT4_NO_EXPORT void changeState();
+ TELEPATHY_QT4_NO_EXPORT void onStateChanged(uint state, uint stateReason);
+ TELEPATHY_QT4_NO_EXPORT void onInitialOffsetDefined(qulonglong initialOffset);
+ TELEPATHY_QT4_NO_EXPORT void onTransferredBytesChanged(qulonglong count);
+
+protected Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onUriDefined(const QString &uri);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/filter.dox b/qt4/TelepathyQt4/filter.dox
new file mode 100644
index 000000000..d69aa87e3
--- /dev/null
+++ b/qt4/TelepathyQt4/filter.dox
@@ -0,0 +1,30 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::Filter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/filter.h <TelepathyQt4/Filter>
+ *
+ * \brief The Filter class provides a base class to be used by specialized
+ * filters such as GenericCapabilityFilter, GenericPropertyFilter, etc.
+ */
diff --git a/qt4/TelepathyQt4/filter.h b/qt4/TelepathyQt4/filter.h
new file mode 100644
index 000000000..e81eafc78
--- /dev/null
+++ b/qt4/TelepathyQt4/filter.h
@@ -0,0 +1,63 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+template <class T>
+class Filter : public RefCounted
+{
+ Q_DISABLE_COPY(Filter)
+
+public:
+ virtual ~Filter() {}
+
+ virtual bool isValid() const { return false; }
+
+ virtual bool matches(const SharedPtr<T> &t) const
+ {
+ Q_UNUSED(t);
+
+ return false;
+ }
+
+protected:
+ Filter() {}
+
+private:
+ struct Private;
+ Private *mPriv; // Just a placeholder really
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/fixed-feature-factory.cpp b/qt4/TelepathyQt4/fixed-feature-factory.cpp
new file mode 100644
index 000000000..fc4dc6757
--- /dev/null
+++ b/qt4/TelepathyQt4/fixed-feature-factory.cpp
@@ -0,0 +1,116 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/FixedFeatureFactory>
+
+#include "TelepathyQt4/_gen/fixed-feature-factory.moc.hpp"
+
+#include <TelepathyQt4/Feature>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT FixedFeatureFactory::Private
+{
+ Features features;
+};
+
+/**
+ * \class FixedFeatureFactory
+ * \ingroup utils
+ * \headerfile TelepathyQt4/fixed-feature-factory.h <TelepathyQt4/FixedFeatureFactory>
+ *
+ * \brief The FixedFeatureFactory class is a base class for all D-Bus proxy
+ * factories which want the same set of features for all constructed proxies.
+ */
+
+/**
+ * Class constructor.
+ *
+ * The intention for storing the bus here is that it generally doesn't make sense to construct
+ * proxies for multiple buses in the same context. Allowing that would lead to more complex keying
+ * needs in the cache, as well.
+ *
+ * \param bus The D-Bus bus connection for the objects constructed using this factory.
+ */
+FixedFeatureFactory::FixedFeatureFactory(const QDBusConnection &bus)
+ : DBusProxyFactory(bus), mPriv(new Private)
+{
+}
+
+/**
+ * Class destructor.
+ */
+FixedFeatureFactory::~FixedFeatureFactory()
+{
+ delete mPriv;
+}
+
+/**
+ * Gets the features this factory will make ready on constructed proxies.
+ *
+ * \return The set of features.
+ */
+Features FixedFeatureFactory::features() const
+{
+ return mPriv->features;
+}
+
+/**
+ * Adds a single feature this factory will make ready on further constructed proxies.
+ *
+ * No feature removal is provided, to guard against uncooperative modules removing features other
+ * modules have set and depend on.
+ *
+ * \param feature The feature to add.
+ */
+void FixedFeatureFactory::addFeature(const Feature &feature)
+{
+ addFeatures(Features(feature));
+}
+
+/**
+ * Adds a set of features this factory will make ready on further constructed proxies.
+ *
+ * No feature removal is provided, to guard against uncooperative modules removing features other
+ * modules have set and depend on.
+ *
+ * \param features The features to add.
+ */
+void FixedFeatureFactory::addFeatures(const Features &features)
+{
+ mPriv->features.unite(features);
+}
+
+/**
+ * Fixed implementation of the per-proxy feature getter.
+ *
+ * \return features(), irrespective of the actual \a proxy.
+ */
+Features FixedFeatureFactory::featuresFor(const DBusProxyPtr &proxy) const
+{
+ Q_UNUSED(proxy);
+
+ return features();
+}
+
+}
diff --git a/qt4/TelepathyQt4/fixed-feature-factory.h b/qt4/TelepathyQt4/fixed-feature-factory.h
new file mode 100644
index 000000000..00dd027b7
--- /dev/null
+++ b/qt4/TelepathyQt4/fixed-feature-factory.h
@@ -0,0 +1,69 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_fixed_feature_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_fixed_feature_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/SharedPtr>
+
+#include <TelepathyQt4/DBusProxyFactory>
+
+class QDBusConnection;
+
+namespace Tp
+{
+
+class Feature;
+class Features;
+
+class TELEPATHY_QT4_EXPORT FixedFeatureFactory : public DBusProxyFactory
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FixedFeatureFactory)
+
+public:
+ virtual ~FixedFeatureFactory();
+
+ Features features() const;
+
+ void addFeature(const Feature &feature);
+ void addFeatures(const Features &features);
+
+protected:
+ FixedFeatureFactory(const QDBusConnection &bus);
+
+ virtual Features featuresFor(const DBusProxyPtr &proxy) const;
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/future-channel-dispatcher.xml b/qt4/TelepathyQt4/future-channel-dispatcher.xml
new file mode 100644
index 000000000..0e7f67c57
--- /dev/null
+++ b/qt4/TelepathyQt4/future-channel-dispatcher.xml
@@ -0,0 +1,20 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel Dispatcher extensions from the future</tp:title>
+
+<xi:include href="../spec/Channel_Dispatcher_Interface_Messages.xml"/>
+
+<tp:generic-types>
+ <tp:external-type name="Message_Part" type="a{sv}" from="Telepathy specification"/>
+ <tp:mapping name="Message_Part" array-name="Message_Part_List" array-depth="2">
+ <tp:member name="Key" type="s"/>
+ <tp:member name="Value" type="v"/>
+ </tp:mapping>
+</tp:generic-types>
+
+<xi:include href="../spec/generic-types.xml"/>
+<xi:include href="../spec/errors.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/future-channel.xml b/qt4/TelepathyQt4/future-channel.xml
new file mode 100644
index 000000000..6cf1e4948
--- /dev/null
+++ b/qt4/TelepathyQt4/future-channel.xml
@@ -0,0 +1,13 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel extensions from the future</tp:title>
+
+<xi:include href="../spec/Channel_Interface_Mergeable_Conference.xml"/>
+<xi:include href="../spec/Channel_Interface_Splittable.xml"/>
+
+<xi:include href="../spec/generic-types.xml"/>
+<xi:include href="../spec/errors.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/future-interfaces.xml b/qt4/TelepathyQt4/future-interfaces.xml
new file mode 100644
index 000000000..8cc5d753a
--- /dev/null
+++ b/qt4/TelepathyQt4/future-interfaces.xml
@@ -0,0 +1,14 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Extensions from the future</tp:title>
+
+<xi:include href="future-channel.xml"/>
+<xi:include href="future-channel-dispatcher.xml"/>
+<xi:include href="future-misc.xml"/>
+
+<xi:include href="../spec/generic-types.xml"/>
+<xi:include href="../spec/errors.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/future-internal.h b/qt4/TelepathyQt4/future-internal.h
new file mode 100644
index 000000000..3636ff2df
--- /dev/null
+++ b/qt4/TelepathyQt4/future-internal.h
@@ -0,0 +1,36 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_future_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_future_internal_h_HEADER_GUARD_
+
+#include "TelepathyQt4/_gen/future-constants.h"
+#include "TelepathyQt4/_gen/future-types.h"
+
+#include "TelepathyQt4/Channel"
+#include "TelepathyQt4/ChannelDispatcher"
+
+#include "TelepathyQt4/_gen/future-channel.h"
+#include "TelepathyQt4/_gen/future-channel-dispatcher.h"
+#include "TelepathyQt4/_gen/future-misc.h"
+
+#endif
diff --git a/qt4/TelepathyQt4/future-misc.xml b/qt4/TelepathyQt4/future-misc.xml
new file mode 100644
index 000000000..b756661e5
--- /dev/null
+++ b/qt4/TelepathyQt4/future-misc.xml
@@ -0,0 +1,7 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Miscellaneous extensions from the future</tp:title>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/future.cpp b/qt4/TelepathyQt4/future.cpp
new file mode 100644
index 000000000..5bbcc62bc
--- /dev/null
+++ b/qt4/TelepathyQt4/future.cpp
@@ -0,0 +1,32 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "TelepathyQt4/future-internal.h"
+
+#include "TelepathyQt4/_gen/future-channel-body.hpp"
+#include "TelepathyQt4/_gen/future-channel.moc.hpp"
+
+#include "TelepathyQt4/_gen/future-channel-dispatcher-body.hpp"
+#include "TelepathyQt4/_gen/future-channel-dispatcher.moc.hpp"
+
+#include "TelepathyQt4/_gen/future-misc-body.hpp"
+#include "TelepathyQt4/_gen/future-misc.moc.hpp"
diff --git a/qt4/TelepathyQt4/generic-capability-filter.dox b/qt4/TelepathyQt4/generic-capability-filter.dox
new file mode 100644
index 000000000..23c13b35f
--- /dev/null
+++ b/qt4/TelepathyQt4/generic-capability-filter.dox
@@ -0,0 +1,36 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::GenericCapabilityFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/generic-capability-filter.h <TelepathyQt4/GenericCapabilityFilter>
+ *
+ * \brief The GenericCapabilityFilter class provides a generic filter object to
+ * be used to filter objects by capabilities.
+ *
+ * The objects used in conjunction with this filter must implement a method
+ * called capabilities() returning a CapabilitiesBase (or a subclass of it)
+ * instance.
+ * Specialized classes such as AccountCapabilityFilter are also provided and
+ * should be used where appropriate.
+ */
diff --git a/qt4/TelepathyQt4/generic-capability-filter.h b/qt4/TelepathyQt4/generic-capability-filter.h
new file mode 100644
index 000000000..ed22b3be7
--- /dev/null
+++ b/qt4/TelepathyQt4/generic-capability-filter.h
@@ -0,0 +1,114 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_generic_capability_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_generic_capability_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+template <class T>
+class GenericCapabilityFilter : public Filter<T>
+{
+public:
+ static SharedPtr<GenericCapabilityFilter<T> > create(
+ const RequestableChannelClassSpecList &rccSpecs = RequestableChannelClassSpecList())
+ {
+ return SharedPtr<GenericCapabilityFilter<T> >(new GenericCapabilityFilter<T>(
+ rccSpecs));
+ }
+
+ inline virtual ~GenericCapabilityFilter() { }
+
+ inline virtual bool isValid() const { return true; }
+
+ inline virtual bool matches(const SharedPtr<T> &t) const
+ {
+ bool supportedRcc;
+ RequestableChannelClassSpecList objectRccSpecs = t->capabilities().allClassSpecs();
+ Q_FOREACH (const RequestableChannelClassSpec &filterRccSpec, mFilter) {
+ supportedRcc = false;
+
+ Q_FOREACH (const RequestableChannelClassSpec &objectRccSpec, objectRccSpecs) {
+ /* check if fixed properties match */
+ if (filterRccSpec.fixedProperties() == objectRccSpec.fixedProperties()) {
+ supportedRcc = true;
+
+ /* check if all allowed properties in the filter RCC
+ * are in the object RCC allowed properties */
+ Q_FOREACH (const QString &value, filterRccSpec.allowedProperties()) {
+ if (!objectRccSpec.allowsProperty(value)) {
+ /* one of the properties in the filter RCC
+ * allowed properties is not in the object RCC
+ * allowed properties */
+ supportedRcc = false;
+ break;
+ }
+ }
+
+ /* this RCC is supported, no need to check anymore */
+ if (supportedRcc) {
+ break;
+ }
+ }
+ }
+
+ /* one of the filter RCC is not supported, this object
+ * won't match filter */
+ if (!supportedRcc) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ inline RequestableChannelClassSpecList filter() const { return mFilter; }
+
+ inline void addRequestableChannelClassSubset(const RequestableChannelClassSpec &rccSpec)
+ {
+ mFilter.append(rccSpec.bareClass());
+ }
+
+ inline void setRequestableChannelClassesSubset(const RequestableChannelClassSpecList &rccSpecs)
+ {
+ mFilter = rccSpecs.bareClasses();
+ }
+
+private:
+ GenericCapabilityFilter(const RequestableChannelClassSpecList &rccSpecs)
+ : Filter<T>(), mFilter(rccSpecs) { }
+
+ RequestableChannelClassSpecList mFilter;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/generic-property-filter.dox b/qt4/TelepathyQt4/generic-property-filter.dox
new file mode 100644
index 000000000..e25b17b05
--- /dev/null
+++ b/qt4/TelepathyQt4/generic-property-filter.dox
@@ -0,0 +1,33 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::GenericPropertyFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/generic-property-filter.h <TelepathyQt4/GenericPropertyFilter>
+ *
+ * \brief The GenericPropertyFilter class provides a generic filter object
+ * to be used to filter objects by properties.
+ *
+ * Specialized classes such as AccountPropertyFilter are also provided and
+ * should be used where appropriate.
+ */
diff --git a/qt4/TelepathyQt4/generic-property-filter.h b/qt4/TelepathyQt4/generic-property-filter.h
new file mode 100644
index 000000000..f25e54059
--- /dev/null
+++ b/qt4/TelepathyQt4/generic-property-filter.h
@@ -0,0 +1,77 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_generic_property_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_generic_property_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+template <class T>
+class GenericPropertyFilter : public Filter<T>
+{
+public:
+ inline virtual ~GenericPropertyFilter() { }
+
+ inline virtual bool isValid() const { return true; }
+
+ inline virtual bool matches(const SharedPtr<T> &t) const
+ {
+ for (QVariantMap::const_iterator i = mFilter.constBegin();
+ i != mFilter.constEnd(); ++i) {
+ QString propertyName = i.key();
+ QVariant propertyValue = i.value();
+
+ if (t->property(propertyName.toLatin1().constData()) != propertyValue) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ inline QVariantMap filter() const { return mFilter; }
+
+ inline void addProperty(const QString &propertyName, const QVariant &propertyValue)
+ {
+ mFilter.insert(propertyName, propertyValue);
+ }
+
+ inline void setProperties(const QVariantMap &filter) { mFilter = filter; }
+
+protected:
+ inline GenericPropertyFilter() : Filter<T>() { }
+
+private:
+ QVariantMap mFilter;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/global.h b/qt4/TelepathyQt4/global.h
new file mode 100644
index 000000000..69de68b42
--- /dev/null
+++ b/qt4/TelepathyQt4/global.h
@@ -0,0 +1,103 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_global_h_HEADER_GUARD_
+#define _TelepathyQt4_global_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <QtGlobal>
+
+#ifdef BUILDING_TELEPATHY_QT4
+# define TELEPATHY_QT4_EXPORT Q_DECL_EXPORT
+#else
+# define TELEPATHY_QT4_EXPORT Q_DECL_IMPORT
+#endif
+
+#if !defined(Q_OS_WIN) && defined(QT_VISIBILITY_AVAILABLE)
+# define TELEPATHY_QT4_NO_EXPORT __attribute__((visibility("hidden")))
+#endif
+
+#ifndef TELEPATHY_QT4_NO_EXPORT
+# define TELEPATHY_QT4_NO_EXPORT
+#endif
+
+/**
+ * @def TELEPATHY_QT4_DEPRECATED
+ * @ingroup TELEPATHY_QT4_MACROS
+ *
+ * The TELEPATHY_QT4_DEPRECATED macro can be used to trigger compile-time
+ * warnings with newer compilers when deprecated functions are used.
+ *
+ * For non-inline functions, the macro gets inserted at front of the
+ * function declaration, right before the return type:
+ *
+ * \code
+ * TELEPATHY_QT4_DEPRECATED void deprecatedFunctionA();
+ * TELEPATHY_QT4_DEPRECATED int deprecatedFunctionB() const;
+ * \endcode
+ *
+ * For functions which are implemented inline,
+ * the TELEPATHY_QT4_DEPRECATED macro is inserted at the front, right before the
+ * return type, but after "static", "inline" or "virtual":
+ *
+ * \code
+ * TELEPATHY_QT4_DEPRECATED void deprecatedInlineFunctionA() { .. }
+ * virtual TELEPATHY_QT4_DEPRECATED int deprecatedInlineFunctionB() { .. }
+ * static TELEPATHY_QT4_DEPRECATED bool deprecatedInlineFunctionC() { .. }
+ * inline TELEPATHY_QT4_DEPRECATED bool deprecatedInlineFunctionD() { .. }
+ * \endcode
+ *
+ * You can also mark whole structs or classes as deprecated, by inserting the
+ * TELEPATHY_QT4_DEPRECATED macro after the struct/class keyword, but before the
+ * name of the struct/class:
+ *
+ * \code
+ * class TELEPATHY_QT4_DEPRECATED DeprecatedClass { };
+ * struct TELEPATHY_QT4_DEPRECATED DeprecatedStruct { };
+ * \endcode
+ *
+ * \note
+ * It does not make much sense to use the TELEPATHY_QT4_DEPRECATED keyword for a
+ * Qt signal; this is because usually get called by the class which they belong
+ * to, and one would assume that a class author does not use deprecated methods
+ * of his own class. The only exception to this are signals which are connected
+ * to other signals; they get invoked from moc-generated code. In any case,
+ * printing a warning message in either case is not useful.
+ * For slots, it can make sense (since slots can be invoked directly) but be
+ * aware that if the slots get triggered by a signal, they will get called from
+ * moc code as well and thus the warnings are useless.
+ *
+ * \note
+ * TELEPATHY_QT4_DEPRECATED cannot be used for constructors.
+ */
+#ifndef TELEPATHY_QT4_DEPRECATED
+# ifdef TELEPATHY_QT4_DEPRECATED_WARNINGS
+# define TELEPATHY_QT4_DEPRECATED Q_DECL_DEPRECATED
+# else
+# define TELEPATHY_QT4_DEPRECATED
+# endif
+#endif
+
+#endif
diff --git a/qt4/TelepathyQt4/groups.dox b/qt4/TelepathyQt4/groups.dox
new file mode 100644
index 000000000..e56eb1e11
--- /dev/null
+++ b/qt4/TelepathyQt4/groups.dox
@@ -0,0 +1,115 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \defgroup clientsideproxies Client-side proxies
+ *
+ * Proxy objects representing remote service objects accessed via D-Bus.
+ *
+ * In addition to providing direct access to methods, signals and properties
+ * exported by the remote objects, some of these proxies offer features like
+ * automatic inspection of remote object capabilities, property tracking,
+ * backwards compatibility helpers for older services and other utilities.
+ */
+
+/**
+ * \defgroup clientaccount Account proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy account objects and their
+ * optional interfaces.
+ */
+
+/**
+ * \defgroup clientam Account manager proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy account manager objects and their
+ * optional interfaces.
+ */
+
+/**
+ * \defgroup clientchannel Channel proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy Channel objects and their
+ * optional interfaces.
+ */
+
+/**
+ * \defgroup clientchanneldispatcher ChannelDispatcher proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy ChannelDispatcher objects and
+ * their optional interfaces.
+ */
+
+/**
+ * \defgroup clientchanneldispatchoperation ChannelDispatchOperation proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy ChannelDispatchOperation objects
+ * and their optional interfaces.
+ */
+
+/**
+ * \defgroup clientchannelrequest ChannelRequest proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy ChannelRequest objects and their
+ * optional interfaces.
+ */
+
+/**
+ * \defgroup clientclient Client proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy Client objects (approvers,
+ * handlers and observers) and their optional interfaces.
+ */
+
+/**
+ * \defgroup clientcm Connection manager proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy ConnectionManager objects and
+ * their optional interfaces.
+ */
+
+/**
+ * \defgroup clientconn Connection proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy Connection objects and their
+ * optional interfaces.
+ */
+
+/**
+ * \defgroup wrappers Wrapper classes
+ *
+ * Wrapper classes representing a Telepathy type.
+ */
+
+/**
+ * \defgroup utils Utililty classes
+ *
+ * Utility classes.
+ */
diff --git a/qt4/TelepathyQt4/handled-channel-notifier.cpp b/qt4/TelepathyQt4/handled-channel-notifier.cpp
new file mode 100644
index 000000000..1cf9b31ba
--- /dev/null
+++ b/qt4/TelepathyQt4/handled-channel-notifier.cpp
@@ -0,0 +1,103 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/HandledChannelNotifier>
+
+#include "TelepathyQt4/_gen/handled-channel-notifier.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/request-temporary-handler-internal.h"
+
+#include <TelepathyQt4/ChannelRequestHints>
+#include <TelepathyQt4/ClientRegistrar>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT HandledChannelNotifier::Private
+{
+ Private(const ClientRegistrarPtr &cr,
+ const SharedPtr<RequestTemporaryHandler> &handler)
+ : cr(cr),
+ handler(handler),
+ channel(handler->channel())
+ {
+ }
+
+ ClientRegistrarPtr cr;
+ SharedPtr<RequestTemporaryHandler> handler;
+ ChannelPtr channel; // needed to keep channel alive, since RTH maintains only a weak ref
+};
+
+/**
+ * \class HandledChannelNotifier
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/handled-channel-notifier.h <TelepathyQt4/HandledChannelNotifier>
+ *
+ * \brief The HandledChannelNotifier class can be used to keep track of
+ * channel() being re-requested.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is trough PendingChannel.
+ */
+
+HandledChannelNotifier::HandledChannelNotifier(const ClientRegistrarPtr &cr,
+ const SharedPtr<RequestTemporaryHandler> &handler)
+ : mPriv(new Private(cr, handler))
+{
+ connect(mPriv->channel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onChannelInvalidated()));
+ connect(handler.data(),
+ SIGNAL(channelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onChannelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)));
+}
+
+HandledChannelNotifier::~HandledChannelNotifier()
+{
+ delete mPriv;
+}
+
+ChannelPtr HandledChannelNotifier::channel() const
+{
+ return mPriv->channel;
+}
+
+void HandledChannelNotifier::onChannelReceived(const Tp::ChannelPtr &channel,
+ const QDateTime &userActionTime, const Tp::ChannelRequestHints &requestHints)
+{
+ emit handledAgain(userActionTime, requestHints);
+}
+
+void HandledChannelNotifier::onChannelInvalidated()
+{
+ deleteLater();
+}
+
+void HandledChannelNotifier::connectNotify(const char *signalName)
+{
+ if (qstrcmp(signalName, SIGNAL(handledAgain(QDateTime,Tp::ChannelRequestHints))) == 0) {
+ mPriv->handler->setQueueChannelReceived(false);
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/handled-channel-notifier.h b/qt4/TelepathyQt4/handled-channel-notifier.h
new file mode 100644
index 000000000..223c29c77
--- /dev/null
+++ b/qt4/TelepathyQt4/handled-channel-notifier.h
@@ -0,0 +1,75 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_handled_channel_notifier_h_HEADER_GUARD_
+#define _TelepathyQt4_handled_channel_notifier_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Types>
+
+#include <QObject>
+
+namespace Tp
+{
+
+class ChannelRequestHints;
+class RequestTemporaryHandler;
+
+class TELEPATHY_QT4_EXPORT HandledChannelNotifier : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(HandledChannelNotifier)
+
+public:
+ ~HandledChannelNotifier();
+
+ ChannelPtr channel() const;
+
+Q_SIGNALS:
+ void handledAgain(const QDateTime &userActionTime, const Tp::ChannelRequestHints &requestHints);
+
+protected:
+ void connectNotify(const char *);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onChannelReceived(const Tp::ChannelPtr &channel,
+ const QDateTime &userActionTime, const Tp::ChannelRequestHints &requestHints);
+ TELEPATHY_QT4_NO_EXPORT void onChannelInvalidated();
+
+private:
+ friend class PendingChannel;
+
+ HandledChannelNotifier(const ClientRegistrarPtr &cr,
+ const SharedPtr<RequestTemporaryHandler> &handler);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/incoming-file-transfer-channel.cpp b/qt4/TelepathyQt4/incoming-file-transfer-channel.cpp
new file mode 100644
index 000000000..a9bdad520
--- /dev/null
+++ b/qt4/TelepathyQt4/incoming-file-transfer-channel.cpp
@@ -0,0 +1,390 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/IncomingFileTransferChannel>
+
+#include "TelepathyQt4/_gen/incoming-file-transfer-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/types-internal.h>
+
+#include <QIODevice>
+#include <QTcpSocket>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT IncomingFileTransferChannel::Private
+{
+ Private(IncomingFileTransferChannel *parent);
+ ~Private();
+
+ // Public object
+ IncomingFileTransferChannel *parent;
+
+ Client::ChannelTypeFileTransferInterface *fileTransferInterface;
+
+ QIODevice *output;
+ QTcpSocket *socket;
+ SocketAddressIPv4 addr;
+
+ qulonglong requestedOffset;
+ qint64 pos;
+};
+
+IncomingFileTransferChannel::Private::Private(IncomingFileTransferChannel *parent)
+ : parent(parent),
+ fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()),
+ output(0),
+ socket(0),
+ requestedOffset(0),
+ pos(0)
+{
+ parent->connect(fileTransferInterface,
+ SIGNAL(URIDefined(QString)),
+ SLOT(onUriDefined(QString)));
+ parent->connect(fileTransferInterface,
+ SIGNAL(URIDefined(QString)),
+ SIGNAL(uriDefined(QString)));
+}
+
+IncomingFileTransferChannel::Private::~Private()
+{
+}
+
+/**
+ * \class IncomingFileTransferChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/incoming-file-transfer-channel.h <TelepathyQt4/IncomingFileTransferChannel>
+ *
+ * \brief The IncomingFileTransferChannel class represents a Telepathy channel
+ * of type FileTransfer for incoming file transfers.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * IncomingFileTransferChannel object usable.
+ *
+ * This is currently the same as FileTransferChannel::FeatureCore, but may change to include more.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature IncomingFileTransferChannel::FeatureCore =
+ Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); // FT::FeatureCore
+
+/**
+ * Create a new IncomingFileTransferChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A IncomingFileTransferChannelPtr object pointing to the newly created
+ * IncomingFileTransfer object.
+ */
+IncomingFileTransferChannelPtr IncomingFileTransferChannel::create(
+ const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties)
+{
+ return IncomingFileTransferChannelPtr(new IncomingFileTransferChannel(
+ connection, objectPath, immutableProperties,
+ IncomingFileTransferChannel::FeatureCore));
+}
+
+/**
+ * Construct a new IncomingFileTransferChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on IncomingFileTransferChannel::FeatureCore.
+ */
+IncomingFileTransferChannel::IncomingFileTransferChannel(
+ const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : FileTransferChannel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+IncomingFileTransferChannel::~IncomingFileTransferChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Set the URI where the file will be saved.
+ *
+ * This property may be set by the channel handler before calling AcceptFile to inform observers
+ * where the incoming file will be saved. When the URI property is set, the signal
+ * uriDefined() is emitted.
+ *
+ * This method requires IncomingFileTransferChannel::FeatureCore to be ready.
+ *
+ * \param uri The URI where the file will be saved.
+ * \return A PendingOperation object which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa FileTransferChannel::uri(), uriDefined()
+ */
+PendingOperation *IncomingFileTransferChannel::setUri(const QString& uri)
+{
+ if (!isReady(FileTransferChannel::FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling setUri";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ IncomingFileTransferChannelPtr(this));
+ }
+
+ if (state() != FileTransferStatePending) {
+ warning() << "setUri must be called before calling acceptFile";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Cannot set URI after calling acceptFile"),
+ IncomingFileTransferChannelPtr(this));
+ }
+
+ return mPriv->fileTransferInterface->setPropertyURI(uri);
+}
+
+/**
+ * Accept a file transfer that's in the #FileTransferStatePending state().
+ *
+ * The state will change to #FileTransferStateOpen as soon as the transfer
+ * starts.
+ * The given output device should not be closed/destroyed until the state()
+ * changes to #FileTransferStateCompleted or #FileTransferStateCancelled.
+ *
+ * Only the primary handler of a file transfer channel may call this method.
+ *
+ * This method requires IncomingFileTransferChannel::FeatureCore to be ready.
+ *
+ * \param offset The desired offset in bytes where the file transfer should
+ * start. The offset is taken from the beginning of the file.
+ * Specifying an offset of zero will start the transfer from the
+ * beginning of the file. The offset that is actually given in the
+ * initialOffset() method can differ from this argument where the
+ * requested offset is not supported. (For example, some
+ * protocols do not support offsets at all so the initialOffset()
+ * will always be 0.).
+ * \param output A QIODevice object where the data will be written to. The
+ * device should be ready to use when the state() changes to
+ * #FileTransferStateCompleted.
+ * If the transfer is cancelled, state() becomes
+ * #FileTransferStateCancelled, the data in \a output should be
+ * ignored
+ * \return A PendingOperation object which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa FileTransferChannel::stateChanged(), FileTransferChannel::state(),
+ * FileTransferChannel::stateReason(), FileTransferChannel::initialOffset()
+ */
+PendingOperation *IncomingFileTransferChannel::acceptFile(qulonglong offset,
+ QIODevice *output)
+{
+ if (!isReady(FileTransferChannel::FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling acceptFile";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ IncomingFileTransferChannelPtr(this));
+ }
+
+ // let's fail here direclty as we may only have one device to handle
+ if (mPriv->output) {
+ warning() << "File transfer can only be started once in the same "
+ "channel";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("File transfer can only be started once in the same channel"),
+ IncomingFileTransferChannelPtr(this));
+ }
+
+ if ((!output->isOpen() && !output->open(QIODevice::WriteOnly)) &&
+ (!output->isWritable())) {
+ warning() << "Unable to open IO device for writing";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED),
+ QLatin1String("Unable to open IO device for writing"),
+ IncomingFileTransferChannelPtr(this));
+ }
+
+ mPriv->output = output;
+
+ mPriv->requestedOffset = offset;
+
+ PendingVariant *pv = new PendingVariant(
+ mPriv->fileTransferInterface->AcceptFile(SocketAddressTypeIPv4,
+ SocketAccessControlLocalhost, QDBusVariant(QVariant(QString())),
+ offset),
+ IncomingFileTransferChannelPtr(this));
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAcceptFileFinished(Tp::PendingOperation*)));
+ return pv;
+}
+
+void IncomingFileTransferChannel::onAcceptFileFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Error accepting file transfer " <<
+ op->errorName() << ":" << op->errorMessage();
+ invalidate(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ PendingVariant *pv = qobject_cast<PendingVariant *>(op);
+ mPriv->addr = qdbus_cast<SocketAddressIPv4>(pv->result());
+ debug().nospace() << "Got address " << mPriv->addr.address <<
+ ":" << mPriv->addr.port;
+
+ if (state() == FileTransferStateOpen) {
+ // now we have the address and we are already opened,
+ // connect to host
+ connectToHost();
+ }
+}
+
+void IncomingFileTransferChannel::connectToHost()
+{
+ if (isConnected() || mPriv->addr.address.isNull()) {
+ return;
+ }
+
+ // we already have initialOffsetDefined, called before State became Open, so
+ // let's make sure everything is ok.
+ if (initialOffset() > mPriv->requestedOffset) {
+ // either the CM or the sender is doing something really wrong here,
+ // cancel the transfer.
+ warning() << "InitialOffset bigger than requested offset, "
+ "cancelling the transfer";
+ cancel();
+ invalidate(QLatin1String(TELEPATHY_QT4_ERROR_INCONSISTENT),
+ QLatin1String("Initial offset bigger than requested offset"));
+ return;
+ }
+
+ mPriv->pos = initialOffset();
+
+ mPriv->socket = new QTcpSocket(this);
+
+ connect(mPriv->socket, SIGNAL(connected()),
+ SLOT(onSocketConnected()));
+ connect(mPriv->socket, SIGNAL(disconnected()),
+ SLOT(onSocketDisconnected()));
+ connect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ SLOT(onSocketError(QAbstractSocket::SocketError)));
+ connect(mPriv->socket, SIGNAL(readyRead()),
+ SLOT(doTransfer()));
+
+ debug().nospace() << "Connecting to host " <<
+ mPriv->addr.address << ":" << mPriv->addr.port << "...";
+ mPriv->socket->connectToHost(mPriv->addr.address, mPriv->addr.port);
+}
+
+void IncomingFileTransferChannel::onSocketConnected()
+{
+ debug() << "Connected to host";
+ setConnected();
+
+ doTransfer();
+}
+
+void IncomingFileTransferChannel::onSocketDisconnected()
+{
+ debug() << "Disconnected from host";
+ setFinished();
+}
+
+void IncomingFileTransferChannel::onSocketError(QAbstractSocket::SocketError error)
+{
+ setFinished();
+}
+
+void IncomingFileTransferChannel::doTransfer()
+{
+ QByteArray data;
+ while (mPriv->socket->bytesAvailable()) {
+ data = mPriv->socket->readAll();
+
+ // skip until we reach requetedOffset and start writing from there
+ if ((qulonglong) mPriv->pos < mPriv->requestedOffset) {
+ if ((qulonglong) data.length() <= (mPriv->requestedOffset - mPriv->pos)) {
+ break;
+ }
+ data = data.mid(mPriv->requestedOffset - mPriv->pos);
+ }
+
+ mPriv->output->write(data); // never fails
+ }
+
+ mPriv->pos += data.length();
+}
+
+void IncomingFileTransferChannel::setFinished()
+{
+ if (isFinished()) {
+ // it shouldn't happen but let's make sure
+ return;
+ }
+
+ if (mPriv->socket) {
+ disconnect(mPriv->socket, SIGNAL(connected()),
+ this, SLOT(onSocketConnected()));
+ disconnect(mPriv->socket, SIGNAL(disconnected()),
+ this, SLOT(onSocketDisconnected()));
+ disconnect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(onSocketError(QAbstractSocket::SocketError)));
+ disconnect(mPriv->socket, SIGNAL(readyRead()),
+ this, SLOT(doTransfer()));
+ mPriv->socket->close();
+ }
+
+ if (mPriv->output) {
+ mPriv->output->close();
+ }
+
+ FileTransferChannel::setFinished();
+}
+
+/**
+ * \fn void IncomingFileTransferChannel::uriDefined(const QString &uri)
+ *
+ * Emitted when the value of uri() changes.
+ *
+ * \param uri The new URI of this file transfer channel.
+ * \sa FileTransferChannel::uri(), setUri()
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/incoming-file-transfer-channel.h b/qt4/TelepathyQt4/incoming-file-transfer-channel.h
new file mode 100644
index 000000000..cd53b5f08
--- /dev/null
+++ b/qt4/TelepathyQt4/incoming-file-transfer-channel.h
@@ -0,0 +1,81 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_incoming_file_transfer_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_incoming_file_transfer_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/FileTransferChannel>
+
+#include <QAbstractSocket>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT IncomingFileTransferChannel : public FileTransferChannel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(IncomingFileTransferChannel)
+
+public:
+ static const Feature FeatureCore;
+
+ static IncomingFileTransferChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~IncomingFileTransferChannel();
+
+ PendingOperation *setUri(const QString& uri);
+ PendingOperation *acceptFile(qulonglong offset, QIODevice *output);
+
+Q_SIGNALS:
+ void uriDefined(const QString &uri);
+
+protected:
+ IncomingFileTransferChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = IncomingFileTransferChannel::FeatureCore);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onAcceptFileFinished(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onSocketConnected();
+ TELEPATHY_QT4_NO_EXPORT void onSocketDisconnected();
+ TELEPATHY_QT4_NO_EXPORT void onSocketError(QAbstractSocket::SocketError error);
+ TELEPATHY_QT4_NO_EXPORT void doTransfer();
+
+private:
+ TELEPATHY_QT4_NO_EXPORT void connectToHost();
+ TELEPATHY_QT4_NO_EXPORT void setFinished();
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/incoming-stream-tube-channel.cpp b/qt4/TelepathyQt4/incoming-stream-tube-channel.cpp
new file mode 100644
index 000000000..610c12706
--- /dev/null
+++ b/qt4/TelepathyQt4/incoming-stream-tube-channel.cpp
@@ -0,0 +1,435 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+
+#include "TelepathyQt4/_gen/incoming-stream-tube-channel.moc.hpp"
+
+#include "TelepathyQt4/types-internal.h"
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingStreamTubeConnection>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/Types>
+
+#include <QHostAddress>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT IncomingStreamTubeChannel::Private
+{
+ Private(IncomingStreamTubeChannel *parent);
+
+ // Public object
+ IncomingStreamTubeChannel *parent;
+ static bool initRandom;
+};
+
+bool IncomingStreamTubeChannel::Private::initRandom = true;
+
+IncomingStreamTubeChannel::Private::Private(IncomingStreamTubeChannel *parent)
+ : parent(parent)
+{
+}
+
+/**
+ * \class IncomingStreamTubeChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/incoming-stream-tube-channel.h <TelepathyQt4/IncomingStreamTubeChannel>
+ *
+ * \brief The IncomingStreamTubeChannel class represents an incoming Telepathy channel
+ * of type StreamTube.
+ *
+ * In particular, this class is meant to be used as a comfortable way for
+ * accepting incoming stream tubes. Tubes can be accepted as TCP and/or Unix sockets with various
+ * access control methods depending on what the service supports using the various overloads of
+ * acceptTubeAsTcpSocket() and acceptTubeAsUnixSocket().
+ *
+ * Once a tube is successfully accepted and open (the PendingStreamTubeConnection returned from the
+ * accepting methods has finished), the application can connect to the socket the address of which
+ * can be retrieved from PendingStreamTubeConnection::ipAddress() and/or
+ * PendingStreamTubeConnection::localAddress() depending on which accepting method was used.
+ * Connecting to this socket will open a tunneled connection to the service listening at the
+ * offering end of the tube.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * IncomingStreamTubeChannel object usable.
+ *
+ * This is currently the same as StreamTubeChannel::FeatureCore, but may change to include more.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature IncomingStreamTubeChannel::FeatureCore =
+ Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); // ST::FeatureCore
+
+/**
+ * Create a new IncomingStreamTubeChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A IncomingStreamTubeChannelPtr object pointing to the newly created
+ * IncomingStreamTubeChannel object.
+ */
+IncomingStreamTubeChannelPtr IncomingStreamTubeChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return IncomingStreamTubeChannelPtr(new IncomingStreamTubeChannel(connection, objectPath,
+ immutableProperties, IncomingStreamTubeChannel::FeatureCore));
+}
+
+/**
+ * Construct a new IncomingStreamTubeChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on IncomingStreamTubeChannel::FeatureCore.
+ */
+IncomingStreamTubeChannel::IncomingStreamTubeChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : StreamTubeChannel(connection, objectPath,
+ immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+IncomingStreamTubeChannel::~IncomingStreamTubeChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Accept an incoming stream tube as a TCP socket.
+ *
+ * This method accepts an incoming connection request for a stream tube. It can be called
+ * only if the tube is in the #TubeStateLocalPending state.
+ *
+ * The connection manager will open a TCP socket for the application to connect to. The address of
+ * the socket will be returned in PendingStreamTubeConnection::ipAddress() once the operation has
+ * finished successfully.
+ *
+ * This overload lets you specify an allowed address/port combination for connecting to the CM
+ * socket. Connections with other source addresses won't be accepted. The accessors
+ * supportsIPv4SocketsWithSpecifiedAddress() and supportsIPv6SocketsWithSpecifiedAddress() can be
+ * used to verify that the connection manager supports this kind of access control; otherwise, this
+ * method will always fail unless QHostAddress::Any or QHostAddress::AnyIPv6 is passed, in which
+ * case the behavior is identical to the always supported acceptTubeAsTcpSocket() overload.
+ *
+ * Note that when using QHostAddress::Any or QHostAddress::AnyIPv6, \a allowedPort is ignored.
+ *
+ * This method requires IncomingStreamTubeChannel::FeatureCore to be ready.
+ *
+ * \param allowedAddress An allowed address for connecting to the socket.
+ * \param allowedPort An allowed port for connecting to the socket.
+ * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ */
+PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsTcpSocket(
+ const QHostAddress &allowedAddress,
+ quint16 allowedPort)
+{
+ if (!isReady(IncomingStreamTubeChannel::FeatureCore)) {
+ warning() << "IncomingStreamTubeChannel::FeatureCore must be ready before "
+ "calling acceptTubeAsTcpSocket";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ // The tube must be in local pending state
+ if (state() != TubeChannelStateLocalPending) {
+ warning() << "You can accept tubes only when they are in LocalPending state";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ QVariant controlParameter;
+ SocketAccessControl accessControl;
+
+ // Now, let's check what we need to do with accessControl. There is just one special case, Port.
+ if (allowedAddress != QHostAddress::Any && allowedAddress != QHostAddress::AnyIPv6) {
+ // We need to have a valid QHostAddress AND Port.
+ if (allowedAddress.isNull() || allowedPort == 0) {
+ warning() << "You have to set a valid allowed address+port to use Port access control";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("The supplied allowed address and/or port was invalid"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ accessControl = SocketAccessControlPort;
+
+ // IPv4 or IPv6?
+ if (allowedAddress.protocol() == QAbstractSocket::IPv4Protocol) {
+ // IPv4 case
+ SocketAddressIPv4 addr;
+ addr.address = allowedAddress.toString();
+ addr.port = allowedPort;
+
+ controlParameter = QVariant::fromValue(addr);
+ } else if (allowedAddress.protocol() == QAbstractSocket::IPv6Protocol) {
+ // IPv6 case
+ SocketAddressIPv6 addr;
+ addr.address = allowedAddress.toString();
+ addr.port = allowedPort;
+
+ controlParameter = QVariant::fromValue(addr);
+ } else {
+ // We're handling an IPv4/IPv6 socket only
+ warning() << "acceptTubeAsTcpSocket can be called only with a QHostAddress "
+ "representing an IPv4 or IPv6 address";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid host given"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+ } else {
+ // We have to do no special stuff here
+ accessControl = SocketAccessControlLocalhost;
+ // Since QDBusMarshaller does not like null variants, just add an empty string.
+ controlParameter = QVariant(QString());
+ }
+
+ // Set the correct address type and access control
+ setAddressType(allowedAddress.protocol() == QAbstractSocket::IPv4Protocol ?
+ SocketAddressTypeIPv4 :
+ SocketAddressTypeIPv6);
+ setAccessControl(accessControl);
+
+ // Fail early if the combination is not supported
+ if ((accessControl == SocketAccessControlLocalhost &&
+ addressType() == SocketAddressTypeIPv4 &&
+ !supportsIPv4SocketsOnLocalhost()) ||
+ (accessControl == SocketAccessControlPort &&
+ addressType() == SocketAddressTypeIPv4 &&
+ !supportsIPv4SocketsWithSpecifiedAddress()) ||
+ (accessControl == SocketAccessControlLocalhost &&
+ addressType() == SocketAddressTypeIPv6 &&
+ !supportsIPv6SocketsOnLocalhost()) ||
+ (accessControl == SocketAccessControlPort &&
+ addressType() == SocketAddressTypeIPv6 &&
+ !supportsIPv6SocketsWithSpecifiedAddress())) {
+ warning() << "You requested an address type/access control combination "
+ "not supported by this channel";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The requested address type/access control "
+ "combination is not supported"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ // Perform the actual call
+ PendingVariant *pv = new PendingVariant(
+ interface<Client::ChannelTypeStreamTubeInterface>()->Accept(
+ addressType(),
+ accessControl,
+ QDBusVariant(controlParameter)),
+ IncomingStreamTubeChannelPtr(this));
+
+ PendingStreamTubeConnection *op = new PendingStreamTubeConnection(pv, addressType(),
+ false, 0, IncomingStreamTubeChannelPtr(this));
+ return op;
+}
+
+/**
+ * Accept an incoming stream tube as a TCP socket.
+ *
+ * This method accepts an incoming connection request for a stream tube. It can be called
+ * only if the tube is in the #TubeStateLocalPending state.
+ *
+ * The connection manager will open a TCP socket for the application to connect to. The address of
+ * the socket will be returned in PendingStreamTubeConnection::ipAddress() once the operation has
+ * finished successfully.
+ *
+ * Using this overload, the connection manager will accept every incoming connection from localhost.
+ *
+ * This accept method must be supported by all connection managers adhering to the \telepathy_spec.
+ *
+ * This method requires IncomingStreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ */
+PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsTcpSocket()
+{
+ return acceptTubeAsTcpSocket(QHostAddress::Any, 0);
+}
+
+/**
+ * Accept an incoming stream tube as a Unix socket.
+ *
+ * This method accepts an incoming connection request for a stream tube. It can be called
+ * only if the tube is in the #TubeStateLocalPending state.
+ *
+ * An Unix socket (can be used with QLocalSocket or alike) will be opened by the connection manager
+ * as the local tube endpoint. This is only supported if supportsUnixSocketsOnLocalhost() is \c
+ * true.
+ *
+ * You can also specify whether the CM should require an SCM_CREDS or SCM_CREDENTIALS message
+ * upon connection instead of accepting every incoming connection from localhost. This provides
+ * additional security, but requires sending the byte retrieved from
+ * PendingStreamTubeConnection::credentialByte() in-line in the socket byte stream (in a credentials
+ * message if available on the platform), which might not be compatible with all protocols or
+ * libraries. Also, only connection managers for which supportsUnixSocketsWithCredentials() is \c
+ * true support this type of access control.
+ *
+ * This method requires IncomingStreamTubeChannel::FeatureCore to be ready.
+ *
+ * \param requireCredentials Whether the CM should require an SCM_CREDS or SCM_CREDENTIALS message
+ * upon connection.
+ * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ * \sa StreamTubeChannel::supportsUnixSocketsOnLocalhost(),
+ * StreamTubeChannel::supportsUnixSocketsWithCredentials(),
+ * StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost(),
+ * StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials()
+ */
+PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsUnixSocket(
+ bool requireCredentials)
+{
+ if (!isReady(IncomingStreamTubeChannel::FeatureCore)) {
+ warning() << "IncomingStreamTubeChannel::FeatureCore must be ready before "
+ "calling acceptTubeAsUnixSocket";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ // The tube must be in local pending state
+ if (state() != TubeChannelStateLocalPending) {
+ warning() << "You can accept tubes only when they are in LocalPending state";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ SocketAccessControl accessControl = requireCredentials ?
+ SocketAccessControlCredentials :
+ SocketAccessControlLocalhost;
+ setAddressType(SocketAddressTypeUnix);
+ setAccessControl(accessControl);
+
+ // Fail early if the combination is not supported
+ if ((accessControl == SocketAccessControlLocalhost &&
+ addressType() == SocketAddressTypeUnix &&
+ !supportsUnixSocketsOnLocalhost()) ||
+ (accessControl == SocketAccessControlCredentials &&
+ addressType() == SocketAddressTypeUnix &&
+ !supportsUnixSocketsWithCredentials()) ||
+ (accessControl == SocketAccessControlLocalhost &&
+ addressType() == SocketAddressTypeAbstractUnix &&
+ !supportsAbstractUnixSocketsOnLocalhost()) ||
+ (accessControl == SocketAccessControlCredentials &&
+ addressType() == SocketAddressTypeAbstractUnix &&
+ !supportsAbstractUnixSocketsWithCredentials())) {
+ warning() << "You requested an address type/access control combination "
+ "not supported by this channel";
+ return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The requested address type/access control "
+ "combination is not supported"),
+ IncomingStreamTubeChannelPtr(this));
+ }
+
+ QDBusVariant accessControlParam;
+ uchar credentialByte = 0;
+ if (accessControl == SocketAccessControlLocalhost) {
+ accessControlParam.setVariant(qVariantFromValue(static_cast<uint>(0)));
+ } else if (accessControl == SocketAccessControlCredentials) {
+ if (mPriv->initRandom) {
+ qsrand(QTime::currentTime().msec());
+ mPriv->initRandom = false;
+ }
+ credentialByte = static_cast<uchar>(qrand());
+ accessControlParam.setVariant(qVariantFromValue(credentialByte));
+ } else {
+ Q_ASSERT(false);
+ }
+
+ // Perform the actual call
+ PendingVariant *pv = new PendingVariant(
+ interface<Client::ChannelTypeStreamTubeInterface>()->Accept(
+ addressType(),
+ accessControl,
+ accessControlParam),
+ IncomingStreamTubeChannelPtr(this));
+
+ PendingStreamTubeConnection *op = new PendingStreamTubeConnection(pv, addressType(),
+ requireCredentials, credentialByte, IncomingStreamTubeChannelPtr(this));
+ return op;
+}
+
+/**
+ * Return the local address of the opened stream tube.
+ *
+ * Calling this method when the tube has not been opened will cause it
+ * to return an undefined value. The same will happen if the tube has been accepted as a TCP
+ * socket. Use ipAddress() if that is the case.
+ *
+ * \return Unix socket address if using an Unix socket,
+ * or an undefined value otherwise.
+ * \sa acceptTubeAsUnixSocket(), ipAddress()
+ */
+QString IncomingStreamTubeChannel::localAddress() const
+{
+ return StreamTubeChannel::localAddress();
+}
+
+/**
+ * Return the IP address/port combination of the opened stream tube.
+ *
+ * Calling this method when the tube has not been opened will cause it
+ * to return an undefined value. The same will happen if the tube has been accepted as an Unix
+ * socket. Use localAddress() if that is the case.
+ *
+ * \return Pair of IP address as QHostAddress and port if using a TCP socket,
+ * or an undefined value otherwise.
+ * \sa acceptTubeAsTcpSocket(), localAddress()
+ */
+QPair<QHostAddress, quint16> IncomingStreamTubeChannel::ipAddress() const
+{
+ return StreamTubeChannel::ipAddress();
+}
+
+void IncomingStreamTubeChannel::onNewLocalConnection(uint connectionId)
+{
+ addConnection(connectionId);
+}
+
+}
diff --git a/qt4/TelepathyQt4/incoming-stream-tube-channel.h b/qt4/TelepathyQt4/incoming-stream-tube-channel.h
new file mode 100644
index 000000000..5406410b3
--- /dev/null
+++ b/qt4/TelepathyQt4/incoming-stream-tube-channel.h
@@ -0,0 +1,80 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_incoming_stream_tube_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_incoming_stream_tube_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/StreamTubeChannel>
+
+#include <QtNetwork/QHostAddress>
+
+class QIODevice;
+
+namespace Tp
+{
+
+class PendingStreamTubeConnection;
+
+class TELEPATHY_QT4_EXPORT IncomingStreamTubeChannel : public StreamTubeChannel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(IncomingStreamTubeChannel)
+
+public:
+ static const Feature FeatureCore;
+
+ static IncomingStreamTubeChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~IncomingStreamTubeChannel();
+
+ PendingStreamTubeConnection *acceptTubeAsTcpSocket();
+ PendingStreamTubeConnection *acceptTubeAsTcpSocket(const QHostAddress &allowedAddress,
+ quint16 allowedPort);
+ PendingStreamTubeConnection *acceptTubeAsUnixSocket(bool requireCredentials = false);
+
+ // FIXME: (API/ABI break) Remove ipAddress() and localAddress() (already in StreamTubeChannel)
+ QPair<QHostAddress, quint16> ipAddress() const;
+ QString localAddress() const;
+
+protected:
+ IncomingStreamTubeChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = IncomingStreamTubeChannel::FeatureCore);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onNewLocalConnection(uint connectionId);
+
+private:
+ struct Private;
+ friend class PendingStreamTubeConnection;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+#endif
diff --git a/qt4/TelepathyQt4/key-file.cpp b/qt4/TelepathyQt4/key-file.cpp
new file mode 100644
index 000000000..f8396deb3
--- /dev/null
+++ b/qt4/TelepathyQt4/key-file.cpp
@@ -0,0 +1,582 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/KeyFile>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Utils>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QFile>
+#include <QtCore/QHash>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT KeyFile::Private
+{
+ Private();
+ Private(const QString &fName);
+
+ void setFileName(const QString &fName);
+ void setError(KeyFile::Status status, const QString &reason);
+ bool read();
+
+ bool validateKey(const QByteArray &data, int from, int to, QString &result);
+
+ QStringList allGroups() const;
+ QStringList allKeys() const;
+ QStringList keys() const;
+ bool contains(const QString &key) const;
+ QString rawValue(const QString &key) const;
+ QString value(const QString &key) const;
+ QStringList valueAsStringList(const QString &key) const;
+
+ QString fileName;
+ KeyFile::Status status;
+ QHash<QString, QHash<QString, QByteArray> > groups;
+ QString currentGroup;
+};
+
+KeyFile::Private::Private()
+ : status(KeyFile::None)
+{
+}
+
+KeyFile::Private::Private(const QString &fName)
+ : fileName(fName),
+ status(KeyFile::NoError)
+{
+ read();
+}
+
+void KeyFile::Private::setFileName(const QString &fName)
+{
+ fileName = fName;
+ status = KeyFile::NoError;
+ currentGroup = QString();
+ groups.clear();
+ read();
+}
+
+void KeyFile::Private::setError(KeyFile::Status st, const QString &reason)
+{
+ warning() << QString(QLatin1String("ERROR: filename(%1) reason(%2)"))
+ .arg(fileName).arg(reason);
+ status = st;
+ groups.clear();
+}
+
+bool KeyFile::Private::read()
+{
+ QFile file(fileName);
+ if (!file.exists()) {
+ setError(KeyFile::NotFoundError,
+ QLatin1String("file does not exist"));
+ return false;
+ }
+
+ if (!file.open(QFile::ReadOnly)) {
+ setError(KeyFile::AccessError,
+ QLatin1String("cannot open file for readonly access"));
+ return false;
+ }
+
+ QByteArray data;
+ QByteArray group;
+ QString currentGroup;
+ QHash<QString, QByteArray> groupMap;
+ QByteArray rawValue;
+ int line = 0;
+ int idx;
+ while (!file.atEnd()) {
+ data = file.readLine().trimmed();
+ line++;
+
+ if (data.size() == 0) {
+ // skip empty lines
+ continue;
+ }
+
+ char ch = data.at(0);
+ if (ch == '#') {
+ // skip comments
+ continue;
+ }
+ else if (ch == '[') {
+ if (groupMap.size()) {
+ groups[currentGroup] = groupMap;
+ groupMap.clear();
+ }
+
+ idx = data.indexOf(']');
+ if (idx == -1) {
+ // line starts with [ and it's not a group
+ setError(KeyFile::FormatError,
+ QString(QLatin1String("invalid group at line %2 - missing ']'"))
+ .arg(line));
+ return false;
+ }
+
+ group = data.mid(1, idx - 1).trimmed();
+ if (groups.contains(QLatin1String(group))) {
+ setError(KeyFile::FormatError,
+ QString(QLatin1String("duplicated group '%1' at line %2"))
+ .arg(QLatin1String(group)).arg(line));
+ return false;
+ }
+
+ currentGroup = QLatin1String("");
+ if (!unescapeString(group, 0, group.size(), currentGroup)) {
+ setError(KeyFile::FormatError,
+ QString(QLatin1String("invalid group '%1' at line %2"))
+ .arg(currentGroup).arg(line));
+ return false;
+ }
+ }
+ else {
+ idx = data.indexOf('=');
+ if (idx == -1) {
+ setError(KeyFile::FormatError,
+ QString(QLatin1String("format error at line %1 - missing '='"))
+ .arg(line));
+ return false;
+ }
+
+ // remove trailing spaces
+ char ch;
+ int idxKeyEnd = idx;
+ while ((ch = data.at(idxKeyEnd - 1)) == ' ' || ch == '\t') {
+ --idxKeyEnd;
+ }
+
+ QString key;
+ if (!validateKey(data, 0, idxKeyEnd, key)) {
+ setError(KeyFile::FormatError,
+ QString(QLatin1String("invalid key '%1' at line %2"))
+ .arg(key).arg(line));
+ return false;
+ }
+
+ if (groupMap.contains(key)) {
+ setError(KeyFile::FormatError,
+ QString(QLatin1String("duplicated key '%1' on group '%2' at line %3"))
+ .arg(key).arg(currentGroup).arg(line));
+ return false;
+ }
+
+ data = data.mid(idx + 1).trimmed();
+ rawValue = data.mid(0, data.size());
+ groupMap[key] = rawValue;
+ }
+ }
+
+ if (groupMap.size()) {
+ groups[currentGroup] = groupMap;
+ groupMap.clear();
+ }
+
+ return true;
+}
+
+bool KeyFile::Private::validateKey(const QByteArray &data, int from, int to, QString &result)
+{
+ int i = from;
+ bool ret = true;
+ while (i < to) {
+ uint ch = data.at(i++);
+ // as an extension to the Desktop Entry spec, we allow " ", "_", "." and "@"
+ // as valid key characters - "_" and "." are needed for keys that are
+ // D-Bus property names, and GKeyFile and KConfigIniBackend also accept
+ // all four of those characters.
+ if (!((ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z') ||
+ (ch >= '0' && ch <= '9') ||
+ (ch == ' ') ||
+ (ch == '-') || (ch == '_') ||
+ (ch == '.') || (ch == '@'))) {
+ ret = false;
+ }
+ result += ch;
+ }
+ return ret;
+}
+
+QStringList KeyFile::Private::allGroups() const
+{
+ return groups.keys();
+}
+
+QStringList KeyFile::Private::allKeys() const
+{
+ QStringList keys;
+ QHash<QString, QHash<QString, QByteArray> >::const_iterator itrGroups = groups.begin();
+ while (itrGroups != groups.end()) {
+ keys << itrGroups.value().keys();
+ ++itrGroups;
+ }
+ return keys;
+}
+
+QStringList KeyFile::Private::keys() const
+{
+ QHash<QString, QByteArray> groupMap = groups[currentGroup];
+ return groupMap.keys();
+}
+
+bool KeyFile::Private::contains(const QString &key) const
+{
+ QHash<QString, QByteArray> groupMap = groups[currentGroup];
+ return groupMap.contains(key);
+}
+
+QString KeyFile::Private::rawValue(const QString &key) const
+{
+ QHash<QString, QByteArray> groupMap = groups[currentGroup];
+ QByteArray rawValue = groupMap.value(key);
+ return QLatin1String(rawValue);
+}
+
+QString KeyFile::Private::value(const QString &key) const
+{
+ QHash<QString, QByteArray> groupMap = groups[currentGroup];
+ QString result;
+ QByteArray rawValue = groupMap.value(key);
+ if (unescapeString(rawValue, 0, rawValue.size(), result)) {
+ return result;
+ }
+ return QString();
+}
+
+QStringList KeyFile::Private::valueAsStringList(const QString &key) const
+{
+ QHash<QString, QByteArray> groupMap = groups[currentGroup];
+ QStringList result;
+ QByteArray rawValue = groupMap.value(key);
+ if (unescapeStringList(rawValue, 0, rawValue.size(), result)) {
+ return result;
+ }
+ return QStringList();
+}
+
+/**
+ * \class KeyFile
+ * \ingroup utils
+ * \headerfile TelepathyQt4/key-file.h <TelepathyQt4/KeyFile>
+ *
+ * \brief The KeyFile class provides an easy way to read key-pair files such as
+ * INI style files and .desktop files.
+ *
+ * It follows the rules regarding string escaping as defined in
+ * http://standards.freedesktop.org/desktop-entry-spec/latest/index.html
+ *
+ * \todo Consider making this private, because the ConnectionManager and Account classes provide a
+ * nice view to the data parsed by this class anyway (and there's even a higher-level ManagerFile
+ * class in between) (fd.o #41655).
+ */
+
+/**
+ * Create a KeyFile object used to read (key-pair) compliant files.
+ *
+ * The status will be KeyFile::None
+ * \sa setFileName()
+ */
+KeyFile::KeyFile()
+ : mPriv(new Private())
+{
+}
+
+/**
+ * Create a KeyFile object used to read (key-pair) compliant files.
+ *
+ * \param fileName Name of the file to be read.
+ */
+KeyFile::KeyFile(const QString &fileName)
+ : mPriv(new Private(fileName))
+{
+}
+
+/**
+ * Create a KeyFile object used to read (key-pair) compliant files.
+ */
+KeyFile::KeyFile(const KeyFile &other)
+ : mPriv(new Private())
+{
+ mPriv->fileName = other.mPriv->fileName;
+ mPriv->status = other.mPriv->status;
+ mPriv->groups = other.mPriv->groups;
+ mPriv->currentGroup = other.mPriv->currentGroup;
+}
+
+/**
+ * Class destructor.
+ */
+KeyFile::~KeyFile()
+{
+ delete mPriv;
+}
+
+KeyFile &KeyFile::operator=(const KeyFile &other)
+{
+ mPriv->fileName = other.mPriv->fileName;
+ mPriv->status = other.mPriv->status;
+ mPriv->groups = other.mPriv->groups;
+ mPriv->currentGroup = other.mPriv->currentGroup;
+ return *this;
+}
+
+/**
+ * Set the name of the file to be read.
+ *
+ * \param fileName Name of the file to be read.
+ */
+void KeyFile::setFileName(const QString &fileName)
+{
+ mPriv->setFileName(fileName);
+}
+
+/**
+ * Return the name of the file associated with this object.
+ *
+ * \return Name of the file associated with this object.
+ */
+QString KeyFile::fileName() const
+{
+ return mPriv->fileName;
+}
+
+/**
+ * Return a status code indicating the first error that was met by #KeyFile,
+ * or KeyFile::NoError if no error occurred.
+ *
+ * Make sure to use this method if you set the filename to be read using
+ * setFileName().
+ *
+ * \return Status code.
+ * \sa setFileName()
+ */
+KeyFile::Status KeyFile::status() const
+{
+ return mPriv->status;
+}
+
+/**
+ * Set the current group to be used while reading keys.
+ *
+ * Query functions such as keys(), contains() and value() are based on this
+ * group.
+ *
+ * By default a empty group is used as the group for global
+ * keys and is used as the default group if none is set.
+ *
+ * \param group Name of the group to be used.
+ * \sa group()
+ */
+void KeyFile::setGroup(const QString &group)
+{
+ mPriv->currentGroup = group;
+}
+
+/**
+ * Return the current group.
+ *
+ * \return Name of the current group.
+ * \sa setGroup()
+ */
+QString KeyFile::group() const
+{
+ return mPriv->currentGroup;
+}
+
+/**
+ * Return all groups in the desktop file.
+ *
+ * Global keys will be added to a empty group.
+ *
+ * \return List of all groups in the desktop file.
+ */
+QStringList KeyFile::allGroups() const
+{
+ return mPriv->allGroups();
+}
+
+/**
+ * Return all keys described in the desktop file.
+ *
+ * \return List of all keys in the desktop file.
+ */
+QStringList KeyFile::allKeys() const
+{
+ return mPriv->allKeys();
+}
+
+/**
+ * Return a list of keys in the current group.
+ *
+ * \return List of all keys in the current group.
+ * \sa group(), setGroup()
+ */
+QStringList KeyFile::keys() const
+{
+ return mPriv->keys();
+}
+
+/**
+ * Check if the current group contains a key named \a key.
+ *
+ * \return true if \a key exists, false otherwise.
+ * \sa group(), setGroup()
+ */
+bool KeyFile::contains(const QString &key) const
+{
+ return mPriv->contains(key);
+}
+
+/**
+ * Get the raw value for the key in the current group named \a key.
+ *
+ * The raw value is the value as is in the key file.
+ *
+ * \return Value of \a key, empty string if not found.
+ * \sa group(), setGroup()
+ */
+QString KeyFile::rawValue(const QString &key) const
+{
+ return mPriv->rawValue(key);
+}
+
+/**
+ * Get the value for the key in the current group named \a key.
+ *
+ * Escape sequences in the value are interpreted as defined in:
+ * http://standards.freedesktop.org/desktop-entry-spec/latest/
+ *
+ * \return Value of \a key, empty string if not found or an error occurred.
+ * \sa group(), setGroup()
+ */
+QString KeyFile::value(const QString &key) const
+{
+ return mPriv->value(key);
+}
+
+/**
+ * Get the value for the key in the current group named \a key as a list.
+ *
+ * Return a list containing all strings on this key separated by ';'.
+ * Escape sequences in the value are interpreted as defined in:
+ * http://standards.freedesktop.org/desktop-entry-spec/latest/
+ *
+ * \return Value of \a key as a list, empty string list if not found or an error occurred.
+ * \sa group(), setGroup()
+ */
+QStringList KeyFile::valueAsStringList(const QString &key) const
+{
+ return mPriv->valueAsStringList(key);
+}
+
+bool KeyFile::unescapeString(const QByteArray &data, int from, int to, QString &result)
+{
+ int i = from;
+ while (i < to) {
+ uint ch = data.at(i++);
+
+ if (ch == '\\') {
+ if (i == to) {
+ result += QLatin1String("\\");
+ return true;
+ }
+
+ char nextCh = data.at(i++);
+ switch (nextCh) {
+ case 's':
+ result += QLatin1String(" ");
+ break;
+ case 'n':
+ result += QLatin1String("\n");
+ break;
+ case 't':
+ result += QLatin1String("\t");
+ break;
+ case 'r':
+ result += QLatin1String("\r");
+ break;
+ case ';':
+ result += QLatin1String(";");
+ break;
+ case '\\':
+ result += QLatin1String("\\");
+ break;
+ default:
+ return false;
+ }
+ } else {
+ result += ch;
+ }
+ }
+
+ return true;
+}
+
+bool KeyFile::unescapeStringList(const QByteArray &data, int from, int to, QStringList &result)
+{
+ QByteArray value;
+ QList<QByteArray> valueList;
+ int i = from;
+ char ch;
+ while (i < to) {
+ ch = data.at(i++);
+
+ if (ch == '\\') {
+ value += ch;
+ if (i < to) {
+ value += data.at(i++);
+ continue;
+ } else {
+ valueList << value;
+ break;
+ }
+ } else if (ch == ';') {
+ valueList << value;
+ value = "";
+ } else {
+ value += ch;
+ if (i == to) {
+ valueList << value;
+ }
+ }
+ }
+
+ foreach (value, valueList) {
+ QString str;
+ if (!unescapeString(value, 0, value.size(), str)) {
+ return false;
+ }
+ result << str;
+ }
+
+ return true;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/key-file.h b/qt4/TelepathyQt4/key-file.h
new file mode 100644
index 000000000..14f45cb8b
--- /dev/null
+++ b/qt4/TelepathyQt4/key-file.h
@@ -0,0 +1,91 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_key_file_h_HEADER_GUARD_
+#define _TelepathyQt4_key_file_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QMetaType>
+#include <QtGlobal>
+
+class QString;
+class QStringList;
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT KeyFile
+{
+public:
+ enum Status {
+ None = 0,
+ NoError,
+ NotFoundError,
+ AccessError,
+ FormatError,
+ };
+
+ KeyFile();
+ KeyFile(const KeyFile &other);
+ KeyFile(const QString &fileName);
+ ~KeyFile();
+
+ KeyFile &operator=(const KeyFile &other);
+
+ void setFileName(const QString &fileName);
+ QString fileName() const;
+
+ Status status() const;
+
+ void setGroup(const QString &group);
+ QString group() const;
+
+ QStringList allGroups() const;
+ QStringList allKeys() const;
+ QStringList keys() const;
+ bool contains(const QString &key) const;
+
+ QString rawValue(const QString &key) const;
+ QString value(const QString &key) const;
+ QStringList valueAsStringList(const QString &key) const;
+
+ static bool unescapeString(const QByteArray &data, int from, int to,
+ QString &result);
+ static bool unescapeStringList(const QByteArray &data, int from, int to,
+ QStringList &result);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+Q_DECLARE_METATYPE(Tp::KeyFile);
+
+#endif
diff --git a/qt4/TelepathyQt4/location-info.cpp b/qt4/TelepathyQt4/location-info.cpp
new file mode 100644
index 000000000..09aa1d28d
--- /dev/null
+++ b/qt4/TelepathyQt4/location-info.cpp
@@ -0,0 +1,221 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/LocationInfo>
+
+#include <QDBusArgument>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT LocationInfo::Private : public QSharedData
+{
+ QVariantMap location;
+};
+
+/**
+ * \class LocationInfo
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/location-info.h <TelepathyQt4/LocationInfo>
+ *
+ * \brief The LocationInfo class represents the location of a
+ * Telepathy Contact.
+ */
+
+/**
+ * Construct a new LocationInfo object.
+ */
+LocationInfo::LocationInfo()
+ : mPriv(new Private)
+{
+}
+
+LocationInfo::LocationInfo(const QVariantMap &location)
+ : mPriv(new Private)
+{
+ mPriv->location = location;
+}
+
+LocationInfo::LocationInfo(const LocationInfo &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+LocationInfo::~LocationInfo()
+{
+}
+
+LocationInfo &LocationInfo::operator=(const LocationInfo &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+QString LocationInfo::countryCode() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("countrycode")));
+}
+
+QString LocationInfo::country() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("country")));
+}
+
+QString LocationInfo::region() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("region")));
+}
+
+QString LocationInfo::locality() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("locality")));
+}
+
+QString LocationInfo::area() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("area")));
+}
+
+QString LocationInfo::postalCode() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("postalcode")));
+}
+
+QString LocationInfo::street() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("street")));
+}
+
+QString LocationInfo::building() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("building")));
+}
+
+QString LocationInfo::floor() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("floor")));
+}
+
+QString LocationInfo::room() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("room")));
+}
+
+QString LocationInfo::text() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("text")));
+}
+
+QString LocationInfo::description() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("description")));
+}
+
+QString LocationInfo::uri() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("uri")));
+}
+
+QString LocationInfo::language() const
+{
+ return qdbus_cast<QString>(mPriv->location.value(
+ QLatin1String("language")));
+}
+
+double LocationInfo::latitude() const
+{
+ return qdbus_cast<double>(mPriv->location.value(
+ QLatin1String("lat")));
+}
+
+double LocationInfo::longitude() const
+{
+ return qdbus_cast<double>(mPriv->location.value(
+ QLatin1String("lon")));
+}
+
+double LocationInfo::altitude() const
+{
+ return qdbus_cast<double>(mPriv->location.value(
+ QLatin1String("alt")));
+}
+
+double LocationInfo::accuracy() const
+{
+ return qdbus_cast<double>(mPriv->location.value(
+ QLatin1String("accuracy")));
+}
+
+double LocationInfo::speed() const
+{
+ return qdbus_cast<double>(mPriv->location.value(
+ QLatin1String("speed")));
+}
+
+double LocationInfo::bearing() const
+{
+ return qdbus_cast<double>(mPriv->location.value(
+ QLatin1String("bearing")));
+}
+
+QDateTime LocationInfo::timestamp() const
+{
+ // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690
+ qlonglong t = qdbus_cast<qlonglong>(mPriv->location.value(
+ QLatin1String("timestamp")));
+ if (t != 0) {
+ return QDateTime::fromTime_t((uint) t);
+ }
+ return QDateTime();
+}
+
+QVariantMap LocationInfo::allDetails() const
+{
+ return mPriv->location;
+}
+
+void LocationInfo::updateData(const QVariantMap &location)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->location = location;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/location-info.h b/qt4/TelepathyQt4/location-info.h
new file mode 100644
index 000000000..0917fbf91
--- /dev/null
+++ b/qt4/TelepathyQt4/location-info.h
@@ -0,0 +1,95 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_location_info_h_HEADER_GUARD_
+#define _TelepathyQt4_location_info_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QDateTime>
+#include <QSharedDataPointer>
+#include <QString>
+#include <QVariantMap>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT LocationInfo
+{
+public:
+ LocationInfo();
+ LocationInfo(const QVariantMap &location);
+ LocationInfo(const LocationInfo &other);
+ virtual ~LocationInfo();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ LocationInfo &operator=(const LocationInfo &other);
+
+ QString countryCode() const;
+ QString country() const;
+ QString region() const;
+ QString locality() const;
+ QString area() const;
+ QString postalCode() const;
+ QString street() const;
+
+ QString building() const;
+ QString floor() const;
+ QString room() const;
+ QString text() const;
+ QString description() const;
+ QString uri() const;
+
+ QString language() const;
+
+ double latitude() const;
+ double longitude() const;
+ double altitude() const;
+ double accuracy() const;
+
+ double speed() const;
+ double bearing() const;
+
+ QDateTime timestamp() const;
+
+ QVariantMap allDetails() const;
+
+private:
+ friend class Contact;
+
+ TELEPATHY_QT4_NO_EXPORT void updateData(const QVariantMap &location);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::LocationInfo);
+
+#endif
diff --git a/qt4/TelepathyQt4/main.dox b/qt4/TelepathyQt4/main.dox
new file mode 100644
index 000000000..10042ba26
--- /dev/null
+++ b/qt4/TelepathyQt4/main.dox
@@ -0,0 +1,133 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \mainpage Telepathy-Qt4
+ *
+ * \section Introduction
+ *
+ * Telepathy-Qt4 is a Qt4 high-level binding for \telepathy.
+ *
+ * \telepathy is a \dbus framework for unifying real time communication, including instant
+ * messaging, voice calls and video calls. It abstracts differences between protocols to
+ * provide a unified interface for applications.
+ *
+ * Releases can be found <a
+ * href="http://telepathy.freedesktop.org/releases/telepathy-qt4">here</a>.
+ *
+ * Development is done in the git repository found <a
+ * href="http://cgit.freedesktop.org/telepathy/telepathy-qt4/">here</a>.
+ *
+ * \li <a href="classes.html">All Classes</a>
+ *
+ * \section getting_started Getting Started
+ * \li \subpage installation
+ *
+ * \section examples Examples
+ *
+ * This is the list of examples in Telepathy-Qt4's examples directory.
+ * The examples demonstrate Telepathy-Qt4 features in small, self-contained
+ * programs. They are not all designed to be impressive when you run them,
+ * but their source code is carefully written to show good Telepathy-Qt4
+ * programming practices.
+ *
+ * \li \subpage accounts_example
+ * \li \subpage contact_messenger_example
+ * \li \subpage protocols_example
+ * \li \subpage roster_example
+ *
+ * \section developer_resources Further Information
+ * \li \subpage bugreport
+ * \li \subpage mailing_lists
+ * \li \subpage async_model
+ * \li \subpage shared_ptr
+ */
+
+/**
+ * \page installation Installation
+ *
+ * \section installation_from_source Installing from source on Linux
+ *
+ * \subsection installation_from_source_requirements Requirements
+ *
+ * Building Telepathy-Qt4 requires:
+ * \li Qt, including QtDBus <http://www.qtsoftware.com/>
+ * \li GNU make <http://www.gnu.org/software/make/>
+ * \li pkg-config <http://ftp.gnome.org/pub/GNOME/sources/pkg-config/>
+ * \li libxslt, xsltproc <http://xmlsoft.org/XSLT/>
+ * \li Python <http://www.python.org/>
+ *
+ * For the full set of regression tests to run, you'll also need:
+ * \li telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/>
+ *
+ * and to build the example VoIP call UI (examples/call), you'll need:
+ * \li telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/>
+ * \li telepathy-farsight
+ * <http://telepathy.freedesktop.org/releases/telepathy-farsight/>
+ * \li GStreamer <http://gstreamer.freedesktop.org/>\n
+ *
+ * Building also requires the cmake build system.
+ *
+ * \subsection installation_from_source_building Building
+ *
+ * After installing all dependencies, run:
+ *
+ * \verbatim
+ $ mkdir build; cd build
+ $ cmake ..
+ $ make
+ $ make install \endverbatim
+ */
+
+/**
+ * \page bugreport How to report a bug
+ *
+ * Before reporting a bug, please check the <a
+ * href="https://bugs.freedesktop.org/query.cgi?product=Telepathy&component=telepathy-qt4">
+ * Bug Tracker</a> to see if the issue is already known.
+ *
+ * Always include the following information in your bug report:
+ * \li The version of Telepathy-Qt4 you are using
+ *
+ * Please submit the bug report, feature request or "to-do" item
+ * <a
+ * href="https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=telepathy-qt4">
+ * here</a>.
+ */
+
+/**
+ * \page mailing_lists Mailing Lists
+ *
+ * <a href="http://lists.freedesktop.org/mailman/listinfo/telepathy">General
+ * discussion list</a>\n
+ * This list should be used for general discussion about \telepathy usage,
+ * development.
+ *
+ * <a
+ * href="http://lists.freedesktop.org/mailman/listinfo/telepathy-commits">
+ * Commits list</a>\n
+ * Subscribe to this list to follow the commits.
+ *
+ * <a
+ * href="http://lists.freedesktop.org/mailman/listinfo/telepathy-bugs">
+ * Bugs list</a>\n
+ * Subscribe to this list to follow the bug reports.
+ */
diff --git a/qt4/TelepathyQt4/manager-file.cpp b/qt4/TelepathyQt4/manager-file.cpp
new file mode 100644
index 000000000..faf93c409
--- /dev/null
+++ b/qt4/TelepathyQt4/manager-file.cpp
@@ -0,0 +1,618 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ManagerFile>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/KeyFile>
+
+#include <QtCore/QDir>
+#include <QtCore/QHash>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtDBus/QDBusVariant>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ManagerFile::Private
+{
+ Private();
+ Private(const QString &cnName);
+
+ void init();
+ bool parse(const QString &fileName);
+ bool isValid() const;
+
+ bool hasParameter(const QString &protocol, const QString &paramName) const;
+ ParamSpec *getParameter(const QString &protocol, const QString &paramName);
+ QStringList protocols() const;
+ ParamSpecList parameters(const QString &protocol) const;
+
+ QVariant valueForKey(const QString &param, const QString &dbusSignature);
+
+ struct ProtocolInfo
+ {
+ ProtocolInfo() {}
+ ProtocolInfo(const ParamSpecList &params, const PresenceSpecList &statuses)
+ : params(params),
+ statuses(statuses)
+ {
+ }
+
+ ParamSpecList params;
+ QString vcardField;
+ QString englishName;
+ QString iconName;
+ RequestableChannelClassList rccs;
+ PresenceSpecList statuses;
+ AvatarSpec avatarRequirements;
+ };
+
+ QString cmName;
+ KeyFile keyFile;
+ QHash<QString, ProtocolInfo> protocolsMap;
+ bool valid;
+};
+
+ManagerFile::Private::Private()
+ : valid(false)
+{
+}
+
+ManagerFile::Private::Private(const QString &cmName)
+ : cmName(cmName),
+ valid(false)
+{
+ init();
+}
+
+void ManagerFile::Private::init()
+{
+ // TODO: should we cache the configDirs anywhere?
+ QStringList configDirs;
+
+ QString xdgDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"));
+ if (xdgDataHome.isEmpty()) {
+ configDirs << QDir::homePath() + QLatin1String("/.local/share/data/telepathy/managers/");
+ }
+ else {
+ configDirs << xdgDataHome + QLatin1String("/telepathy/managers/");
+ }
+
+ QString xdgDataDirsEnv = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS"));
+ if (xdgDataDirsEnv.isEmpty()) {
+ configDirs << QLatin1String("/usr/local/share/telepathy/managers/");
+ configDirs << QLatin1String("/usr/share/telepathy/managers/");
+ }
+ else {
+ QStringList xdgDataDirs = xdgDataDirsEnv.split(QLatin1Char(':'));
+ foreach (const QString xdgDataDir, xdgDataDirs) {
+ configDirs << xdgDataDir + QLatin1String("/telepathy/managers/");
+ }
+ }
+
+ foreach (const QString configDir, configDirs) {
+ QString fileName = configDir + cmName + QLatin1String(".manager");
+ if (QFile::exists(fileName)) {
+ debug() << "parsing manager file" << fileName;
+ protocolsMap.clear();
+ if (!parse(fileName)) {
+ warning() << "error parsing manager file" << fileName;
+ continue;
+ }
+ valid = true;
+ return;
+ }
+ }
+}
+
+bool ManagerFile::Private::parse(const QString &fileName)
+{
+ keyFile.setFileName(fileName);
+ if (keyFile.status() != KeyFile::NoError) {
+ return false;
+ }
+
+ /* read supported protocols and parameters */
+ QString protocol;
+ QStringList groups = keyFile.allGroups();
+ foreach (const QString group, groups) {
+ if (group.startsWith(QLatin1String("Protocol "))) {
+ protocol = group.right(group.length() - 9);
+ keyFile.setGroup(group);
+
+ ParamSpecList paramSpecList;
+ SimpleStatusSpecMap statuses;
+ QString param;
+ QStringList params = keyFile.keys();
+ foreach (param, params) {
+ ParamSpec spec;
+ SimpleStatusSpec status;
+ spec.flags = 0;
+
+ QStringList values = keyFile.value(param).split(QLatin1String(" "));
+
+ if (param.startsWith(QLatin1String("param-"))) {
+ spec.name = param.right(param.length() - 6);
+
+ if (values.length() == 0) {
+ warning() << "param" << spec.name << "set but no signature defined";
+ return false;
+ }
+
+ if (spec.name.endsWith(QLatin1String("password"))) {
+ spec.flags |= ConnMgrParamFlagSecret;
+ }
+
+ spec.signature = values[0];
+
+ if (values.contains(QLatin1String("secret"))) {
+ spec.flags |= ConnMgrParamFlagSecret;
+ }
+
+ if (values.contains(QLatin1String("dbus-property"))) {
+ spec.flags |= ConnMgrParamFlagDBusProperty;
+ }
+
+ if (values.contains(QLatin1String("required"))) {
+ spec.flags |= ConnMgrParamFlagRequired;
+ }
+
+ if (values.contains(QLatin1String("register"))) {
+ spec.flags |= ConnMgrParamFlagRegister;
+ }
+
+ paramSpecList.append(spec);
+ } else if (param.startsWith(QLatin1String("status-"))) {
+ QString statusName = param.right(param.length() - 7);
+
+ if (values.length() == 0) {
+ warning() << "status" << statusName << "set but no type defined";
+ return false;
+ }
+
+ bool ok;
+ status.type = values[0].toUInt(&ok);
+ if (!ok) {
+ warning() << "status" << statusName << "set but type is not an uint";
+ return false;
+ }
+
+ if (values.contains(QLatin1String("settable"))) {
+ status.maySetOnSelf = true;
+ } else {
+ status.maySetOnSelf = false;
+ }
+
+ if (values.contains(QLatin1String("message"))) {
+ status.canHaveMessage = true;
+ } else {
+ status.canHaveMessage = false;
+ }
+
+ if (statuses.contains(statusName)) {
+ warning() << "status" << statusName << "defined more than once, "
+ "replacing it";
+ }
+
+ statuses.insert(statusName, status);
+ }
+ }
+
+ protocolsMap.insert(protocol, ProtocolInfo(paramSpecList, PresenceSpecList(statuses)));
+
+ /* now that we have all param-* created, let's find their default values */
+ foreach (param, params) {
+ if (param.startsWith(QLatin1String("default-"))) {
+ QString paramName = param.right(param.length() - 8);
+
+ if (!hasParameter(protocol, paramName)) {
+ warning() << "param" << paramName
+ << "has default value set, but not a definition";
+ continue;
+ }
+
+ ParamSpec *spec = getParameter(protocol, paramName);
+
+ spec->flags |= ConnMgrParamFlagHasDefault;
+
+ /* map based on the param dbus signature, otherwise use
+ * QString */
+ QVariant value = valueForKey(param, spec->signature);
+ if (value.type() == QVariant::Invalid) {
+ warning() << "param" << paramName
+ << "has invalid signature";
+ protocolsMap.clear();
+ return false;
+ }
+ spec->defaultValue = QDBusVariant(value);
+ }
+ }
+
+ ProtocolInfo &info = protocolsMap[protocol];
+ info.vcardField = keyFile.value(QLatin1String("VCardField"));
+ info.englishName = keyFile.value(QLatin1String("EnglishName"));
+ if (info.englishName.isEmpty()) {
+ QStringList words = protocol.split(QLatin1Char('-'));
+ for (int i = 0; i < words.size(); ++i) {
+ words[i][0] = words[i].at(0).toUpper();
+ }
+ info.englishName = words.join(QLatin1String(" "));
+ }
+
+ info.iconName = keyFile.value(QLatin1String("Icon"));
+ if (info.iconName.isEmpty()) {
+ info.iconName = QString(QLatin1String("im-%1")).arg(protocol);
+ }
+
+ QStringList supportedMimeTypes = keyFile.valueAsStringList(
+ QLatin1String("SupportedAvatarMIMETypes"));
+ uint minHeight = keyFile.value(QLatin1String("MinimumAvatarHeight")).toUInt();
+ uint maxHeight = keyFile.value(QLatin1String("MaximumAvatarHeight")).toUInt();
+ uint recommendedHeight = keyFile.value(
+ QLatin1String("RecommendedAvatarHeight")).toUInt();
+ uint minWidth = keyFile.value(QLatin1String("MinimumAvatarWidth")).toUInt();
+ uint maxWidth = keyFile.value(QLatin1String("MaximumAvatarWidth")).toUInt();
+ uint recommendedWidth = keyFile.value(
+ QLatin1String("RecommendedAvatarWidth")).toUInt();
+ uint maxBytes = keyFile.value(QLatin1String("MaximumAvatarBytes")).toUInt();
+ info.avatarRequirements = AvatarSpec(supportedMimeTypes,
+ minHeight, maxHeight, recommendedHeight,
+ minWidth, maxWidth, recommendedWidth,
+ maxBytes);
+
+ QStringList rccGroups = keyFile.valueAsStringList(
+ QLatin1String("RequestableChannelClasses"));
+ RequestableChannelClass rcc;
+ foreach (const QString &rccGroup, rccGroups) {
+ keyFile.setGroup(rccGroup);
+
+ foreach (const QString &key, keyFile.keys()) {
+ int spaceIdx = key.indexOf(QLatin1String(" "));
+ if (spaceIdx == -1) {
+ continue;
+ }
+
+ QString propertyName = key.mid(0, spaceIdx);
+ QString signature = key.mid(spaceIdx + 1);
+ QString param = keyFile.value(key);
+ QVariant value = valueForKey(key, signature);
+ rcc.fixedProperties.insert(propertyName, value);
+ }
+
+ rcc.allowedProperties = keyFile.valueAsStringList(
+ QLatin1String("allowed"));
+
+ info.rccs.append(rcc);
+
+ rcc.fixedProperties.clear();
+ rcc.allowedProperties.clear();
+ }
+ }
+ }
+
+ return true;
+}
+
+bool ManagerFile::Private::isValid() const
+{
+ return ((keyFile.status() == KeyFile::NoError) && (valid));
+}
+
+bool ManagerFile::Private::hasParameter(const QString &protocol,
+ const QString &paramName) const
+{
+ ParamSpecList paramSpecList = protocolsMap[protocol].params;
+ foreach (const ParamSpec &paramSpec, paramSpecList) {
+ if (paramSpec.name == paramName) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ParamSpec *ManagerFile::Private::getParameter(const QString &protocol,
+ const QString &paramName)
+{
+ ParamSpecList &paramSpecList = protocolsMap[protocol].params;
+ for (int i = 0; i < paramSpecList.size(); ++i) {
+ ParamSpec &paramSpec = paramSpecList[i];
+ if (paramSpec.name == paramName) {
+ return &paramSpec;
+ }
+ }
+ return NULL;
+}
+
+QStringList ManagerFile::Private::protocols() const
+{
+ return protocolsMap.keys();
+}
+
+ParamSpecList ManagerFile::Private::parameters(const QString &protocol) const
+{
+ return protocolsMap.value(protocol).params;
+}
+
+QVariant ManagerFile::Private::valueForKey(const QString &param,
+ const QString &dbusSignature)
+{
+ QString value = keyFile.rawValue(param);
+ return parseValueWithDBusSignature(value, dbusSignature);
+}
+
+
+/**
+ * \class ManagerFile
+ * \ingroup utils
+ * \headerfile TelepathyQt4/manager-file.h <TelepathyQt4/ManagerFile>
+ *
+ * \brief The ManagerFile class provides an easy way to read Telepathy manager
+ * files according to the \telepathy_spec.
+ *
+ * \todo Consider making this private, because the ConnectionManager and Account classes provide a
+ * nice view to the data parsed by this class anyway (fd.o #41655).
+ */
+
+/**
+ * Create a ManagerFile object used to read .manager compliant files.
+ */
+ManagerFile::ManagerFile()
+ : mPriv(new Private())
+{
+}
+
+/**
+ * Create a ManagerFile object used to read .manager compliant files.
+ *
+ * \param cmName Name of the connection manager to read the file for.
+ */
+ManagerFile::ManagerFile(const QString &cmName)
+ : mPriv(new Private(cmName))
+{
+}
+
+/**
+ * Create a ManagerFile object used to read .manager compliant files.
+ */
+ManagerFile::ManagerFile(const ManagerFile &other)
+ : mPriv(new Private())
+{
+ mPriv->cmName = other.mPriv->cmName;
+ mPriv->keyFile = other.mPriv->keyFile;
+ mPriv->protocolsMap = other.mPriv->protocolsMap;
+ mPriv->valid = other.mPriv->valid;
+}
+
+/**
+ * Class destructor.
+ */
+ManagerFile::~ManagerFile()
+{
+ delete mPriv;
+}
+
+ManagerFile &ManagerFile::operator=(const ManagerFile &other)
+{
+ mPriv->cmName = other.mPriv->cmName;
+ mPriv->keyFile = other.mPriv->keyFile;
+ mPriv->protocolsMap = other.mPriv->protocolsMap;
+ mPriv->valid = other.mPriv->valid;
+ return *this;
+}
+
+/**
+ * Check whether or not a ManagerFile object is valid. If the file for the
+ * specified connection manager cannot be found it will be considered invalid.
+ *
+ * \return true if valid, false otherwise.
+ */
+bool ManagerFile::isValid() const
+{
+ return mPriv->isValid();
+}
+
+/**
+ * Return a list of all protocols defined in the manager file.
+ *
+ * \return List of all protocols defined in the file.
+ */
+QStringList ManagerFile::protocols() const
+{
+ return mPriv->protocols();
+}
+
+/**
+ * Return a list of parameters for the given \a protocol.
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return List of ParamSpec of a specific protocol defined in the file, or an
+ * empty list if the protocol is not defined.
+ */
+ParamSpecList ManagerFile::parameters(const QString &protocol) const
+{
+ return mPriv->parameters(protocol);
+}
+
+/**
+ * Return the name of the most common vCard field used for the given \a protocol's
+ * contact identifiers, normalized to lower case.
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return The most common vCard field used for the given protocol's contact
+ * identifiers, or an empty string if there is no such field or the
+ * protocol is not defined.
+ */
+QString ManagerFile::vcardField(const QString &protocol) const
+{
+ return mPriv->protocolsMap.value(protocol).vcardField;
+}
+
+/**
+ * Return the English-language name of the given \a protocol, such as "AIM" or "Yahoo!".
+ *
+ * The name can be used as a fallback if an application doesn't have a localized name for the
+ * protocol.
+ *
+ * If the manager file doesn't specify the english name, it is inferred from the protocol name, such
+ * that for example "google-talk" becomes "Google Talk", but "local-xmpp" becomes "Local Xmpp".
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return An English-language name for the given \a protocol.
+ */
+QString ManagerFile::englishName(const QString &protocol) const
+{
+ return mPriv->protocolsMap.value(protocol).englishName;
+}
+
+/**
+ * Return the name of an icon for the given \a protocol in the system's icon
+ * theme, such as "im-msn".
+ *
+ * If the manager file doesn't specify the icon name, "im-<protocolname>" is assumed.
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return The likely name of an icon for the given \a protocol.
+ */
+QString ManagerFile::iconName(const QString &protocol) const
+{
+ return mPriv->protocolsMap.value(protocol).iconName;
+}
+
+/**
+ * Return a list of channel classes which might be requestable from a connection
+ * to the given \a protocol.
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return A list of channel classes which might be requestable from a
+ * connection to the given \a protocol or a default constructed
+ * RequestableChannelClassList instance if the protocol is not defined.
+ */
+RequestableChannelClassList ManagerFile::requestableChannelClasses(
+ const QString &protocol) const
+{
+ return mPriv->protocolsMap.value(protocol).rccs;
+}
+
+/**
+ * Return a list of PresenceSpec representing the possible presence statuses
+ * from a connection to the given \a protocol.
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return A list of PresenceSpec representing the possible presence statuses
+ * from a connection to the given \a protocol or an empty list
+ * if the protocol is not defined.
+ */
+PresenceSpecList ManagerFile::allowedPresenceStatuses(const QString &protocol) const
+{
+ return mPriv->protocolsMap.value(protocol).statuses;
+}
+
+/**
+ * Return the requirements (size limits, supported MIME types, etc)
+ * for avatars used on the given \a protocol.
+ *
+ * \param protocol Name of the protocol to look for.
+ * \return The requirements for avatars used on the given \a protocol or an invalid
+ * AvatarSpec if the protocol is not defined.
+ */
+AvatarSpec ManagerFile::avatarRequirements(const QString &protocol) const
+{
+ return mPriv->protocolsMap.value(protocol).avatarRequirements;
+}
+
+QVariant::Type ManagerFile::variantTypeFromDBusSignature(const QString &signature)
+{
+ QVariant::Type type;
+ if (signature == QLatin1String("b")) {
+ type = QVariant::Bool;
+ } else if (signature == QLatin1String("n") || signature == QLatin1String("i")) {
+ type = QVariant::Int;
+ } else if (signature == QLatin1String("q") || signature == QLatin1String("u")) {
+ type = QVariant::UInt;
+ } else if (signature == QLatin1String("x")) {
+ type = QVariant::LongLong;
+ } else if (signature == QLatin1String("t")) {
+ type = QVariant::ULongLong;
+ } else if (signature == QLatin1String("d")) {
+ type = QVariant::Double;
+ } else if (signature == QLatin1String("as")) {
+ type = QVariant::StringList;
+ } else if (signature == QLatin1String("s") || signature == QLatin1String("o")) {
+ type = QVariant::String;
+ } else {
+ type = QVariant::Invalid;
+ }
+
+ return type;
+}
+
+QVariant ManagerFile::parseValueWithDBusSignature(const QString &value,
+ const QString &dbusSignature)
+{
+ QVariant::Type type = variantTypeFromDBusSignature(dbusSignature);
+
+ if (type == QVariant::Invalid) {
+ return QVariant(type);
+ }
+
+ switch (type) {
+ case QVariant::Bool:
+ {
+ if (value.toLower() == QLatin1String("true") ||
+ value == QLatin1String("1")) {
+ return QVariant(true);
+ } else {
+ return QVariant(false);
+ }
+ break;
+ }
+ case QVariant::Int:
+ return QVariant(value.toInt());
+ case QVariant::UInt:
+ return QVariant(value.toUInt());
+ case QVariant::LongLong:
+ return QVariant(value.toLongLong());
+ case QVariant::ULongLong:
+ return QVariant(value.toULongLong());
+ case QVariant::Double:
+ return QVariant(value.toDouble());
+ case QVariant::StringList:
+ {
+ QStringList list;
+ QByteArray rawValue = value.toAscii();
+ if (KeyFile::unescapeStringList(rawValue, 0, rawValue.size(), list)) {
+ return QVariant(list);
+ } else {
+ return QVariant(QVariant::Invalid);
+ }
+ }
+ default:
+ break;
+ }
+ return QVariant(value);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/manager-file.h b/qt4/TelepathyQt4/manager-file.h
new file mode 100644
index 000000000..3f67350e8
--- /dev/null
+++ b/qt4/TelepathyQt4/manager-file.h
@@ -0,0 +1,78 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_manager_file_h_HEADER_GUARD_
+#define _TelepathyQt4_manager_file_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/AvatarSpec>
+#include <TelepathyQt4/PresenceSpec>
+#include <TelepathyQt4/Types>
+
+#include <QMetaType>
+#include <QVariant>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT ManagerFile
+{
+public:
+ ManagerFile();
+ ManagerFile(const ManagerFile &other);
+ ManagerFile(const QString &cmName);
+ ~ManagerFile();
+
+ ManagerFile &operator=(const ManagerFile &other);
+
+ QString cmName() const;
+
+ bool isValid() const;
+ QStringList protocols() const;
+ ParamSpecList parameters(const QString &protocol) const;
+ QString vcardField(const QString &protocol) const;
+ QString englishName(const QString &protocol) const;
+ QString iconName(const QString &protocol) const;
+ RequestableChannelClassList requestableChannelClasses(
+ const QString &protocol) const;
+ PresenceSpecList allowedPresenceStatuses(const QString &protocol) const;
+ AvatarSpec avatarRequirements(const QString &protocol) const;
+
+ static QVariant::Type variantTypeFromDBusSignature(
+ const QString &dbusSignature);
+ static QVariant parseValueWithDBusSignature(const QString &value,
+ const QString &dbusSignature);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+Q_DECLARE_METATYPE(Tp::ManagerFile);
+
+#endif
diff --git a/qt4/TelepathyQt4/media-session-handler.cpp b/qt4/TelepathyQt4/media-session-handler.cpp
new file mode 100644
index 000000000..18dfb5381
--- /dev/null
+++ b/qt4/TelepathyQt4/media-session-handler.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/MediaSessionHandler>
+
+#include "TelepathyQt4/_gen/cli-media-session-handler-body.hpp"
+#include "TelepathyQt4/_gen/cli-media-session-handler.moc.hpp"
diff --git a/qt4/TelepathyQt4/media-session-handler.h b/qt4/TelepathyQt4/media-session-handler.h
new file mode 100644
index 000000000..b2c789ef8
--- /dev/null
+++ b/qt4/TelepathyQt4/media-session-handler.h
@@ -0,0 +1,50 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_media_session_handler_h_HEADER_GUARD_
+#define _TelepathyQt4_media_session_handler_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+/**
+ * \addtogroup clientsideproxies Client-side proxies
+ *
+ * Proxy objects representing remote service objects accessed via D-Bus.
+ *
+ * In addition to providing direct access to methods, signals and properties
+ * exported by the remote objects, some of these proxies offer features like
+ * automatic inspection of remote object capabilities, property tracking,
+ * backwards compatibility helpers for older services and other utilities.
+ */
+
+/**
+ * \defgroup clientmsesh Media session handler proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy MediaSessionHandler objects.
+ */
+
+#include <TelepathyQt4/_gen/cli-media-session-handler.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/media-session-handler.xml b/qt4/TelepathyQt4/media-session-handler.xml
new file mode 100644
index 000000000..8b051cd26
--- /dev/null
+++ b/qt4/TelepathyQt4/media-session-handler.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Media session handler</tp:title>
+
+<xi:include href="../spec/Media_Session_Handler.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/media-stream-handler.cpp b/qt4/TelepathyQt4/media-stream-handler.cpp
new file mode 100644
index 000000000..dbfedb54a
--- /dev/null
+++ b/qt4/TelepathyQt4/media-stream-handler.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/MediaStreamHandler>
+
+#include "TelepathyQt4/_gen/cli-media-stream-handler-body.hpp"
+#include "TelepathyQt4/_gen/cli-media-stream-handler.moc.hpp"
diff --git a/qt4/TelepathyQt4/media-stream-handler.h b/qt4/TelepathyQt4/media-stream-handler.h
new file mode 100644
index 000000000..8833a4420
--- /dev/null
+++ b/qt4/TelepathyQt4/media-stream-handler.h
@@ -0,0 +1,50 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_media_stream_handler_h_HEADER_GUARD_
+#define _TelepathyQt4_media_stream_handler_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+/**
+ * \addtogroup clientsideproxies Client-side proxies
+ *
+ * Proxy objects representing remote service objects accessed via D-Bus.
+ *
+ * In addition to providing direct access to methods, signals and properties
+ * exported by the remote objects, some of these proxies offer features like
+ * automatic inspection of remote object capabilities, property tracking,
+ * backwards compatibility helpers for older services and other utilities.
+ */
+
+/**
+ * \defgroup clientmstrh Media stream handler proxies
+ * \ingroup clientsideproxies
+ *
+ * Proxy objects representing remote Telepathy MediaStreamHandler objects.
+ */
+
+#include <TelepathyQt4/_gen/cli-media-stream-handler.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/media-stream-handler.xml b/qt4/TelepathyQt4/media-stream-handler.xml
new file mode 100644
index 000000000..348d5a686
--- /dev/null
+++ b/qt4/TelepathyQt4/media-stream-handler.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Media stream handler</tp:title>
+
+<xi:include href="../spec/Media_Stream_Handler.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/message-content-part.cpp b/qt4/TelepathyQt4/message-content-part.cpp
new file mode 100644
index 000000000..db051ea3a
--- /dev/null
+++ b/qt4/TelepathyQt4/message-content-part.cpp
@@ -0,0 +1,96 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/MessageContentPart>
+
+#include <QSharedData>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT MessageContentPart::Private : public QSharedData
+{
+ Private(const MessagePart &mp)
+ : mp(mp) {}
+
+ MessagePart mp;
+};
+
+/**
+ * \class MessageContentPart
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/message-content-part.h <TelepathyQt4/MessageContentPart>
+ *
+ * \brief The MessageContentPart class represents a Telepathy message part.
+ */
+
+MessageContentPart::MessageContentPart()
+{
+}
+
+MessageContentPart::MessageContentPart(const MessagePart &mp)
+ : mPriv(new Private(mp))
+{
+}
+
+MessageContentPart::MessageContentPart(const MessageContentPart &other)
+ : mPriv(other.mPriv)
+{
+}
+
+MessageContentPart::~MessageContentPart()
+{
+}
+
+MessageContentPart &MessageContentPart::operator=(const MessageContentPart &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool MessageContentPart::operator==(const MessageContentPart &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return true;
+ }
+ return false;
+ }
+
+ return mPriv->mp == other.mPriv->mp;
+}
+
+MessagePart MessageContentPart::barePart() const
+{
+ return isValid() ? mPriv->mp : MessagePart();
+}
+
+/**
+ * \class MessageContentPartList
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/message-content-part.h <TelepathyQt4/MessageContentPartList>
+ *
+ * \brief The MessageContentPartList class represents a list of
+ * MessageContentPart.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/message-content-part.h b/qt4/TelepathyQt4/message-content-part.h
new file mode 100644
index 000000000..9ffe3fcaa
--- /dev/null
+++ b/qt4/TelepathyQt4/message-content-part.h
@@ -0,0 +1,96 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_message_content_part_h_HEADER_GUARD_
+#define _TelepathyQt4_message_content_part_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT MessageContentPart
+{
+public:
+ MessageContentPart();
+ MessageContentPart(const MessagePart &mp);
+ MessageContentPart(const MessageContentPart &other);
+ ~MessageContentPart();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ MessageContentPart &operator=(const MessageContentPart &other);
+ bool operator==(const MessageContentPart &other) const;
+
+ MessagePart barePart() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT MessageContentPartList :
+ public QList<MessageContentPart>
+{
+public:
+ MessageContentPartList() { }
+ MessageContentPartList(const MessagePart &mp)
+ {
+ append(MessageContentPart(mp));
+ }
+ MessageContentPartList(const MessagePartList &mps)
+ {
+ Q_FOREACH (const MessagePart &mp, mps) {
+ append(MessageContentPart(mp));
+ }
+ }
+ MessageContentPartList(const MessageContentPart &mcp)
+ {
+ append(mcp);
+ }
+ MessageContentPartList(const QList<MessageContentPart> &other)
+ : QList<MessageContentPart>(other)
+ {
+ }
+
+ MessagePartList bareParts() const
+ {
+ MessagePartList list;
+ Q_FOREACH (const MessageContentPart &mcp, *this) {
+ list.append(mcp.barePart());
+ }
+ return list;
+ }
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::MessageContentPart);
+Q_DECLARE_METATYPE(Tp::MessageContentPartList);
+
+#endif
diff --git a/qt4/TelepathyQt4/message.cpp b/qt4/TelepathyQt4/message.cpp
new file mode 100644
index 000000000..5680862ae
--- /dev/null
+++ b/qt4/TelepathyQt4/message.cpp
@@ -0,0 +1,911 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/ReceivedMessage>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/TextChannel>
+
+#include <QDateTime>
+#include <QPointer>
+#include <QSet>
+
+namespace Tp
+{
+
+namespace
+{
+
+QVariant valueFromPart(const MessagePartList &parts, uint index, const char *key)
+{
+ return parts.at(index).value(QLatin1String(key)).variant();
+}
+
+uint uintOrZeroFromPart(const MessagePartList &parts, uint index, const char *key)
+{
+ return valueFromPart(parts, index, key).toUInt();
+}
+
+QString stringOrEmptyFromPart(const MessagePartList &parts, uint index, const char *key)
+{
+ QString s = valueFromPart(parts, index, key).toString();
+ if (s.isNull()) {
+ s = QLatin1String("");
+ }
+ return s;
+}
+
+bool booleanFromPart(const MessagePartList &parts, uint index, const char *key,
+ bool assumeIfAbsent)
+{
+ QVariant v = valueFromPart(parts, index, key);
+ if (v.isValid() && v.type() == QVariant::Bool) {
+ return v.toBool();
+ }
+ return assumeIfAbsent;
+}
+
+MessagePartList partsFromPart(const MessagePartList &parts, uint index, const char *key)
+{
+ return qdbus_cast<MessagePartList>(valueFromPart(parts, index, key));
+}
+
+bool partContains(const MessagePartList &parts, uint index, const char *key)
+{
+ return parts.at(index).contains(QLatin1String(key));
+}
+
+}
+
+struct TELEPATHY_QT4_NO_EXPORT Message::Private : public QSharedData
+{
+ Private(const MessagePartList &parts);
+ ~Private();
+
+ uint senderHandle() const;
+ QString senderId() const;
+ uint pendingId() const;
+ void clearSenderHandle();
+
+ MessagePartList parts;
+
+ // if the Text interface says "non-text" we still only have the text,
+ // because the interface can't tell us anything else...
+ bool forceNonText;
+
+ // for received messages only
+ QWeakPointer<TextChannel> textChannel;
+ ContactPtr sender;
+};
+
+Message::Private::Private(const MessagePartList &parts)
+ : parts(parts),
+ forceNonText(false),
+ sender(0)
+{
+}
+
+Message::Private::~Private()
+{
+}
+
+inline uint Message::Private::senderHandle() const
+{
+ return uintOrZeroFromPart(parts, 0, "message-sender");
+}
+
+inline QString Message::Private::senderId() const
+{
+ return stringOrEmptyFromPart(parts, 0, "message-sender-id");
+}
+
+inline uint Message::Private::pendingId() const
+{
+ return uintOrZeroFromPart(parts, 0, "pending-message-id");
+}
+
+void Message::Private::clearSenderHandle()
+{
+ parts[0].remove(QLatin1String("message-sender"));
+}
+
+/**
+ * \class Message
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/message.h <TelepathyQt4/Message>
+ *
+ * \brief The Message class represents a Telepathy message in a TextChannel.
+ *
+ * This class is implicitly shared, like QString.
+ */
+
+/**
+ * \internal Default constructor.
+ */
+Message::Message()
+ : mPriv(new Private(MessagePartList()))
+{
+}
+
+/**
+ * Construct a new Message object.
+ *
+ * \param parts The parts of a message as defined by the \telepathy_spec.
+ * This list must have length at least 1.
+ */
+Message::Message(const MessagePartList &parts)
+ : mPriv(new Private(parts))
+{
+ Q_ASSERT(parts.size() > 0);
+}
+
+/**
+ * Construct a new Message object.
+ *
+ * \param timestamp The time the message was sent.
+ * \param type The message type.
+ * \param text The message body.
+ */
+Message::Message(uint timestamp, uint type, const QString &text)
+ : mPriv(new Private(MessagePartList() << MessagePart() << MessagePart()))
+{
+ mPriv->parts[0].insert(QLatin1String("message-sent"),
+ QDBusVariant(static_cast<qlonglong>(timestamp)));
+ mPriv->parts[0].insert(QLatin1String("message-type"),
+ QDBusVariant(type));
+
+ mPriv->parts[1].insert(QLatin1String("content-type"),
+ QDBusVariant(QLatin1String("text/plain")));
+ mPriv->parts[1].insert(QLatin1String("content"), QDBusVariant(text));
+}
+
+/**
+ * Construct a new Message object.
+ *
+ * \param type The message type.
+ * \param text The message body.
+ */
+Message::Message(ChannelTextMessageType type, const QString &text)
+ : mPriv(new Private(MessagePartList() << MessagePart() << MessagePart()))
+{
+ mPriv->parts[0].insert(QLatin1String("message-type"),
+ QDBusVariant(static_cast<uint>(type)));
+
+ mPriv->parts[1].insert(QLatin1String("content-type"),
+ QDBusVariant(QLatin1String("text/plain")));
+ mPriv->parts[1].insert(QLatin1String("content"), QDBusVariant(text));
+}
+
+/**
+ * Copy constructor.
+ */
+Message::Message(const Message &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Assignment operator.
+ */
+Message &Message::operator=(const Message &other)
+{
+ if (this != &other) {
+ mPriv = other.mPriv;
+ }
+
+ return *this;
+}
+
+/**
+ * Equality operator.
+ */
+bool Message::operator==(const Message &other) const
+{
+ return this->mPriv == other.mPriv;
+}
+
+/**
+ * Class destructor.
+ */
+Message::~Message()
+{
+}
+
+/**
+ * Return the time the message was sent, or QDateTime() if that time is
+ * unknown.
+ *
+ * \return The timestamp as QDateTime.
+ */
+QDateTime Message::sent() const
+{
+ // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690
+ uint stamp = valueFromPart(mPriv->parts, 0, "message-sent").toUInt();
+ if (stamp != 0) {
+ return QDateTime::fromTime_t(stamp);
+ } else {
+ return QDateTime();
+ }
+}
+
+/**
+ * Return the type of this message, or #ChannelTextMessageTypeNormal
+ * if the type is not recognised.
+ *
+ * \return The type as #ChannelTextMessageType.
+ */
+ChannelTextMessageType Message::messageType() const
+{
+ uint raw = valueFromPart(mPriv->parts, 0, "message-type").toUInt();
+
+ if (raw < static_cast<uint>(NUM_CHANNEL_TEXT_MESSAGE_TYPES)) {
+ return ChannelTextMessageType(raw);
+ } else {
+ return ChannelTextMessageTypeNormal;
+ }
+}
+
+/**
+ * Return whether this message was truncated during delivery.
+ *
+ * \return \c true if truncated, \c false otherwise.
+ */
+bool Message::isTruncated() const
+{
+ for (int i = 1; i < size(); i++) {
+ if (booleanFromPart(mPriv->parts, i, "truncated", false)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether this message contains parts not representable as plain
+ * text.
+ *
+ * \return \c true if it cannot completely be represented as plain text, \c false
+ * otherwise.
+ */
+bool Message::hasNonTextContent() const
+{
+ if (mPriv->forceNonText || size() <= 1 || isSpecificToDBusInterface()) {
+ return true;
+ }
+
+ QSet<QString> texts;
+ QSet<QString> textNeeded;
+
+ for (int i = 1; i < size(); i++) {
+ QString altGroup = stringOrEmptyFromPart(mPriv->parts, i, "alternative");
+ QString contentType = stringOrEmptyFromPart(mPriv->parts, i, "content-type");
+
+ if (contentType == QLatin1String("text/plain")) {
+ if (!altGroup.isEmpty()) {
+ // we can use this as an alternative for a non-text part
+ // with the same altGroup
+ texts << altGroup;
+ }
+ } else {
+ QString alt = stringOrEmptyFromPart(mPriv->parts, i, "alternative");
+ if (altGroup.isEmpty()) {
+ // we can't possibly rescue this part by using a text/plain
+ // alternative, because it's not in any alternative group
+ return true;
+ } else {
+ // maybe we'll find a text/plain alternative for this
+ textNeeded << altGroup;
+ }
+ }
+ }
+
+ textNeeded -= texts;
+ return !textNeeded.isEmpty();
+}
+
+/**
+ * Return the unique token identifying this message (e.g. the id attribute
+ * for XMPP messages), or an empty string if there is no suitable token.
+ *
+ * \return The non-empty message identifier, or an empty string if none.
+ */
+QString Message::messageToken() const
+{
+ return stringOrEmptyFromPart(mPriv->parts, 0, "message-token");
+}
+
+/**
+ * Return whether this message is specific to a D-Bus interface. This is
+ * \c false in almost all cases.
+ *
+ * If this function returns \c true, the message is specific to the interface
+ * indicated by dbusInterface(). Clients that don't understand that interface
+ * should not display the message. However, if the client would acknowledge
+ * an ordinary message, it must also acknowledge this interface-specific
+ * message.
+ *
+ * \return \c true if dbusInterface() would return a non-empty string, \c false otherwise.
+ * \sa dbusInterface()
+ */
+bool Message::isSpecificToDBusInterface() const
+{
+ return !dbusInterface().isEmpty();
+}
+
+/**
+ * Return the D-Bus interface to which this message is specific, or an
+ * empty string for normal messages.
+ *
+ * \return The D-Bus interface name, or an empty string.
+ * \sa isSpecificToDBusInterface()
+ */
+QString Message::dbusInterface() const
+{
+ return stringOrEmptyFromPart(mPriv->parts, 0, "interface");
+}
+
+/**
+ * Return the message body containing all "text/plain" parts.
+ *
+ * \return The body text.
+ */
+QString Message::text() const
+{
+ // Alternative-groups for which we've already emitted an alternative
+ QSet<QString> altGroupsUsed;
+ QString text;
+
+ for (int i = 1; i < size(); i++) {
+ QString altGroup = stringOrEmptyFromPart(mPriv->parts, i, "alternative");
+ QString contentType = stringOrEmptyFromPart(mPriv->parts, i, "content-type");
+
+ if (contentType == QLatin1String("text/plain")) {
+ if (!altGroup.isEmpty()) {
+ if (altGroupsUsed.contains(altGroup)) {
+ continue;
+ } else {
+ altGroupsUsed << altGroup;
+ }
+ }
+
+ QVariant content = valueFromPart(mPriv->parts, i, "content");
+ if (content.type() == QVariant::String) {
+ text += content.toString();
+ } else {
+ // O RLY?
+ debug() << "allegedly text/plain part wasn't";
+ }
+ }
+ }
+
+ return text;
+}
+
+/**
+ * Return the message's header part, as defined by the \telepathy_spec.
+ *
+ * This is provided for advanced clients that need to access
+ * additional information not available through the normal Message API.
+ *
+ * \return The header as a MessagePart object. The same thing as part(0).
+ */
+MessagePart Message::header() const
+{
+ return part(0);
+}
+
+/**
+ * Return the number of parts in this message.
+ *
+ * \return 1 greater than the largest valid argument to part().
+ * \sa part(), parts()
+ */
+int Message::size() const
+{
+ return mPriv->parts.size();
+}
+
+/**
+ * Return the message's part for \a index, as defined by the \telepathy_spec.
+ *
+ * This is provided for advanced clients that need to access
+ * additional information not available through the normal Message API.
+ *
+ * \param index The part to access, which must be strictly less than size();
+ * part number 0 is the header, parts numbered 1 or greater
+ * are the body of the message.
+ * \return A MessagePart object.
+ */
+MessagePart Message::part(uint index) const
+{
+ return mPriv->parts.at(index);
+}
+
+/**
+ * Return the list of message parts forming this message.
+ *
+ * \return The list of MessagePart objects.
+ */
+MessagePartList Message::parts() const
+{
+ return mPriv->parts;
+}
+
+/**
+ * \class ReceivedMessage
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/message.h <TelepathyQt4/ReceivedMessage>
+ *
+ * \brief The ReceivedMessage class is a subclass of Message, representing a
+ * received message only.
+ *
+ * It contains additional information that's generally only
+ * available on received messages.
+ */
+
+/**
+ * \class ReceivedMessage::DeliveryDetails
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/message.h <TelepathyQt4/ReceivedMessage>
+ *
+ * \brief The ReceivedMessage::DeliveryDetails class represents the details of a delivery report.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT ReceivedMessage::DeliveryDetails::Private : public QSharedData
+{
+ Private(const MessagePartList &parts)
+ : parts(parts)
+ {
+ }
+
+ MessagePartList parts;
+};
+
+/**
+ * Default constructor.
+ */
+ReceivedMessage::DeliveryDetails::DeliveryDetails()
+{
+}
+
+/**
+ * Copy constructor.
+ */
+ReceivedMessage::DeliveryDetails::DeliveryDetails(const DeliveryDetails &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Construct a new ReceivedMessage::DeliveryDetails object.
+ *
+ * \param The message parts.
+ */
+ReceivedMessage::DeliveryDetails::DeliveryDetails(const MessagePartList &parts)
+ : mPriv(new Private(parts))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ReceivedMessage::DeliveryDetails::~DeliveryDetails()
+{
+}
+
+/**
+ * Assignment operator.
+ */
+ReceivedMessage::DeliveryDetails &ReceivedMessage::DeliveryDetails::operator=(
+ const DeliveryDetails &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+/**
+ * Return the delivery status of a message.
+ *
+ * \return The delivery status as #DeliveryStatus.
+ */
+DeliveryStatus ReceivedMessage::DeliveryDetails::status() const
+{
+ if (!isValid()) {
+ return DeliveryStatusUnknown;
+ }
+ return static_cast<DeliveryStatus>(uintOrZeroFromPart(mPriv->parts, 0, "delivery-status"));
+}
+
+/**
+ * Return whether this delivery report contains an identifier for the message to which it
+ * refers.
+ *
+ * \return \c true if an original message token is known, \c false otherwise.
+ * \sa originalToken()
+ */
+bool ReceivedMessage::DeliveryDetails::hasOriginalToken() const
+{
+ if (!isValid()) {
+ return false;
+ }
+ return partContains(mPriv->parts, 0, "delivery-token");
+}
+
+/**
+ * Return an identifier for the message to which this delivery report refers, or an empty string if
+ * hasOriginalToken() returns \c false.
+ *
+ * Clients may match this against the token produced by the TextChannel::send() method and
+ * TextChannel::messageSent() signal. A status report with no token could match any sent message,
+ * and a sent message with an empty token could match any status report.
+ * If multiple sent messages match, clients should use some reasonable heuristic.
+ *
+ * \return The message token if hasOriginalToken() returns \c true, an empty string otherwise.
+ * \sa hasOriginalToken().
+ */
+QString ReceivedMessage::DeliveryDetails::originalToken() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+ return stringOrEmptyFromPart(mPriv->parts, 0, "delivery-token");
+}
+
+/**
+ * Return whether the delivery of the message this delivery report refers to, failed.
+ *
+ * \return \c true if the message delivery failed, \c false otherwise.
+ * \sa error()
+ */
+bool ReceivedMessage::DeliveryDetails::isError() const
+{
+ if (!isValid()) {
+ return false;
+ }
+ DeliveryStatus st(status());
+ return st == DeliveryStatusTemporarilyFailed || st == DeliveryStatusPermanentlyFailed;
+}
+
+/**
+ * Return the reason for the delivery failure if known.
+ *
+ * \return The reason as #ChannelTextSendError.
+ * \sa isError()
+ */
+ChannelTextSendError ReceivedMessage::DeliveryDetails::error() const
+{
+ if (!isValid()) {
+ return ChannelTextSendErrorUnknown;
+ }
+ return static_cast<ChannelTextSendError>(uintOrZeroFromPart(mPriv->parts, 0, "delivery-error"));
+}
+
+/**
+ * Return whether this delivery report contains a debugging information on why the message it refers
+ * to could not be delivered.
+ *
+ * \return \c true if a debugging information is provided, \c false otherwise.
+ * \sa debugMessage()
+ */
+bool ReceivedMessage::DeliveryDetails::hasDebugMessage() const
+{
+ if (!isValid()) {
+ return false;
+ }
+ return partContains(mPriv->parts, 0, "delivery-error-message");
+}
+
+/**
+ * Return the debugging information on why the message this delivery report refers to could not be
+ * delivered.
+ *
+ * \return The debug string.
+ * \sa hasDebugMessage()
+ */
+QString ReceivedMessage::DeliveryDetails::debugMessage() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+ return stringOrEmptyFromPart(mPriv->parts, 0, "delivery-error-message");
+}
+
+/**
+ * Return the reason for the delivery failure if known, specified as a
+ * (possibly implementation-specific) D-Bus error.
+ *
+ * \return The D-Bus error string representing the error.
+ */
+QString ReceivedMessage::DeliveryDetails::dbusError() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+ QString ret = stringOrEmptyFromPart(mPriv->parts, 0, "delivery-dbus-error");
+ if (ret.isEmpty()) {
+ switch (error()) {
+ case ChannelTextSendErrorOffline:
+ ret = TP_QT4_ERROR_OFFLINE;
+ break;
+ case ChannelTextSendErrorInvalidContact:
+ ret = TP_QT4_ERROR_DOES_NOT_EXIST;
+ break;
+ case ChannelTextSendErrorPermissionDenied:
+ ret = TP_QT4_ERROR_PERMISSION_DENIED;
+ break;
+ case ChannelTextSendErrorTooLong:
+ ret = TP_QT4_ERROR_INVALID_ARGUMENT;
+ break;
+ case ChannelTextSendErrorNotImplemented:
+ ret = TP_QT4_ERROR_NOT_IMPLEMENTED;
+ break;
+ default:
+ ret = TP_QT4_ERROR_NOT_AVAILABLE;
+ }
+ }
+ return ret;
+}
+
+/**
+ * Return whether the message content for the message this delivery report refers to is known.
+ *
+ * \return \c true if the original message content is known, \c false otherwise.
+ * \sa echoedMessage()
+ */
+bool ReceivedMessage::DeliveryDetails::hasEchoedMessage() const
+{
+ if (!isValid()) {
+ return false;
+ }
+ return partContains(mPriv->parts, 0, "delivery-echo");
+}
+
+/**
+ * Return the Message object for the message this delivery report refers to, omitted if the message
+ * is unknown.
+ *
+ * <div class='rationale'>
+ * <h5>Rationale:</h5>
+ * <div>
+ * Some protocols, like XMPP, echo the failing message back to the sender. This is sometimes the
+ * only way to match it against the sent message, so we include it here.
+ * </div>
+ * </div>
+ *
+ * \return The Message object, or an empty Message object if hasEchoedMessage()
+ * returns \c false.
+ * \sa hasEchoedMessage()
+ */
+Message ReceivedMessage::DeliveryDetails::echoedMessage() const
+{
+ if (!isValid()) {
+ return Message();
+ }
+ return Message(partsFromPart(mPriv->parts, 0, "delivery-echo"));
+}
+
+/**
+ * \internal Default constructor.
+ */
+ReceivedMessage::ReceivedMessage()
+{
+}
+
+/**
+ * Construct a new ReceivedMessage object.
+ *
+ * \param parts The parts of a message as defined by the \telepathy_spec.
+ * This list must have length at least 1.
+ * \param channel The channel owning this message.
+ */
+ReceivedMessage::ReceivedMessage(const MessagePartList &parts,
+ const TextChannelPtr &channel)
+ : Message(parts)
+{
+ if (!mPriv->parts[0].contains(QLatin1String("message-received"))) {
+ mPriv->parts[0].insert(QLatin1String("message-received"),
+ QDBusVariant(static_cast<qlonglong>(
+ QDateTime::currentDateTime().toTime_t())));
+ }
+ mPriv->textChannel = QWeakPointer<TextChannel>(channel.data());
+}
+
+/**
+ * Copy constructor.
+ */
+ReceivedMessage::ReceivedMessage(const ReceivedMessage &other)
+ : Message(other)
+{
+}
+
+/**
+ * Assignment operator.
+ */
+ReceivedMessage &ReceivedMessage::operator=(const ReceivedMessage &other)
+{
+ if (this != &other) {
+ mPriv = other.mPriv;
+ }
+
+ return *this;
+}
+
+/**
+ * Class destructor.
+ */
+ReceivedMessage::~ReceivedMessage()
+{
+}
+
+/**
+ * Return the time the message was received.
+ *
+ * \return The timestamp as QDateTime, or QDateTime() if unknown.
+ */
+QDateTime ReceivedMessage::received() const
+{
+ // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690
+ uint stamp = valueFromPart(mPriv->parts, 0, "message-received").toUInt();
+ if (stamp != 0) {
+ return QDateTime::fromTime_t(stamp);
+ } else {
+ return QDateTime();
+ }
+}
+
+/**
+ * Return the contact who sent the message.
+ *
+ * \return A pointer to the Contact object.
+ * \sa senderNickname()
+ */
+ContactPtr ReceivedMessage::sender() const
+{
+ return mPriv->sender;
+}
+
+/**
+ * Return the nickname chosen by the sender of the message, which can be different for each
+ * message in a conversation.
+ *
+ * \return The nickname.
+ * \sa sender()
+ */
+QString ReceivedMessage::senderNickname() const
+{
+ QString ret = stringOrEmptyFromPart(mPriv->parts, 0, "sender-nickname");
+ if (ret.isEmpty() && mPriv->sender) {
+ ret = mPriv->sender->alias();
+ }
+ return ret;
+}
+
+/**
+ * If this message replaces a previous message, return the value of
+ * messageToken() for that previous message. Otherwise, return an empty string.
+ *
+ * For instance, a user interface could replace the superseded
+ * message with this message, or grey out the superseded message.
+ *
+ * \return The message token of the superseded message or an empty string if none.
+ */
+QString ReceivedMessage::supersededToken() const
+{
+ return stringOrEmptyFromPart(mPriv->parts, 0, "supersedes");
+}
+
+/**
+ * Return whether the incoming message was part of a replay of message
+ * history.
+ *
+ * If \c true, loggers can use this to improve their heuristics for elimination
+ * of duplicate messages (a simple, correct implementation would be to avoid
+ * logging any message that has this flag).
+ *
+ * \return \c true if the scrollback flag is set, \c false otherwise.
+ */
+bool ReceivedMessage::isScrollback() const
+{
+ return booleanFromPart(mPriv->parts, 0, "scrollback", false);
+}
+
+/**
+ * Return whether the incoming message was seen in a previous channel during
+ * the lifetime of the connection, but was not acknowledged before that
+ * channel closed, causing the channel in which it now appears to open.
+ *
+ * If \c true, loggers should not log this message again.
+ *
+ * \return \c true if the rescued flag is set, \c false otherwise.
+ */
+bool ReceivedMessage::isRescued() const
+{
+ return booleanFromPart(mPriv->parts, 0, "rescued", false);
+}
+
+/**
+ * Return whether the incoming message is a delivery report.
+ *
+ * \return \c true if a delivery report, \c false otherwise.
+ * \sa deliveryDetails()
+ */
+bool ReceivedMessage::isDeliveryReport() const
+{
+ return messageType() == ChannelTextMessageTypeDeliveryReport;
+}
+
+/**
+ * Return the details of a delivery report.
+ *
+ * This method should only be used if isDeliveryReport() returns \c true.
+ *
+ * \return The delivery report as a ReceivedMessage::DeliveryDetails object.
+ * \sa isDeliveryReport()
+ */
+ReceivedMessage::DeliveryDetails ReceivedMessage::deliveryDetails() const
+{
+ return DeliveryDetails(parts());
+}
+
+/**
+ * Return whether this message is from \a channel.
+ *
+ * \return \c true if the message is from \a channel, \c false otherwise.
+ */
+bool ReceivedMessage::isFromChannel(const TextChannelPtr &channel) const
+{
+ return TextChannelPtr(mPriv->textChannel) == channel;
+}
+
+uint ReceivedMessage::pendingId() const
+{
+ return mPriv->pendingId();
+}
+
+uint ReceivedMessage::senderHandle() const
+{
+ return mPriv->senderHandle();
+}
+
+QString ReceivedMessage::senderId() const
+{
+ return mPriv->senderId();
+}
+
+void ReceivedMessage::setForceNonText()
+{
+ mPriv->forceNonText = true;
+}
+
+void ReceivedMessage::clearSenderHandle()
+{
+ mPriv->clearSenderHandle();
+}
+
+void ReceivedMessage::setSender(const ContactPtr &sender)
+{
+ mPriv->sender = sender;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/message.h b/qt4/TelepathyQt4/message.h
new file mode 100644
index 000000000..8324b2429
--- /dev/null
+++ b/qt4/TelepathyQt4/message.h
@@ -0,0 +1,173 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_message_h_HEADER_GUARD_
+#define _TelepathyQt4_message_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <QSharedDataPointer>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Types>
+
+class QDateTime;
+
+namespace Tp
+{
+
+class Contact;
+class TextChannel;
+
+class TELEPATHY_QT4_EXPORT Message
+{
+public:
+ Message(ChannelTextMessageType, const QString &);
+ Message(const Message &other);
+ ~Message();
+
+ Message &operator=(const Message &other);
+ bool operator==(const Message &other) const;
+ inline bool operator!=(const Message &other) const
+ {
+ return !(*this == other);
+ }
+
+ // Convenient access to headers
+
+ QDateTime sent() const;
+
+ ChannelTextMessageType messageType() const;
+
+ bool isTruncated() const;
+
+ bool hasNonTextContent() const;
+
+ QString messageToken() const;
+
+ bool isSpecificToDBusInterface() const;
+ QString dbusInterface() const;
+
+ QString text() const;
+
+ // Direct access to the whole message (header and body)
+
+ MessagePart header() const;
+
+ int size() const;
+ MessagePart part(uint index) const;
+ MessagePartList parts() const;
+
+private:
+ friend class ContactMessenger;
+ friend class ReceivedMessage;
+ friend class TextChannel;
+
+ TELEPATHY_QT4_NO_EXPORT Message();
+ TELEPATHY_QT4_NO_EXPORT Message(const MessagePartList &parts);
+ TELEPATHY_QT4_NO_EXPORT Message(uint, uint, const QString &);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT ReceivedMessage : public Message
+{
+public:
+ class DeliveryDetails
+ {
+ public:
+ DeliveryDetails();
+ DeliveryDetails(const DeliveryDetails &other);
+ ~DeliveryDetails();
+
+ DeliveryDetails &operator=(const DeliveryDetails &other);
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ DeliveryStatus status() const;
+
+ bool hasOriginalToken() const;
+ QString originalToken() const;
+
+ bool isError() const;
+ ChannelTextSendError error() const;
+
+ bool hasDebugMessage() const;
+ QString debugMessage() const;
+
+ QString dbusError() const;
+
+ bool hasEchoedMessage() const;
+ Message echoedMessage() const;
+
+ private:
+ friend class ReceivedMessage;
+
+ TELEPATHY_QT4_NO_EXPORT DeliveryDetails(const MessagePartList &parts);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ ReceivedMessage(const ReceivedMessage &other);
+ ReceivedMessage &operator=(const ReceivedMessage &other);
+ ~ReceivedMessage();
+
+ QDateTime received() const;
+ ContactPtr sender() const;
+ QString senderNickname() const;
+
+ QString supersededToken() const;
+
+ bool isScrollback() const;
+ bool isRescued() const;
+
+ bool isDeliveryReport() const;
+ DeliveryDetails deliveryDetails() const;
+
+ bool isFromChannel(const TextChannelPtr &channel) const;
+
+private:
+ friend class TextChannel;
+
+ TELEPATHY_QT4_NO_EXPORT ReceivedMessage(const MessagePartList &parts,
+ const TextChannelPtr &channel);
+ TELEPATHY_QT4_NO_EXPORT ReceivedMessage();
+
+ TELEPATHY_QT4_NO_EXPORT uint senderHandle() const;
+ TELEPATHY_QT4_NO_EXPORT QString senderId() const;
+ TELEPATHY_QT4_NO_EXPORT uint pendingId() const;
+
+ TELEPATHY_QT4_NO_EXPORT void setForceNonText();
+ TELEPATHY_QT4_NO_EXPORT void clearSenderHandle();
+ TELEPATHY_QT4_NO_EXPORT void setSender(const ContactPtr &sender);
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/method-invocation-context.dox b/qt4/TelepathyQt4/method-invocation-context.dox
new file mode 100644
index 000000000..31975cbb7
--- /dev/null
+++ b/qt4/TelepathyQt4/method-invocation-context.dox
@@ -0,0 +1,41 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::MethodInvocationContext
+ * \ingroup utils
+ * \headerfile TelepathyQt4/method-invocation-context.h <TelepathyQt4/MethodInvocationContext>
+ *
+ * \brief The MethodInvocationContext class provides a way for the service implementation to
+ * respond to method calls.
+ *
+ * The methods setFinished() and setFinishedWithError() can be used to indicate
+ * whether the method call succeeded or failed.
+ *
+ * If neither setFinished() nor setFinishedWithError() is called explicitly,
+ * the method call will be considered to have failed.
+ *
+ " In case an asynchronous operation needs to be performed when implementing a method call
+ * receiving a MethodInvocationContextPtr object, a reference to this object may be kept around
+ * until all asynchronous operations finish, and the appropriate finish method
+ * should be called to indicate whether the method call succeeded or failed later.
+ */
diff --git a/qt4/TelepathyQt4/method-invocation-context.h b/qt4/TelepathyQt4/method-invocation-context.h
new file mode 100644
index 000000000..53082c544
--- /dev/null
+++ b/qt4/TelepathyQt4/method-invocation-context.h
@@ -0,0 +1,192 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_method_invocation_context_h_HEADER_GUARD_
+#define _TelepathyQt4_method_invocation_context_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <QtDBus>
+#include <QtCore>
+
+namespace Tp
+{
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+namespace MethodInvocationContextTypes
+{
+
+struct Nil
+{
+};
+
+template<int Index,
+ typename T1, typename T2, typename T3, typename T4,
+ typename T5, typename T6, typename T7, typename T8>
+struct Select
+{
+ typedef Select<Index - 1, T2, T3, T4, T5, T6, T7, T8, Nil> Next;
+ typedef typename Next::Type Type;
+};
+template<typename T1, typename T2, typename T3, typename T4,
+ typename T5, typename T6, typename T7, typename T8>
+struct Select<0, T1, T2, T3, T4, T5, T6, T7, T8>
+{
+ typedef T1 Type;
+};
+
+template<typename T1, typename T2, typename T3, typename T4,
+ typename T5, typename T6, typename T7, typename T8>
+struct ForEach
+{
+ typedef ForEach<T2, T3, T4, T5, T6, T7, T8, Nil> Next;
+ enum { Total = Next::Total + 1 };
+};
+template<>
+struct ForEach<Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil>
+{
+ enum { Total = 0 };
+};
+
+}
+
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+template<typename T1 = MethodInvocationContextTypes::Nil, typename T2 = MethodInvocationContextTypes::Nil,
+ typename T3 = MethodInvocationContextTypes::Nil, typename T4 = MethodInvocationContextTypes::Nil,
+ typename T5 = MethodInvocationContextTypes::Nil, typename T6 = MethodInvocationContextTypes::Nil,
+ typename T7 = MethodInvocationContextTypes::Nil, typename T8 = MethodInvocationContextTypes::Nil>
+class MethodInvocationContext : public RefCounted
+{
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+ template<int Index>
+ struct Select : MethodInvocationContextTypes::Select<Index, T1, T2, T3, T4, T5, T6, T7, T8>
+ {
+ };
+
+ typedef MethodInvocationContextTypes::ForEach<T1, T2, T3, T4, T5, T6, T7, T8> ForEach;
+ enum { Count = ForEach::Total };
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+public:
+ MethodInvocationContext(const QDBusConnection &bus, const QDBusMessage &message)
+ : mBus(bus), mMessage(message), mFinished(false)
+ {
+ mMessage.setDelayedReply(true);
+ }
+
+ virtual ~MethodInvocationContext()
+ {
+ if (!mFinished) {
+ setFinishedWithError(QString(), QString());
+ }
+ }
+
+ bool isFinished() const { return mFinished; }
+ bool isError() const { return !mErrorName.isEmpty(); }
+ QString errorName() const { return mErrorName; }
+ QString errorMessage() const { return mErrorMessage; }
+
+ void setFinished(const T1 &t1 = T1(), const T2 &t2 = T2(), const T3 &t3 = T3(),
+ const T4 &t4 = T4(), const T5 &t5 = T5(), const T6 &t6 = T6(),
+ const T7 &t7 = T7(), const T8 &t8 = T8())
+ {
+ if (mFinished) {
+ return;
+ }
+
+ mFinished = true;
+
+ setReplyValue(0, qVariantFromValue(t1));
+ setReplyValue(1, qVariantFromValue(t2));
+ setReplyValue(2, qVariantFromValue(t3));
+ setReplyValue(3, qVariantFromValue(t4));
+ setReplyValue(4, qVariantFromValue(t5));
+ setReplyValue(5, qVariantFromValue(t6));
+ setReplyValue(6, qVariantFromValue(t7));
+ setReplyValue(7, qVariantFromValue(t8));
+
+ if (mReply.isEmpty()) {
+ mBus.send(mMessage.createReply());
+ } else {
+ mBus.send(mMessage.createReply(mReply));
+ }
+ onFinished();
+ }
+
+ void setFinishedWithError(const QString &errorName,
+ const QString &errorMessage)
+ {
+ if (mFinished) {
+ return;
+ }
+
+ mFinished = true;
+
+ if (errorName.isEmpty()) {
+ mErrorName = QLatin1String("org.freedesktop.Telepathy.Qt4.ErrorHandlingError");
+ } else {
+ mErrorName = errorName;
+ }
+ mErrorMessage = errorMessage;
+
+ mBus.send(mMessage.createErrorReply(mErrorName, mErrorMessage));
+ onFinished();
+ }
+
+ template<int Index> inline
+ typename Select<Index>::Type argumentAt() const
+ {
+ Q_ASSERT(Index >= 0 && Index < Count);
+ return qdbus_cast<typename Select<Index>::Type>(mReply.value(Index));
+ }
+
+protected:
+ virtual void onFinished() {}
+
+private:
+ Q_DISABLE_COPY(MethodInvocationContext)
+
+ void setReplyValue(int index, const QVariant &value)
+ {
+ if (index >= Count) {
+ return;
+ }
+ mReply.insert(index, value);
+ }
+
+ QDBusConnection mBus;
+ QDBusMessage mMessage;
+ bool mFinished;
+ QList<QVariant> mReply;
+ QString mErrorName;
+ QString mErrorMessage;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::MethodInvocationContextTypes::Nil)
+
+#endif
diff --git a/qt4/TelepathyQt4/not-filter.dox b/qt4/TelepathyQt4/not-filter.dox
new file mode 100644
index 000000000..f67a3b056
--- /dev/null
+++ b/qt4/TelepathyQt4/not-filter.dox
@@ -0,0 +1,32 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::NotFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/not-filter.h <TelepathyQt4/NotFilter>
+ *
+ * \brief The NotFilter class provides a generic filter object to be used
+ * in conjunction of other filters.
+ *
+ * The NotFilter will match if its given filter does not match its criteria.
+ */
diff --git a/qt4/TelepathyQt4/not-filter.h b/qt4/TelepathyQt4/not-filter.h
new file mode 100644
index 000000000..3e4ceae01
--- /dev/null
+++ b/qt4/TelepathyQt4/not-filter.h
@@ -0,0 +1,73 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_not_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_not_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+template <class T>
+class NotFilter : public Filter<T>
+{
+public:
+ static SharedPtr<NotFilter<T> > create(
+ const SharedPtr<const Filter<T> > &filter = SharedPtr<const Filter<T> >())
+ {
+ return SharedPtr<NotFilter<T> >(new NotFilter<T>(filter));
+ }
+
+ inline virtual ~NotFilter() { }
+
+ inline virtual bool isValid() const
+ {
+ return mFilter && mFilter->isValid();
+ }
+
+ inline virtual bool matches(const SharedPtr<T> &t) const
+ {
+ if (!isValid()) {
+ return false;
+ }
+
+ return !mFilter->matches(t);
+ }
+
+ inline SharedPtr<const Filter<T> > filter() const { return mFilter; }
+
+private:
+ NotFilter(const SharedPtr<const Filter<T> > &filter)
+ : Filter<T>(), mFilter(filter) { }
+
+ SharedPtr<const Filter<T> > mFilter;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/object.cpp b/qt4/TelepathyQt4/object.cpp
new file mode 100644
index 000000000..ff50a4125
--- /dev/null
+++ b/qt4/TelepathyQt4/object.cpp
@@ -0,0 +1,65 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Object>
+
+#include "TelepathyQt4/_gen/object.moc.hpp"
+
+namespace Tp
+{
+
+/**
+ * \class Object
+ * \ingroup clientobject
+ * \headerfile TelepathyQt4/object.h <TelepathyQt4/Object>
+ *
+ * \brief The Object class provides an object with property notification.
+ */
+
+/**
+ * Construct a new Object object.
+ */
+Object::Object()
+ : QObject()
+{
+}
+
+/**
+ * Class destructor.
+ */
+Object::~Object()
+{
+}
+
+/**
+ * Notify that a property named \a propertyName changed.
+ *
+ * This method will emit propertyChanged() for \a propertyName.
+ *
+ * \todo Use for more classes beyond Account. Most importantly Contact.
+ */
+void Object::notify(const char *propertyName)
+{
+ emit propertyChanged(QLatin1String(propertyName));
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/object.h b/qt4/TelepathyQt4/object.h
new file mode 100644
index 000000000..191f7d5fd
--- /dev/null
+++ b/qt4/TelepathyQt4/object.h
@@ -0,0 +1,63 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_object_h_HEADER_GUARD_
+#define _TelepathyQt4_object_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/RefCounted>
+
+#include <QObject>
+#include <QString>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT Object : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Object)
+
+public:
+ virtual ~Object();
+
+Q_SIGNALS:
+ void propertyChanged(const QString &propertyName);
+
+protected:
+ Object();
+
+ void notify(const char *propertyName);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/optional-interface-factory.cpp b/qt4/TelepathyQt4/optional-interface-factory.cpp
new file mode 100644
index 000000000..2580bc841
--- /dev/null
+++ b/qt4/TelepathyQt4/optional-interface-factory.cpp
@@ -0,0 +1,178 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/OptionalInterfaceFactory>
+
+#include <TelepathyQt4/AbstractInterface>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <QMap>
+#include <QString>
+
+namespace Tp
+{
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+struct TELEPATHY_QT4_NO_EXPORT OptionalInterfaceCache::Private
+{
+ QObject *proxy;
+ QMap<QString, AbstractInterface*> interfaces;
+
+ Private(QObject *proxy);
+};
+
+OptionalInterfaceCache::Private::Private(QObject *proxy)
+ : proxy(proxy)
+{
+}
+
+/**
+ * Class constructor.
+ */
+OptionalInterfaceCache::OptionalInterfaceCache(QObject *proxy)
+ : mPriv(new Private(proxy))
+{
+}
+
+/**
+ * Class destructor.
+ *
+ * Frees all interface instances constructed by this factory.
+ */
+OptionalInterfaceCache::~OptionalInterfaceCache()
+{
+ delete mPriv;
+}
+
+QObject *OptionalInterfaceCache::proxy() const
+{
+ return mPriv->proxy;
+}
+
+AbstractInterface *OptionalInterfaceCache::getCached(const QString &name) const
+{
+ if (mPriv->interfaces.contains(name)) {
+ return mPriv->interfaces.value(name);
+ } else {
+ return 0;
+ }
+}
+
+void OptionalInterfaceCache::cache(AbstractInterface *interface) const
+{
+ QString name = interface->interface();
+ Q_ASSERT(!mPriv->interfaces.contains(name));
+
+ mPriv->interfaces[name] = interface;
+}
+
+#endif /* ifndef DOXYGEN_SHOULD_SKIP_THIS */
+
+/**
+ * \class OptionalInterfaceFactory
+ * \ingroup clientsideproxies
+ * \headerfile TelepathyQt4/optional-interface-factory.h <TelepathyQt4/OptionalInterfaceFactory>
+ *
+ * \brief The OptionalInterfaceFactory class is a helper class for
+ * high-level D-Bus proxy classes willing to offer access to shared
+ * instances of interface proxies for optional interfaces.
+ *
+ * To use this helper in a subclass of DBusProxy (say, ExampleObject),
+ * ExampleObject should inherit from
+ * OptionalInterfaceFactory<ExampleObject>, and call
+ * OptionalInterfaceFactory(this) in its constructor's initialization list.
+ *
+ * \tparam DBusProxySubclass A subclass of DBusProxy
+ */
+
+/**
+ * \enum OptionalInterfaceFactory::InterfaceSupportedChecking
+ *
+ * Specifies if the interface being supported by the remote object should be
+ * checked by optionalInterface() and the convenience functions for it.
+ *
+ * \sa optionalInterface()
+ */
+
+/**
+ * \var OptionalInterfaceFactory::InterfaceSupportedChecking OptionalInterfaceFactory::CheckInterfaceSupported
+ *
+ * Don't return an interface instance unless it can be guaranteed that the
+ * remote object actually implements the interface.
+ */
+
+/**
+ * \var OptionalInterfaceFactory::InterfaceSupportedChecking OptionalInterfaceFactory::BypassInterfaceCheck
+ *
+ * Return an interface instance even if it can't be verified that the remote
+ * object supports the interface.
+ */
+
+/**
+ * \fn OptionalInterfaceFactory::OptionalInterfaceFactory(DBusProxySubclass *this_)
+ *
+ * Class constructor.
+ *
+ * \param this_ The class to which this OptionalInterfaceFactory is
+ * attached
+ */
+
+/**
+ * \fn OptionalInterfaceFactory::~OptionalInterfaceFactory()
+ *
+ * Class destructor.
+ *
+ * Frees all interface instances constructed by this factory.
+ */
+
+ /**
+ * \fn OptionalInterfaceFactory::interfaces() const;
+ *
+ * Return a list of interfaces supported by this object.
+ *
+ * \return List of supported interfaces.
+ */
+
+/**
+ * \fn template <typename Interface> inline Interface *OptionalInterfaceFactory::interface() const
+ *
+ * Return a pointer to a valid instance of a interface class, associated
+ * with the same remote object as the given main interface instance. The
+ * given main interface must be of the class the optional interface is
+ * generated for (for eg. ChannelInterfaceGroupInterface this means
+ * ChannelInterface) or a subclass.
+ *
+ * First invocation of this method for a particular optional interface
+ * class will construct the instance; subsequent calls will return a
+ * pointer to the same instance.
+ *
+ * The returned instance is freed when the factory is destroyed; using
+ * it after destroying the factory will likely produce a crash. As the
+ * instance is shared, it should not be freed directly.
+ *
+ * \tparam Interface Class of the interface instance to get.
+ * \return A pointer to an optional interface instance.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/optional-interface-factory.h b/qt4/TelepathyQt4/optional-interface-factory.h
new file mode 100644
index 000000000..598d521b3
--- /dev/null
+++ b/qt4/TelepathyQt4/optional-interface-factory.h
@@ -0,0 +1,140 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_optional_interface_factory_h_HEADER_GUARD_
+#define _TelepathyQt4_optional_interface_factory_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QObject>
+#include <QStringList>
+#include <QtGlobal>
+
+namespace Tp
+{
+
+class AbstractInterface;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+class TELEPATHY_QT4_EXPORT OptionalInterfaceCache
+{
+ Q_DISABLE_COPY(OptionalInterfaceCache)
+
+public:
+ explicit OptionalInterfaceCache(QObject *proxy);
+
+ ~OptionalInterfaceCache();
+
+protected:
+ AbstractInterface *getCached(const QString &name) const;
+ void cache(AbstractInterface *interface) const;
+ QObject *proxy() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+template <typename DBusProxySubclass> class OptionalInterfaceFactory
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+ : private OptionalInterfaceCache
+#endif
+{
+ Q_DISABLE_COPY(OptionalInterfaceFactory)
+
+public:
+ enum InterfaceSupportedChecking
+ {
+ CheckInterfaceSupported,
+ BypassInterfaceCheck
+ };
+
+ inline OptionalInterfaceFactory(DBusProxySubclass *this_)
+ : OptionalInterfaceCache(this_)
+ {
+ }
+
+ inline ~OptionalInterfaceFactory()
+ {
+ }
+
+ inline QStringList interfaces() const { return mInterfaces; }
+
+ inline bool hasInterface(const QString &name) const
+ {
+ return mInterfaces.contains(name);
+ }
+
+ template <class Interface>
+ inline Interface *optionalInterface(
+ InterfaceSupportedChecking check = CheckInterfaceSupported) const
+ {
+ // Check for the remote object supporting the interface
+ QString name(QLatin1String(Interface::staticInterfaceName()));
+ if (check == CheckInterfaceSupported && !mInterfaces.contains(name)) {
+ return 0;
+ }
+
+ // If present or forced, delegate to OptionalInterfaceFactory
+ return interface<Interface>();
+ }
+
+ template <typename Interface>
+ inline Interface *interface() const
+ {
+ AbstractInterface* interfaceMustBeASubclassOfAbstractInterface = static_cast<Interface *>(NULL);
+ Q_UNUSED(interfaceMustBeASubclassOfAbstractInterface);
+
+ // If there is a interface cached already, return it
+ QString name(QLatin1String(Interface::staticInterfaceName()));
+ AbstractInterface *cached = getCached(name);
+ if (cached)
+ return static_cast<Interface *>(cached);
+
+ // Otherwise, cache and return a newly constructed proxy
+ Interface *interface = new Interface(
+ static_cast<DBusProxySubclass *>(proxy()));
+ cache(interface);
+ return interface;
+ }
+
+protected:
+ inline void setInterfaces(const QStringList &interfaces)
+ {
+ mInterfaces = interfaces;
+ }
+
+private:
+ QStringList mInterfaces;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/or-filter.dox b/qt4/TelepathyQt4/or-filter.dox
new file mode 100644
index 000000000..451c5142b
--- /dev/null
+++ b/qt4/TelepathyQt4/or-filter.dox
@@ -0,0 +1,33 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \class Tp::OrFilter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/or-filter.h <TelepathyQt4/OrFilter>
+ *
+ * \brief The OrFilter class provides a generic filter object to be used
+ * in conjunction of other filters.
+ *
+ * The OrFilter will match if any of its given list of filters matches
+ * their criteria.
+ */
diff --git a/qt4/TelepathyQt4/or-filter.h b/qt4/TelepathyQt4/or-filter.h
new file mode 100644
index 000000000..0f8643630
--- /dev/null
+++ b/qt4/TelepathyQt4/or-filter.h
@@ -0,0 +1,83 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_or_filter_h_HEADER_GUARD_
+#define _TelepathyQt4_or_filter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Filter>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+template <class T>
+class OrFilter : public Filter<T>
+{
+public:
+ static SharedPtr<OrFilter<T> > create(
+ const QList<SharedPtr<const Filter<T> > > &filters = QList<SharedPtr<const Filter<T> > >())
+ {
+ return SharedPtr<OrFilter<T> >(new OrFilter<T>(filters));
+ }
+
+ inline virtual ~OrFilter() { }
+
+ inline virtual bool isValid() const
+ {
+ Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) {
+ if (!filter || !filter->isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ inline virtual bool matches(const SharedPtr<T> &t) const
+ {
+ if (!isValid()) {
+ return false;
+ }
+
+ Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) {
+ if (filter->matches(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ inline QList<SharedPtr<const Filter<T> > > filters() const { return mFilters; }
+
+private:
+ OrFilter(const QList<SharedPtr<const Filter<T> > > &filters)
+ : Filter<T>(), mFilters(filters) { }
+
+ QList<SharedPtr<const Filter<T> > > mFilters;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp b/qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp
new file mode 100644
index 000000000..5abf48141
--- /dev/null
+++ b/qt4/TelepathyQt4/outgoing-file-transfer-channel.cpp
@@ -0,0 +1,371 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+
+#include "TelepathyQt4/_gen/outgoing-file-transfer-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/types-internal.h>
+
+#include <QIODevice>
+#include <QTcpSocket>
+
+namespace Tp
+{
+
+static const int FT_BLOCK_SIZE = 16 * 1024;
+
+struct TELEPATHY_QT4_NO_EXPORT OutgoingFileTransferChannel::Private
+{
+ Private(OutgoingFileTransferChannel *parent);
+ ~Private();
+
+ // Public object
+ OutgoingFileTransferChannel *parent;
+
+ Client::ChannelTypeFileTransferInterface *fileTransferInterface;
+
+ // Introspection
+ QIODevice *input;
+ QTcpSocket *socket;
+ SocketAddressIPv4 addr;
+
+ qint64 pos;
+};
+
+OutgoingFileTransferChannel::Private::Private(OutgoingFileTransferChannel *parent)
+ : parent(parent),
+ fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()),
+ input(0),
+ socket(0),
+ pos(0)
+{
+}
+
+OutgoingFileTransferChannel::Private::~Private()
+{
+}
+
+/**
+ * \class OutgoingFileTransferChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/outgoing-file-transfer-channel.h <TelepathyQt4/OutgoingFileTransferChannel>
+ *
+ * \brief The OutgoingFileTransferChannel class represents a Telepathy channel
+ * of type FileTransfer for outgoing file transfers.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * OutgoingFileTransferChannel object usable.
+ *
+ * This is currently the same as FileTransferChannel::FeatureCore, but may change to include more.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature OutgoingFileTransferChannel::FeatureCore =
+ Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); // FT::FeatureCore
+
+/**
+ * Create a new OutgoingFileTransferChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return An OutgoingFileTransferChannelPtr object pointing to the newly created
+ * OutgoingFileTransferChannel object.
+ */
+OutgoingFileTransferChannelPtr OutgoingFileTransferChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return OutgoingFileTransferChannelPtr(new OutgoingFileTransferChannel(
+ connection, objectPath, immutableProperties,
+ OutgoingFileTransferChannel::FeatureCore));
+}
+
+/**
+ * Construct a new OutgoingFileTransferChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on OutgoingFileTransferChannel::FeatureCore.
+ */
+OutgoingFileTransferChannel::OutgoingFileTransferChannel(
+ const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : FileTransferChannel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+OutgoingFileTransferChannel::~OutgoingFileTransferChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Provide the file for an outgoing file transfer which has been offered.
+ *
+ * The state will change to #FileTransferStateOpen as soon as the transfer
+ * starts.
+ * The given input device should not be destroyed until the state()
+ * changes to #FileTransferStateCompleted or #FileTransferStateCancelled.
+ * If input is a sequential device QIODevice::isSequential(), it should be
+ * closed when no more data is available, so that it's known when to stop reading.
+ *
+ * Only the primary handler of a file transfer channel may call this method.
+ *
+ * This method requires FileTransferChannel::FeatureCore to be ready.
+ *
+ * \param input A QIODevice object where the data will be read from.
+ * \return A PendingOperation object which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa stateChanged(), state(), stateReason()
+ */
+PendingOperation *OutgoingFileTransferChannel::provideFile(QIODevice *input)
+{
+ if (!isReady(FileTransferChannel::FeatureCore)) {
+ warning() << "FileTransferChannel::FeatureCore must be ready before "
+ "calling provideFile";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ OutgoingFileTransferChannelPtr(this));
+ }
+
+ // let's fail here direclty as we may only have one device to handle
+ if (mPriv->input) {
+ warning() << "File transfer can only be started once in the same "
+ "channel";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("File transfer can only be started once in the same channel"),
+ OutgoingFileTransferChannelPtr(this));
+ }
+
+ if ((!input->isOpen() && !input->open(QIODevice::ReadOnly)) &&
+ !input->isReadable()) {
+ warning() << "Unable to open IO device for reading";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED),
+ QLatin1String("Unable to open IO device for reading"),
+ OutgoingFileTransferChannelPtr(this));
+ }
+
+ mPriv->input = input;
+ connect(input,
+ SIGNAL(aboutToClose()),
+ SLOT(onInputAboutToClose()));
+
+ PendingVariant *pv = new PendingVariant(
+ mPriv->fileTransferInterface->ProvideFile(
+ SocketAddressTypeIPv4,
+ SocketAccessControlLocalhost,
+ QDBusVariant(QVariant(QString()))),
+ OutgoingFileTransferChannelPtr(this));
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onProvideFileFinished(Tp::PendingOperation*)));
+ return pv;
+}
+
+void OutgoingFileTransferChannel::onProvideFileFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Error providing file transfer " <<
+ op->errorName() << ":" << op->errorMessage();
+ invalidate(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ PendingVariant *pv = qobject_cast<PendingVariant *>(op);
+ mPriv->addr = qdbus_cast<SocketAddressIPv4>(pv->result());
+ debug().nospace() << "Got address " << mPriv->addr.address <<
+ ":" << mPriv->addr.port;
+
+ if (state() == FileTransferStateOpen) {
+ connectToHost();
+ }
+}
+
+void OutgoingFileTransferChannel::connectToHost()
+{
+ if (isConnected() || mPriv->addr.address.isNull()) {
+ return;
+ }
+
+ mPriv->socket = new QTcpSocket(this);
+
+ connect(mPriv->socket, SIGNAL(connected()),
+ SLOT(onSocketConnected()));
+ connect(mPriv->socket, SIGNAL(disconnected()),
+ SLOT(onSocketDisconnected()));
+ connect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ SLOT(onSocketError(QAbstractSocket::SocketError)));
+ connect(mPriv->socket, SIGNAL(bytesWritten(qint64)),
+ SLOT(doTransfer()));
+
+ debug().nospace() << "Connecting to host " <<
+ mPriv->addr.address << ":" << mPriv->addr.port << "...";
+ mPriv->socket->connectToHost(mPriv->addr.address, mPriv->addr.port);
+}
+
+void OutgoingFileTransferChannel::onSocketConnected()
+{
+ debug() << "Connected to host";
+ setConnected();
+
+ connect(mPriv->input, SIGNAL(readyRead()),
+ SLOT(doTransfer()));
+
+ // for non sequential devices, let's seek to the initialOffset
+ if (!mPriv->input->isSequential()) {
+ if (mPriv->input->seek(initialOffset())) {
+ mPriv->pos = initialOffset();
+ }
+ }
+
+ debug() << "Starting transfer...";
+ doTransfer();
+}
+
+void OutgoingFileTransferChannel::onSocketDisconnected()
+{
+ debug() << "Disconnected from host";
+ setFinished();
+}
+
+void OutgoingFileTransferChannel::onSocketError(QAbstractSocket::SocketError error)
+{
+ debug() << "Socket error" << error;
+ setFinished();
+}
+
+void OutgoingFileTransferChannel::onInputAboutToClose()
+{
+ debug() << "Input closed";
+
+ // read all remaining data from input device and write to output device
+ if (isConnected()) {
+ QByteArray data;
+ data = mPriv->input->readAll();
+ mPriv->socket->write(data); // never fails
+ }
+
+ setFinished();
+}
+
+void OutgoingFileTransferChannel::doTransfer()
+{
+ // read FT_BLOCK_SIZE each time, as input can be a QFile, we don't want to
+ // block reading the whole file
+ char buffer[FT_BLOCK_SIZE];
+ char *p = buffer;
+
+ memset(buffer, 0, sizeof(buffer));
+ qint64 len = mPriv->input->read(buffer, sizeof(buffer));
+
+ bool scheduleTransfer = false;
+ if (((qulonglong) mPriv->pos < initialOffset()) && (len > 0)) {
+ qint64 skip = (qint64) qMin(initialOffset() - mPriv->pos,
+ (qulonglong) len);
+
+ debug() << "skipping" << skip << "bytes";
+ if (skip == len) {
+ // nothing to write, all data read was skipped
+ // schedule a transfer, as readyRead may never be called and
+ // bytesWriiten will not
+ scheduleTransfer = true;
+ goto end;
+ }
+
+ p += skip;
+ len -= skip;
+ }
+
+ if (len > 0) {
+ mPriv->socket->write(p, len); // never fails
+ }
+
+end:
+ if (len == -1 || (!mPriv->input->isSequential() && mPriv->input->atEnd())) {
+ // error or EOF
+ setFinished();
+ return;
+ }
+
+ mPriv->pos += len;
+
+ if (scheduleTransfer) {
+ QMetaObject::invokeMethod(this, SLOT(doTransfer()),
+ Qt::QueuedConnection);
+ }
+}
+
+void OutgoingFileTransferChannel::setFinished()
+{
+ if (isFinished()) {
+ // it shouldn't happen but let's make sure
+ return;
+ }
+
+ if (mPriv->socket) {
+ disconnect(mPriv->socket, SIGNAL(connected()),
+ this, SLOT(onSocketConnected()));
+ disconnect(mPriv->socket, SIGNAL(disconnected()),
+ this, SLOT(onSocketDisconnected()));
+ disconnect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(onSocketError(QAbstractSocket::SocketError)));
+ disconnect(mPriv->socket, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(doTransfer()));
+ mPriv->socket->close();
+ }
+
+ if (mPriv->input) {
+ disconnect(mPriv->input, SIGNAL(aboutToClose()),
+ this, SLOT(onInputAboutToClose()));
+ disconnect(mPriv->input, SIGNAL(readyRead()),
+ this, SLOT(doTransfer()));
+ mPriv->input->close();
+ }
+
+ FileTransferChannel::setFinished();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/outgoing-file-transfer-channel.h b/qt4/TelepathyQt4/outgoing-file-transfer-channel.h
new file mode 100644
index 000000000..6f738704e
--- /dev/null
+++ b/qt4/TelepathyQt4/outgoing-file-transfer-channel.h
@@ -0,0 +1,78 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_outgoing_file_transfer_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_outgoing_file_transfer_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/FileTransferChannel>
+
+#include <QAbstractSocket>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT OutgoingFileTransferChannel : public FileTransferChannel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(OutgoingFileTransferChannel)
+
+public:
+ static const Feature FeatureCore;
+
+ static OutgoingFileTransferChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~OutgoingFileTransferChannel();
+
+ PendingOperation *provideFile(QIODevice *input);
+
+protected:
+ OutgoingFileTransferChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = OutgoingFileTransferChannel::FeatureCore);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onProvideFileFinished(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onSocketConnected();
+ TELEPATHY_QT4_NO_EXPORT void onSocketDisconnected();
+ TELEPATHY_QT4_NO_EXPORT void onSocketError(QAbstractSocket::SocketError error);
+ TELEPATHY_QT4_NO_EXPORT void onInputAboutToClose();
+ TELEPATHY_QT4_NO_EXPORT void doTransfer();
+
+private:
+ TELEPATHY_QT4_NO_EXPORT void connectToHost();
+ TELEPATHY_QT4_NO_EXPORT void setFinished();
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/outgoing-stream-tube-channel-internal.h b/qt4/TelepathyQt4/outgoing-stream-tube-channel-internal.h
new file mode 100644
index 000000000..3889123ce
--- /dev/null
+++ b/qt4/TelepathyQt4/outgoing-stream-tube-channel-internal.h
@@ -0,0 +1,122 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_outgoing_stream_tube_channel_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_outgoing_stream_tube_channel_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/PendingOperation>
+
+namespace Tp
+{
+
+class PendingVoid;
+
+class TELEPATHY_QT4_NO_EXPORT PendingOpenTube : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingOpenTube)
+
+public:
+ PendingOpenTube(PendingVoid *offerOperation,
+ const QVariantMap &parameters,
+ const OutgoingStreamTubeChannelPtr &object);
+ ~PendingOpenTube();
+
+private Q_SLOTS:
+ void onTubeStateChanged(Tp::TubeChannelState state);
+ void onOfferFinished(Tp::PendingOperation *operation);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_NO_EXPORT QueuedContactFactory : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(QueuedContactFactory)
+
+public:
+ QueuedContactFactory(ContactManagerPtr contactManager, QObject* parent = 0);
+ ~QueuedContactFactory();
+
+ QUuid appendNewRequest(const UIntList &handles);
+
+Q_SIGNALS:
+ void contactsRetrieved(QUuid uuid, QList<Tp::ContactPtr> contacts);
+
+private Q_SLOTS:
+ void onPendingContactsFinished(Tp::PendingOperation *operation);
+
+private:
+ struct Entry {
+ QUuid uuid;
+ UIntList handles;
+ };
+
+ void processNextRequest();
+
+ bool m_isProcessing;
+ ContactManagerPtr m_manager;
+ QQueue<Entry> m_queue;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT PendingOpenTube::Private
+{
+ Private(const QVariantMap &parameters, PendingOpenTube *parent);
+
+ // Public object
+ PendingOpenTube *parent;
+
+ OutgoingStreamTubeChannelPtr tube;
+ QVariantMap parameters;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT OutgoingStreamTubeChannel::Private
+{
+ Private(OutgoingStreamTubeChannel *parent);
+
+ OutgoingStreamTubeChannel *parent;
+
+ QHash<uint, Tp::ContactPtr> contactsForConnections;
+ QHash<QPair<QHostAddress, quint16>, uint> connectionsForSourceAddresses;
+ QHash<uchar, uint> connectionsForCredentials;
+
+ QHash<QUuid, QPair<uint, QDBusVariant> > pendingNewConnections;
+
+ struct ClosedConnection {
+ uint id;
+ QString error, message;
+
+ ClosedConnection() : id(~0U) {}
+ ClosedConnection(uint id, const QString &error, const QString &message)
+ : id(id), error(error), message(message) {}
+ };
+ QHash<QUuid, ClosedConnection> pendingClosedConnections;
+
+ QueuedContactFactory *queuedContactFactory;
+};
+
+}
+
+#endif
diff --git a/qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp b/qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp
new file mode 100644
index 000000000..4169053bf
--- /dev/null
+++ b/qt4/TelepathyQt4/outgoing-stream-tube-channel.cpp
@@ -0,0 +1,821 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include "TelepathyQt4/outgoing-stream-tube-channel-internal.h"
+
+#include "TelepathyQt4/_gen/outgoing-stream-tube-channel.moc.hpp"
+#include "TelepathyQt4/_gen/outgoing-stream-tube-channel-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/types-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/Types>
+
+#include <QHostAddress>
+#include <QTcpServer>
+#include <QLocalServer>
+
+namespace Tp
+{
+
+PendingOpenTube::Private::Private(const QVariantMap &parameters, PendingOpenTube *parent)
+ : parent(parent),
+ parameters(parameters)
+{
+}
+
+PendingOpenTube::PendingOpenTube(
+ PendingVoid *offerOperation,
+ const QVariantMap &parameters,
+ const OutgoingStreamTubeChannelPtr &object)
+ : PendingOperation(object),
+ mPriv(new Private(parameters, this))
+{
+ mPriv->tube = object;
+
+ // FIXME: connect to channel invalidation here also
+
+ debug() << "Calling StreamTube.Offer";
+ if (offerOperation->isFinished()) {
+ onOfferFinished(offerOperation);
+ } else {
+ // Connect the pending void
+ connect(offerOperation, SIGNAL(finished(Tp::PendingOperation*)),
+ this, SLOT(onOfferFinished(Tp::PendingOperation*)));
+ }
+}
+
+PendingOpenTube::~PendingOpenTube()
+{
+ delete mPriv;
+}
+
+void PendingOpenTube::onOfferFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ warning().nospace() << "StreamTube.Offer failed with " <<
+ op->errorName() << ": " << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ debug() << "StreamTube.Offer returned successfully";
+
+ // It might have been already opened - check
+ if (mPriv->tube->state() != TubeChannelStateOpen) {
+ debug() << "Awaiting tube to be opened";
+ // Wait until the tube gets opened on the other side
+ connect(mPriv->tube.data(),
+ SIGNAL(stateChanged(Tp::TubeChannelState)),
+ SLOT(onTubeStateChanged(Tp::TubeChannelState)));
+ }
+
+ onTubeStateChanged(mPriv->tube->state());
+}
+
+void PendingOpenTube::onTubeStateChanged(TubeChannelState state)
+{
+ if (state == TubeChannelStateOpen) {
+ debug() << "Tube is now opened";
+ // Inject the parameters into the tube
+ mPriv->tube->setParameters(mPriv->parameters);
+ // The tube is ready: let's notify
+ setFinished();
+ } else {
+ if (state != TubeChannelStateRemotePending) {
+ warning() << "Offering tube failed with" << TP_QT4_ERROR_CONNECTION_REFUSED;
+ // Something happened
+ setFinishedWithError(TP_QT4_ERROR_CONNECTION_REFUSED,
+ QLatin1String("The connection to this tube was refused"));
+ } else {
+ debug() << "Awaiting remote to accept the tube";
+ }
+ }
+}
+
+QueuedContactFactory::QueuedContactFactory(Tp::ContactManagerPtr contactManager, QObject* parent)
+ : QObject(parent),
+ m_isProcessing(false),
+ m_manager(contactManager)
+{
+}
+
+QueuedContactFactory::~QueuedContactFactory()
+{
+}
+
+void QueuedContactFactory::processNextRequest()
+{
+ if (m_isProcessing || m_queue.isEmpty()) {
+ // Return, nothing to do
+ return;
+ }
+
+ m_isProcessing = true;
+
+ Entry entry = m_queue.dequeue();
+
+ // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids
+ // from NewRemoteConnection.
+ PendingContacts *pc = m_manager->contactsForHandles(entry.handles);
+ pc->setProperty("__TpQt4__QueuedContactFactoryUuid", entry.uuid.toString());
+ connect(pc, SIGNAL(finished(Tp::PendingOperation*)),
+ this, SLOT(onPendingContactsFinished(Tp::PendingOperation*)));
+}
+
+QUuid QueuedContactFactory::appendNewRequest(const Tp::UIntList &handles)
+{
+ // Create a new entry
+ Entry entry;
+ entry.uuid = QUuid::createUuid();
+ entry.handles = handles;
+ m_queue.enqueue(entry);
+
+ // Check if we can process a request
+ processNextRequest();
+
+ // Return the UUID
+ return entry.uuid;
+}
+
+void QueuedContactFactory::onPendingContactsFinished(PendingOperation *op)
+{
+ PendingContacts *pc = qobject_cast<PendingContacts*>(op);
+
+ QUuid uuid = QUuid(pc->property("__TpQt4__QueuedContactFactoryUuid").toString());
+
+ emit contactsRetrieved(uuid, pc->contacts());
+
+ // No longer processing
+ m_isProcessing = false;
+
+ // Go for next one
+ processNextRequest();
+}
+
+OutgoingStreamTubeChannel::Private::Private(OutgoingStreamTubeChannel *parent)
+ : parent(parent),
+ queuedContactFactory(new QueuedContactFactory(parent->connection()->contactManager(), parent))
+{
+}
+
+/**
+ * \class OutgoingStreamTubeChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/outgoing-stream-tube-channel.h <TelepathyQt4/OutgoingStreamTubeChannel>
+ *
+ * \brief The OutgoingStreamTubeChannel class represents an outgoing Telepathy channel
+ * of type StreamTube.
+ *
+ * Outgoing (locally initiated/requested) tubes are initially in the #TubeChannelStateNotOffered state. The
+ * various offer methods in this class can be used to offer a local listening TCP or Unix socket for
+ * the tube's target to connect to, at which point the tube becomes #TubeChannelStateRemotePending.
+ * If the target accepts the connection request, the state goes #TubeChannelStateOpen and the
+ * connection manager will start tunneling any incoming connections from the recipient side to the
+ * local service.
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * OutgoingStreamTubeChannel object usable.
+ *
+ * This is currently the same as StreamTubeChannel::FeatureCore, but may change to include more.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature OutgoingStreamTubeChannel::FeatureCore =
+ Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); // ST::FeatureCore
+
+/**
+ * Create a new OutgoingStreamTubeChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A OutgoingStreamTubeChannelPtr object pointing to the newly created
+ * OutgoingStreamTubeChannel object.
+ */
+OutgoingStreamTubeChannelPtr OutgoingStreamTubeChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return OutgoingStreamTubeChannelPtr(new OutgoingStreamTubeChannel(connection, objectPath,
+ immutableProperties, OutgoingStreamTubeChannel::FeatureCore));
+}
+
+/**
+ * Construct a new OutgoingStreamTubeChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on OutgoingStreamTubeChannel::FeatureCore.
+ */
+OutgoingStreamTubeChannel::OutgoingStreamTubeChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : StreamTubeChannel(connection, objectPath,
+ immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+ connect(mPriv->queuedContactFactory,
+ SIGNAL(contactsRetrieved(QUuid,QList<Tp::ContactPtr>)),
+ this,
+ SLOT(onContactsRetrieved(QUuid,QList<Tp::ContactPtr>)));
+}
+
+/**
+ * Class destructor.
+ */
+OutgoingStreamTubeChannel::~OutgoingStreamTubeChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Offer a TCP socket over this stream tube.
+ *
+ * This method offers a TCP socket over this tube. The socket's address is given as
+ * a QHostAddress and a numerical port in native byte order.
+ *
+ * If your application uses QTcpServer as the local TCP server implementation, you can use the
+ * offerTcpSocket(const QTcpServer *, const QVariantMap &) overload instead to more easily pass the
+ * server's listen address.
+ *
+ * It is guaranteed that when the PendingOperation returned by this method will be completed,
+ * the tube will be opened and ready to be used.
+ *
+ * Connection managers adhering to the \telepathy_spec should always support offering IPv4 TCP
+ * sockets. IPv6 sockets are only supported if supportsIPv6SocketsOnLocalhost() is \c true.
+ *
+ * Note that the library will try to use #SocketAccessControlPort access control whenever possible,
+ * as it allows to map connections to users based on their source addresses. If
+ * supportsIPv4SocketsWithSpecifiedAddress() or supportsIPv6SocketsWithSpecifiedAddress() for IPv4
+ * and IPv6 sockets respectively is \c false, this feature is not available, and the
+ * connectionsForSourceAddresses() map won't contain useful distinct keys.
+ *
+ * Arbitrary parameters can be associated with the offer to bootstrap legacy protocols; these will
+ * in particular be available as IncomingStreamTubeChannel::parameters() for a tube receiver
+ * implemented using TelepathyQt4 in the other end.
+ *
+ * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready.
+ *
+ * \param address A valid IPv4 or IPv6 address pointing to an existing socket.
+ * \param port The port the socket is listening for connections to.
+ * \param parameters A dictionary of arbitrary parameters to send with the tube offer.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ */
+PendingOperation *OutgoingStreamTubeChannel::offerTcpSocket(
+ const QHostAddress &address,
+ quint16 port,
+ const QVariantMap &parameters)
+{
+ if (!isReady(OutgoingStreamTubeChannel::FeatureCore)) {
+ warning() << "OutgoingStreamTubeChannel::FeatureCore must be ready before "
+ "calling offerTube";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ // The tube must be not offered
+ if (state() != TubeChannelStateNotOffered) {
+ warning() << "You can not expose more than a socket for each Stream Tube";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel busy"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ SocketAccessControl accessControl = SocketAccessControlLocalhost;
+ // Check if port is supported
+
+ // In this specific overload, we're handling an IPv4/IPv6 socket
+ if (address.protocol() == QAbstractSocket::IPv4Protocol) {
+ // IPv4 case
+ SocketAccessControl accessControl;
+ // Do some heuristics to find out the best access control.We always prefer port for tracking
+ // connections and source addresses.
+ if (supportsIPv4SocketsWithSpecifiedAddress()) {
+ accessControl = SocketAccessControlPort;
+ } else if (supportsIPv4SocketsOnLocalhost()) {
+ accessControl = SocketAccessControlLocalhost;
+ } else {
+ // There are no combinations supported for this socket
+ warning() << "You requested an address type/access control combination "
+ "not supported by this channel";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The requested address type/access control "
+ "combination is not supported"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ setAddressType(SocketAddressTypeIPv4);
+ setAccessControl(accessControl);
+ setIpAddress(qMakePair<QHostAddress, quint16>(address, port));
+
+ SocketAddressIPv4 addr;
+ addr.address = address.toString();
+ addr.port = port;
+
+ PendingVoid *pv = new PendingVoid(
+ interface<Client::ChannelTypeStreamTubeInterface>()->Offer(
+ SocketAddressTypeIPv4,
+ QDBusVariant(QVariant::fromValue(addr)),
+ accessControl,
+ parameters),
+ OutgoingStreamTubeChannelPtr(this));
+ PendingOpenTube *op = new PendingOpenTube(pv, parameters,
+ OutgoingStreamTubeChannelPtr(this));
+ return op;
+ } else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
+ // IPv6 case
+ // Do some heuristics to find out the best access control.We always prefer port for tracking
+ // connections and source addresses.
+ if (supportsIPv6SocketsWithSpecifiedAddress()) {
+ accessControl = SocketAccessControlPort;
+ } else if (supportsIPv6SocketsOnLocalhost()) {
+ accessControl = SocketAccessControlLocalhost;
+ } else {
+ // There are no combinations supported for this socket
+ warning() << "You requested an address type/access control combination "
+ "not supported by this channel";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The requested address type/access control "
+ "combination is not supported"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ setAddressType(SocketAddressTypeIPv6);
+ setAccessControl(accessControl);
+ setIpAddress(qMakePair<QHostAddress, quint16>(address, port));
+
+ SocketAddressIPv6 addr;
+ addr.address = address.toString();
+ addr.port = port;
+
+ PendingVoid *pv = new PendingVoid(
+ interface<Client::ChannelTypeStreamTubeInterface>()->Offer(
+ SocketAddressTypeIPv6,
+ QDBusVariant(QVariant::fromValue(addr)),
+ accessControl,
+ parameters),
+ OutgoingStreamTubeChannelPtr(this));
+ PendingOpenTube *op = new PendingOpenTube(pv, parameters,
+ OutgoingStreamTubeChannelPtr(this));
+ return op;
+ } else {
+ // We're handling an IPv4/IPv6 socket only
+ warning() << "offerTube can be called only with a QHostAddress representing "
+ "an IPv4 or IPv6 address";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid host given"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+}
+
+/**
+ * Offer a TCP socket over this stream tube.
+ *
+ * Otherwise identical to offerTcpSocket(const QHostAddress &, quint16, const QVariantMap &), but
+ * allows passing the local service's address in an already listening QTcpServer.
+ *
+ * \param server A valid QTcpServer, which should be already listening for incoming connections.
+ * \param parameters A dictionary of arbitrary parameters to send with the tube offer.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ */
+PendingOperation *OutgoingStreamTubeChannel::offerTcpSocket(
+ const QTcpServer *server,
+ const QVariantMap &parameters)
+{
+ // In this overload, we're handling a superset of QHostAddress.
+ // Let's redirect the call to QHostAddress's overload
+ return offerTcpSocket(server->serverAddress(), server->serverPort(),
+ parameters);
+}
+
+/**
+ * Offer an Unix socket over this stream tube.
+ *
+ * This method offers an Unix socket over this stream tube. The socket address is given as a
+ * a QString, which should contain the path to the socket. Abstract Unix sockets are also supported,
+ * and are given as addresses prefixed with a \c NUL byte.
+ *
+ * If your application uses QLocalServer as the local Unix server implementation, you can use the
+ * offerUnixSocket(const QLocalServer *, const QVariantMap &, bool) overload instead to more easily
+ * pass the server's listen address.
+ *
+ * Note that only connection managers for which supportsUnixSocketsOnLocalhost() or
+ * supportsAbstractUnixSocketsOnLocalhost() is \c true support exporting Unix sockets.
+ *
+ * If supportsUnixSocketsWithCredentials() or supportsAbstractUnixSocketsWithCredentials(), as
+ * appropriate, returns \c true, the \c requireCredentials parameter can be set to \c true to make
+ * the connection manager pass an SCM_CREDS or SCM_CREDENTIALS message as supported by the platform
+ * when making a new connection. This enables preventing other local users from connecting to the
+ * service, but might not be possible to use with all protocols as the message is in-band in the
+ * data stream.
+ *
+ * Arbitrary parameters can be associated with the offer to bootstrap legacy protocols; these will
+ * in particular be available as IncomingStreamTubeChannel::parameters() for a tube receiver
+ * implemented using TelepathyQt4 in the other end.
+ *
+ * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready.
+ *
+ * \param address A valid path to an existing Unix socket or abstract Unix socket.
+ * \param parameters A dictionary of arbitrary parameters to send with the tube offer.
+ * \param requireCredentials Whether the server requires a SCM_CREDS or SCM_CREDENTIALS message
+ * upon connection.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ */
+PendingOperation *OutgoingStreamTubeChannel::offerUnixSocket(
+ const QString &socketAddress,
+ const QVariantMap &parameters,
+ bool requireCredentials)
+{
+ SocketAccessControl accessControl = requireCredentials ?
+ SocketAccessControlCredentials :
+ SocketAccessControlLocalhost;
+
+ if (!isReady(OutgoingStreamTubeChannel::FeatureCore)) {
+ warning() << "OutgoingStreamTubeChannel::FeatureCore must be ready before "
+ "calling offerTube";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel not ready"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ // The tube must be not offered
+ if (state() != TubeChannelStateNotOffered) {
+ warning() << "You can not expose more than a socket for each Stream Tube";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Channel busy"), OutgoingStreamTubeChannelPtr(this));
+ }
+
+ // In this specific overload, we're handling an Unix/AbstractUnix socket
+ if (socketAddress.startsWith(QLatin1Char('\0'))) {
+ // Abstract Unix socket case
+ // Check if the combination type/access control is supported
+ if ((accessControl == SocketAccessControlLocalhost &&
+ !supportsAbstractUnixSocketsOnLocalhost()) ||
+ (accessControl == SocketAccessControlCredentials &&
+ !supportsAbstractUnixSocketsWithCredentials()) ) {
+ warning() << "You requested an address type/access control combination "
+ "not supported by this channel";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The requested address type/access control "
+ "combination is not supported"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ setAddressType(SocketAddressTypeAbstractUnix);
+ setAccessControl(accessControl);
+ setLocalAddress(socketAddress);
+
+ PendingVoid *pv = new PendingVoid(
+ interface<Client::ChannelTypeStreamTubeInterface>()->Offer(
+ SocketAddressTypeAbstractUnix,
+ QDBusVariant(QVariant(socketAddress.toLatin1())),
+ accessControl,
+ parameters),
+ OutgoingStreamTubeChannelPtr(this));
+ PendingOpenTube *op = new PendingOpenTube(pv, parameters,
+ OutgoingStreamTubeChannelPtr(this));
+ return op;
+ } else {
+ // Unix socket case
+ // Check if the combination type/access control is supported
+ if ((accessControl == SocketAccessControlLocalhost &&
+ !supportsUnixSocketsOnLocalhost()) ||
+ (accessControl == SocketAccessControlCredentials &&
+ !supportsUnixSocketsWithCredentials()) ||
+ (accessControl != SocketAccessControlLocalhost &&
+ accessControl != SocketAccessControlCredentials) ) {
+ warning() << "You requested an address type/access control combination "
+ "not supported by this channel";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("The requested address type/access control "
+ "combination is not supported"),
+ OutgoingStreamTubeChannelPtr(this));
+ }
+
+ setAddressType(SocketAddressTypeUnix);
+ setAccessControl(accessControl);
+ setLocalAddress(socketAddress);
+
+ PendingVoid *pv = new PendingVoid(
+ interface<Client::ChannelTypeStreamTubeInterface>()->Offer(
+ SocketAddressTypeUnix,
+ QDBusVariant(QVariant(socketAddress.toLatin1())),
+ accessControl,
+ parameters),
+ OutgoingStreamTubeChannelPtr(this));
+ PendingOpenTube *op = new PendingOpenTube(pv, parameters,
+ OutgoingStreamTubeChannelPtr(this));
+ return op;
+ }
+}
+
+/**
+ * Offer an Unix socket over the tube.
+ *
+ * Otherwise identical to offerUnixSocket(const QString &, const QVariantMap &, bool), but allows
+ * passing the local service's address as an already listening QLocalServer.
+ *
+ * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready.
+ *
+ * \param server A valid QLocalServer, which should be already listening for incoming connections.
+ * \param parameters A dictionary of arbitrary parameters to send with the tube offer.
+ * \param requireCredentials Whether the server should require a SCM_CRED or SCM_CREDENTIALS message
+ * upon connection.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the stream tube is ready to be used
+ * (hence in the #TubeStateOpen state).
+ * \sa StreamTubeChannel::supportsUnixSocketsOnLocalhost(),
+ * StreamTubeChannel::supportsUnixSocketsWithCredentials(),
+ * StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost(),
+ * StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials()
+ */
+PendingOperation *OutgoingStreamTubeChannel::offerUnixSocket(
+ const QLocalServer *server,
+ const QVariantMap &parameters,
+ bool requireCredentials)
+{
+ // In this overload, we're handling a superset of a local socket
+ // Let's redirect the call to QString's overload
+ return offerUnixSocket(server->fullServerName(), parameters, requireCredentials);
+}
+
+/**
+ * Return a map from a source address to the corresponding connections ids.
+ *
+ * The connection ids retrieved here can be used to map a source address
+ * which connected to your socket to a connection ID (for error reporting) and further, to a contact
+ * (by using contactsForConnections()).
+ *
+ * This method is only useful if a TCP socket was offered on this tube and the connection manager
+ * supports #SocketAccessControlPort, which can be discovered using
+ * supportsIPv4SocketsWithSpecifiedAddress() and supportsIPv6SocketsWithSpecifiedAddress() for IPv4
+ * and IPv6 sockets respectively.
+ *
+ * Note that this function will only return valid data after the tube has been opened.
+ *
+ * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready.
+ *
+ * \return The map from source addresses as (QHostAddress, port in native byte order) pairs to the
+ * corresponding connection ids.
+ * \sa connectionsForCredentials()
+ */
+QHash<QPair<QHostAddress, quint16>, uint> OutgoingStreamTubeChannel::connectionsForSourceAddresses() const
+{
+ if (addressType() != SocketAddressTypeIPv4 && addressType() != SocketAddressTypeIPv6) {
+ warning() << "OutgoingStreamTubeChannel::connectionsForSourceAddresses() makes sense "
+ "just when offering a TCP socket";
+ return QHash<QPair<QHostAddress, quint16>, uint>();
+ }
+
+ if (isValid() || !isDroppingConnections() ||
+ !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) {
+ if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) {
+ warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before "
+ " calling connectionsForSourceAddresses";
+ return QHash<QPair<QHostAddress, quint16>, uint>();
+ }
+
+ if (state() != TubeChannelStateOpen) {
+ warning() << "OutgoingStreamTubeChannel::connectionsForSourceAddresses() makes sense "
+ "just when the tube is open";
+ return QHash<QPair<QHostAddress, quint16>, uint>();
+ }
+ }
+
+ return mPriv->connectionsForSourceAddresses;
+}
+
+/**
+ * Return a map from a credential byte to the corresponding connections ids.
+ *
+ * The connection ids retrieved here can be used to map a source address
+ * which connected to your socket to a connection ID (for error reporting) and further, to a contact
+ * (by using contactsForConnections()).
+ *
+ * This method is only useful if this tube was offered using an Unix socket and passing credential
+ * bytes was enabled (\c requireCredentials == true).
+ *
+ * Note that this function will only return valid data after the tube has been opened.
+ *
+ * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready.
+ *
+ * \return The map from credential bytes to the corresponding connection ids.
+ * \sa connectionsForSourceAddresses()
+ */
+QHash<uchar, uint> OutgoingStreamTubeChannel::connectionsForCredentials() const
+{
+ if (addressType() != SocketAddressTypeUnix && addressType() != SocketAddressTypeAbstractUnix) {
+ warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense "
+ "just when offering an Unix socket";
+ return QHash<uchar, uint>();
+ }
+
+ if (accessControl() != SocketAccessControlCredentials) {
+ warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense "
+ "just when offering an Unix socket requiring credentials";
+ return QHash<uchar, uint>();
+ }
+
+ if (isValid() || !isDroppingConnections() ||
+ !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) {
+ if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) {
+ warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before "
+ "calling OutgoingStreamTubeChannel::connectionsForCredentials()";
+ return QHash<uchar, uint>();
+ }
+
+ if (state() != TubeChannelStateOpen) {
+ warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense "
+ "just when the tube is opened";
+ return QHash<uchar, uint>();
+ }
+ }
+
+ return mPriv->connectionsForCredentials;
+}
+
+/**
+ * Return a map from connection ids to the associated contact.
+ *
+ * Note that this function will only return valid data after the tube has been opened.
+ *
+ * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready.
+
+ * \return The map from connection ids to pointer to Contact objects.
+ * \sa connectionsForSourceAddresses(), connectionsForCredentials(),
+ * StreamTubeChannel::addressType()
+ */
+QHash<uint, ContactPtr> OutgoingStreamTubeChannel::contactsForConnections() const
+{
+ if (isValid() || !isDroppingConnections() ||
+ !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) {
+ if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) {
+ warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before "
+ "calling contactsForConnections";
+ return QHash<uint, ContactPtr>();
+ }
+
+ if (state() != TubeChannelStateOpen) {
+ warning() << "OutgoingStreamTubeChannel::contactsForConnections() makes sense "
+ "just when the tube is open";
+ return QHash<uint, ContactPtr>();
+ }
+ }
+
+ return mPriv->contactsForConnections;
+}
+
+void OutgoingStreamTubeChannel::onNewRemoteConnection(
+ uint contactId,
+ const QDBusVariant &parameter,
+ uint connectionId)
+{
+ // Request the handles from our queued contact factory
+ QUuid uuid = mPriv->queuedContactFactory->appendNewRequest(UIntList() << contactId);
+
+ // Add a pending connection
+ mPriv->pendingNewConnections.insert(uuid, qMakePair(connectionId, parameter));
+}
+
+void OutgoingStreamTubeChannel::onContactsRetrieved(
+ const QUuid &uuid,
+ const QList<Tp::ContactPtr> &contacts)
+{
+ if (!isValid()) {
+ debug() << "Invalidated OutgoingStreamTubeChannel not emitting queued connection event";
+ return;
+ }
+
+ if (!mPriv->pendingNewConnections.contains(uuid)) {
+ if (mPriv->pendingClosedConnections.contains(uuid)) {
+ // closed connection
+ Private::ClosedConnection conn = mPriv->pendingClosedConnections.take(uuid);
+
+ // First, do removeConnection() so connectionClosed is emitted, and anybody connected to it
+ // (like StreamTubeServer) has a chance to recover the source address / contact
+ removeConnection(conn.id, conn.error, conn.message);
+
+ // Remove stuff from our hashes
+ mPriv->contactsForConnections.remove(conn.id);
+
+ QHash<QPair<QHostAddress, quint16>, uint>::iterator srcAddrIter =
+ mPriv->connectionsForSourceAddresses.begin();
+ while (srcAddrIter != mPriv->connectionsForSourceAddresses.end()) {
+ if (srcAddrIter.value() == conn.id) {
+ srcAddrIter = mPriv->connectionsForSourceAddresses.erase(srcAddrIter);
+ } else {
+ ++srcAddrIter;
+ }
+ }
+
+ QHash<uchar, uint>::iterator credIter = mPriv->connectionsForCredentials.begin();
+ while (credIter != mPriv->connectionsForCredentials.end()) {
+ if (credIter.value() == conn.id) {
+ credIter = mPriv->connectionsForCredentials.erase(credIter);
+ } else {
+ ++credIter;
+ }
+ }
+ } else {
+ warning() << "No pending connections found in OSTC" << objectPath() << "for contacts"
+ << contacts;
+ }
+
+ return;
+ }
+
+ // new connection
+ QPair<uint, QDBusVariant> connectionProperties = mPriv->pendingNewConnections.take(uuid);
+
+ // Add it to our connections hash
+ foreach (const Tp::ContactPtr &contact, contacts) {
+ mPriv->contactsForConnections.insert(connectionProperties.first, contact);
+ }
+
+ QPair<QHostAddress, quint16> address;
+ address.first = QHostAddress::Null;
+
+ // Now let's try to track the parameter
+ if (addressType() == SocketAddressTypeIPv4) {
+ // Try a qdbus_cast to our address struct: we're shielded from crashes
+ // thanks to our specification
+ SocketAddressIPv4 addr =
+ qdbus_cast<Tp::SocketAddressIPv4>(connectionProperties.second.variant());
+ address.first = QHostAddress(addr.address);
+ address.second = addr.port;
+ } else if (addressType() == SocketAddressTypeIPv6) {
+ SocketAddressIPv6 addr =
+ qdbus_cast<Tp::SocketAddressIPv6>(connectionProperties.second.variant());
+ address.first = QHostAddress(addr.address);
+ address.second = addr.port;
+ } else if (addressType() == SocketAddressTypeUnix ||
+ addressType() == SocketAddressTypeAbstractUnix) {
+ if (accessControl() == SocketAccessControlCredentials) {
+ uchar credentialByte = qdbus_cast<uchar>(connectionProperties.second.variant());
+ mPriv->connectionsForCredentials.insertMulti(credentialByte, connectionProperties.first);
+ }
+ }
+
+ if (address.first != QHostAddress::Null) {
+ // We can map it to a source address as well
+ mPriv->connectionsForSourceAddresses.insertMulti(address, connectionProperties.first);
+ }
+
+ // Time for us to emit the signal
+ addConnection(connectionProperties.first);
+}
+
+// This replaces the base class onConnectionClosed() slot, but unlike a virtual function, is ABI
+// compatible
+void OutgoingStreamTubeChannel::onConnectionClosed(uint connectionId,
+ const QString &errorName, const QString &errorMessage)
+{
+ // Insert a fake request to our queued contact factory to make the close events properly ordered
+ // with new connection events
+ QUuid uuid = mPriv->queuedContactFactory->appendNewRequest(UIntList());
+
+ // Add a pending connection close
+ mPriv->pendingClosedConnections.insert(uuid,
+ Private::ClosedConnection(connectionId, errorName, errorMessage));
+}
+
+}
diff --git a/qt4/TelepathyQt4/outgoing-stream-tube-channel.h b/qt4/TelepathyQt4/outgoing-stream-tube-channel.h
new file mode 100644
index 000000000..36c17247b
--- /dev/null
+++ b/qt4/TelepathyQt4/outgoing-stream-tube-channel.h
@@ -0,0 +1,89 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_outgoing_stream_tube_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_outgoing_stream_tube_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/StreamTubeChannel>
+#include <TelepathyQt4/PendingOperation>
+
+class QHostAddress;
+class QTcpServer;
+class QLocalServer;
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT OutgoingStreamTubeChannel : public StreamTubeChannel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(OutgoingStreamTubeChannel)
+
+public:
+ static const Feature FeatureCore;
+
+ static OutgoingStreamTubeChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~OutgoingStreamTubeChannel();
+
+ PendingOperation *offerTcpSocket(const QHostAddress &address, quint16 port,
+ const QVariantMap &parameters = QVariantMap());
+ PendingOperation *offerTcpSocket(const QTcpServer *server,
+ const QVariantMap &parameters = QVariantMap());
+
+ PendingOperation *offerUnixSocket(const QString &socketAddress,
+ const QVariantMap &parameters = QVariantMap(), bool requireCredentials = false);
+ PendingOperation *offerUnixSocket(const QLocalServer *server,
+ const QVariantMap &parameters = QVariantMap(), bool requireCredentials = false);
+
+ QHash<uint, Tp::ContactPtr> contactsForConnections() const;
+
+ QHash<QPair<QHostAddress,quint16>, uint> connectionsForSourceAddresses() const;
+ QHash<uchar, uint> connectionsForCredentials() const;
+
+protected:
+ OutgoingStreamTubeChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = OutgoingStreamTubeChannel::FeatureCore);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onNewRemoteConnection(uint contactId,
+ const QDBusVariant &parameter, uint connectionId);
+ TELEPATHY_QT4_NO_EXPORT void onContactsRetrieved(const QUuid &uuid,
+ const QList<Tp::ContactPtr> &contacts);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionClosed(uint connectionId,
+ const QString &errorName, const QString &errorMessage);
+
+private:
+ struct Private;
+ friend struct PendingOpenTube;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-account.cpp b/qt4/TelepathyQt4/pending-account.cpp
new file mode 100644
index 000000000..cddff898b
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-account.cpp
@@ -0,0 +1,184 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingAccount>
+
+#include "TelepathyQt4/_gen/pending-account.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDBusObjectPath>
+#include <QDBusPendingCallWatcher>
+#include <QDBusPendingReply>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingAccount::Private
+{
+ AccountPtr account;
+};
+
+/**
+ * \class PendingAccount
+ * \ingroup clientaccount
+ * \headerfile TelepathyQt4/pending-account.h <TelepathyQt4/PendingAccount>
+ *
+ * \brief The PendingAccount class represents the parameters of and the reply to
+ * an asynchronous account request.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is via AccountManager.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingAccount object.
+ *
+ * \param manager AccountManager to use.
+ * \param connectionManager Name of the connection manager to create the account
+ * for.
+ * \param protocol Name of the protocol to create the account for.
+ * \param displayName Account display name.
+ * \param parameters Account parameters.
+ * \param properties An optional map from fully qualified D-Bus property
+ * names such as "org.freedesktop.Telepathy.Account.Enabled"
+ * to their values.
+ */
+PendingAccount::PendingAccount(const AccountManagerPtr &manager,
+ const QString &connectionManager, const QString &protocol,
+ const QString &displayName, const QVariantMap &parameters,
+ const QVariantMap &properties)
+ : PendingOperation(manager),
+ mPriv(new Private)
+{
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ manager->baseInterface()->CreateAccount(connectionManager,
+ protocol, displayName, parameters, properties), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingAccount::~PendingAccount()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account manager through which the request was made.
+ *
+ * \return A pointer to the AccountManager object.
+ */
+AccountManagerPtr PendingAccount::manager() const
+{
+ return AccountManagerPtr(qobject_cast<AccountManager *>((AccountManager*) _object().data()));
+}
+
+/**
+ * Return the newly created account.
+ *
+ * \return A pointer to an Account object, or a null AccountPtr if an error occurred.
+ */
+AccountPtr PendingAccount::account() const
+{
+ if (!isFinished()) {
+ warning() << "PendingAccount::account called before finished, returning 0";
+ return AccountPtr();
+ } else if (!isValid()) {
+ warning() << "PendingAccount::account called when not valid, returning 0";
+ return AccountPtr();
+ }
+
+ return mPriv->account;
+}
+
+void PendingAccount::onCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QDBusObjectPath> reply = *watcher;
+
+ if (!reply.isError()) {
+ QString objectPath = reply.value().path();
+
+ debug() << "Got reply to AccountManager.CreateAccount - object path:" << objectPath;
+
+ PendingReady *readyOp = manager()->accountFactory()->proxy(manager()->busName(),
+ objectPath, manager()->connectionFactory(),
+ manager()->channelFactory(), manager()->contactFactory());
+ mPriv->account = AccountPtr::qObjectCast(readyOp->proxy());
+ connect(readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAccountBuilt(Tp::PendingOperation*)));
+ } else {
+ debug().nospace() <<
+ "CreateAccount failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingAccount::onAccountBuilt(Tp::PendingOperation *op)
+{
+ Q_ASSERT(op->isFinished());
+
+ if (op->isError()) {
+ warning() << "Making account ready using the factory failed:" <<
+ op->errorName() << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ } else {
+ // AM is stateless, so the only way for it to become invalid is in the introspection phase,
+ // and a PendingAccount should never be created if AM introspection hasn't succeeded
+ Q_ASSERT(!manager().isNull() && manager()->isValid());
+
+ if (manager()->allAccounts().contains(mPriv->account)) {
+ setFinished();
+ debug() << "New account" << mPriv->account->objectPath() << "built";
+ } else {
+ // Have to wait for the AM to pick up the change and signal it so the world can be
+ // assumed to be ~round when we finish
+ connect(manager().data(),
+ SIGNAL(newAccount(Tp::AccountPtr)),
+ SLOT(onNewAccount(Tp::AccountPtr)));
+ }
+ }
+}
+
+void PendingAccount::onNewAccount(const AccountPtr &account)
+{
+ if (account != mPriv->account) {
+ return;
+ }
+
+ debug() << "Account" << account->objectPath() << "added to AM, finishing PendingAccount";
+ setFinished();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-account.h b/qt4/TelepathyQt4/pending-account.h
new file mode 100644
index 000000000..49ac68c62
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-account.h
@@ -0,0 +1,75 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_account_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_account_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/PendingOperation>
+
+#include <QString>
+#include <QVariantMap>
+
+class QDBusPendingCallWatcher;
+
+namespace Tp
+{
+
+class AccountManager;
+
+class TELEPATHY_QT4_EXPORT PendingAccount : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingAccount);
+
+public:
+ ~PendingAccount();
+
+ AccountManagerPtr manager() const;
+
+ AccountPtr account() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onAccountBuilt(Tp::PendingOperation *readyOp);
+ TELEPATHY_QT4_NO_EXPORT void onNewAccount(const Tp::AccountPtr &account);
+
+private:
+ friend class AccountManager;
+
+ TELEPATHY_QT4_NO_EXPORT PendingAccount(const AccountManagerPtr &manager,
+ const QString &connectionManager, const QString &protocol,
+ const QString &displayName, const QVariantMap &parameters,
+ const QVariantMap &properties = QVariantMap());
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-channel-request-internal.h b/qt4/TelepathyQt4/pending-channel-request-internal.h
new file mode 100644
index 000000000..b0dc18617
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-channel-request-internal.h
@@ -0,0 +1,73 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_channel_request_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_channel_request_internal_h_HEADER_GUARD_
+
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT PendingChannelRequestCancelOperation : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingChannelRequestCancelOperation)
+
+public:
+ PendingChannelRequestCancelOperation()
+ : PendingOperation(SharedPtr<Object>())
+ {
+ }
+
+ ~PendingChannelRequestCancelOperation()
+ {
+ }
+
+ void go(const ChannelRequestPtr &channelRequest)
+ {
+ Q_ASSERT(mChannelRequest.isNull());
+ mChannelRequest = channelRequest;
+ connect(mChannelRequest->cancel(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onCancelOperationFinished(Tp::PendingOperation*)));
+ }
+
+private Q_SLOTS:
+ void onCancelOperationFinished(Tp::PendingOperation *op)
+ {
+ if (op->isError()) {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+ setFinished();
+ }
+
+private:
+ ChannelRequestPtr mChannelRequest;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-channel-request.cpp b/qt4/TelepathyQt4/pending-channel-request.cpp
new file mode 100644
index 000000000..c648365e2
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-channel-request.cpp
@@ -0,0 +1,276 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingChannelRequest>
+#include "TelepathyQt4/pending-channel-request-internal.h"
+
+#include "TelepathyQt4/_gen/pending-channel-request.moc.hpp"
+#include "TelepathyQt4/_gen/pending-channel-request-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ChannelDispatcher>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/ChannelRequestHints>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingReady>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingChannelRequest::Private
+{
+ Private(const QDBusConnection &dbusConnection)
+ : dbusConnection(dbusConnection),
+ cancelOperation(0)
+ {
+ }
+
+ QDBusConnection dbusConnection;
+ ChannelRequestPtr channelRequest;
+ PendingChannelRequestCancelOperation *cancelOperation;
+};
+
+/**
+ * \class PendingChannelRequest
+ * \ingroup clientchannelrequest
+ * \headerfile TelepathyQt4/pending-channel-request.h <TelepathyQt4/PendingChannelRequest>
+ *
+ * \brief The PendingChannelRequest class represents the parameters of and
+ * the reply to an asynchronous ChannelRequest request.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is trough Account.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingChannelRequest object.
+ *
+ * \param account Account to use.
+ * \param requestedProperties A dictionary containing the desirable properties.
+ * \param userActionTime The time at which user action occurred, or QDateTime()
+ * if this channel request is for some reason not
+ * involving user action.
+ * \param preferredHandler Either the well-known bus name (starting with
+ * org.freedesktop.Telepathy.Client.) of the preferred
+ * handler for this channel, or an empty string to
+ * indicate that any handler would be acceptable.
+ * \param create Whether createChannel or ensureChannel should be called.
+ * \param account The account the request was made through.
+ */
+PendingChannelRequest::PendingChannelRequest(const AccountPtr &account,
+ const QVariantMap &requestedProperties, const QDateTime &userActionTime,
+ const QString &preferredHandler, bool create, const ChannelRequestHints &hints)
+ : PendingOperation(account),
+ mPriv(new Private(account->dbusConnection()))
+{
+ QString channelDispatcherObjectPath =
+ QString(QLatin1String("/%1")).arg(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER));
+ channelDispatcherObjectPath.replace(QLatin1String("."), QLatin1String("/"));
+ Client::ChannelDispatcherInterface *channelDispatcherInterface =
+ account->dispatcherInterface();
+
+ QDBusPendingCallWatcher *watcher = 0;
+ if (create) {
+ if (hints.isValid()) {
+ if (account->supportsRequestHints()) {
+ watcher = new QDBusPendingCallWatcher(
+ channelDispatcherInterface->CreateChannelWithHints(
+ QDBusObjectPath(account->objectPath()),
+ requestedProperties,
+ userActionTime.isNull() ? 0 : userActionTime.toTime_t(),
+ preferredHandler, hints.allHints()), this);
+ } else {
+ warning() << "Hints passed to channel request won't have an effect"
+ << "because the Channel Dispatcher service in use is too old";
+ }
+ }
+
+ if (!watcher) {
+ watcher = new QDBusPendingCallWatcher(
+ channelDispatcherInterface->CreateChannel(
+ QDBusObjectPath(account->objectPath()),
+ requestedProperties,
+ userActionTime.isNull() ? 0 : userActionTime.toTime_t(),
+ preferredHandler), this);
+ }
+ } else {
+ if (hints.isValid()) {
+ if (account->supportsRequestHints()) {
+ watcher = new QDBusPendingCallWatcher(
+ channelDispatcherInterface->EnsureChannelWithHints(
+ QDBusObjectPath(account->objectPath()),
+ requestedProperties,
+ userActionTime.isNull() ? 0 : userActionTime.toTime_t(),
+ preferredHandler, hints.allHints()), this);
+ } else {
+ warning() << "Hints passed to channel request won't have an effect"
+ << "because the Channel Dispatcher service in use is too old";
+ }
+ }
+
+ if (!watcher) {
+ watcher = new QDBusPendingCallWatcher(
+ channelDispatcherInterface->EnsureChannel(
+ QDBusObjectPath(account->objectPath()),
+ requestedProperties,
+ userActionTime.isNull() ? 0 : userActionTime.toTime_t(),
+ preferredHandler), this);
+ }
+ }
+
+ // TODO: This is a Qt bug fixed upstream, should be in the next Qt release.
+ // We should not need to check watcher->isFinished() here, remove the
+ // check when we depend on the fixed Qt version.
+ if (watcher->isFinished()) {
+ onWatcherFinished(watcher);
+ } else {
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onWatcherFinished(QDBusPendingCallWatcher*)));
+ }
+}
+
+/**
+ * Construct a new PendingChannelRequest object that always fails.
+ *
+ * \param account Account to use.
+ * \param errorName The name of a D-Bus error.
+ * \param errorMessage The error message.
+ */
+PendingChannelRequest::PendingChannelRequest(const AccountPtr &account,
+ const QString &errorName, const QString &errorMessage)
+ : PendingOperation(ConnectionPtr()),
+ mPriv(new Private(account->dbusConnection()))
+{
+ setFinishedWithError(errorName, errorMessage);
+}
+
+/**
+ * Class destructor.
+ */
+PendingChannelRequest::~PendingChannelRequest()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account through which the request was made.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr PendingChannelRequest::account() const
+{
+ return AccountPtr(qobject_cast<Account*>((Account*) _object().data()));
+}
+
+ChannelRequestPtr PendingChannelRequest::channelRequest() const
+{
+ return mPriv->channelRequest;
+}
+
+PendingOperation *PendingChannelRequest::cancel()
+{
+ if (isFinished()) {
+ // CR has already succeeded or failed, so let's just fail here
+ return new PendingFailure(QLatin1String(TELEPATHY_DBUS_ERROR_UNKNOWN_METHOD),
+ QLatin1String("ChannnelRequest already finished"),
+ _object());
+ }
+
+ if (!mPriv->cancelOperation) {
+ mPriv->cancelOperation = new PendingChannelRequestCancelOperation();
+ connect(mPriv->cancelOperation,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onCancelOperationFinished(Tp::PendingOperation*)));
+
+ if (mPriv->channelRequest) {
+ mPriv->cancelOperation->go(mPriv->channelRequest);
+ }
+ }
+
+ return mPriv->cancelOperation;
+}
+
+void PendingChannelRequest::onWatcherFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QDBusObjectPath> reply = *watcher;
+
+ if (!reply.isError()) {
+ QDBusObjectPath objectPath = reply.argumentAt<0>();
+ debug() << "Got reply to ChannelDispatcher.Ensure/CreateChannel "
+ "- object path:" << objectPath.path();
+
+ if (!account().isNull()) {
+ mPriv->channelRequest = ChannelRequest::create(account(),
+ objectPath.path(), QVariantMap());
+ }
+
+ if (mPriv->cancelOperation) {
+ mPriv->cancelOperation->go(mPriv->channelRequest);
+ } else {
+ emit channelRequestCreated(mPriv->channelRequest);
+
+ connect(mPriv->channelRequest.data(),
+ SIGNAL(failed(QString,QString)),
+ SLOT(setFinishedWithError(QString,QString)));
+ connect(mPriv->channelRequest.data(),
+ SIGNAL(succeeded(Tp::ChannelPtr)),
+ SLOT(setFinished()));
+
+ connect(mPriv->channelRequest->proceed(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onProceedOperationFinished(Tp::PendingOperation*)));
+ }
+ } else {
+ debug().nospace() << "Ensure/CreateChannel failed:" <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingChannelRequest::onProceedOperationFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ }
+}
+
+void PendingChannelRequest::onCancelOperationFinished(PendingOperation *op)
+{
+ mPriv->cancelOperation = 0;
+ if (!isFinished()) {
+ setFinishedWithError(QLatin1String(TELEPATHY_ERROR_CANCELLED),
+ QLatin1String("ChannelRequest cancelled"));
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-channel-request.h b/qt4/TelepathyQt4/pending-channel-request.h
new file mode 100644
index 000000000..753ebb990
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-channel-request.h
@@ -0,0 +1,84 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_channel_request_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_channel_request_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+#include <QDateTime>
+#include <QString>
+#include <QVariantMap>
+
+class QDBusPendingCallWatcher;
+
+namespace Tp
+{
+
+class Account;
+class ChannelRequestHints;
+
+class TELEPATHY_QT4_EXPORT PendingChannelRequest : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingChannelRequest)
+
+public:
+ ~PendingChannelRequest();
+
+ AccountPtr account() const;
+
+ ChannelRequestPtr channelRequest() const;
+
+ PendingOperation *cancel();
+
+Q_SIGNALS:
+ void channelRequestCreated(const Tp::ChannelRequestPtr &channelRequest);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onWatcherFinished(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onProceedOperationFinished(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onCancelOperationFinished(Tp::PendingOperation *op);
+
+private:
+ friend class Account;
+
+ TELEPATHY_QT4_NO_EXPORT PendingChannelRequest(const AccountPtr &account,
+ const QVariantMap &requestedProperties, const QDateTime &userActionTime,
+ const QString &preferredHandler, bool create, const ChannelRequestHints &hints);
+ TELEPATHY_QT4_NO_EXPORT PendingChannelRequest(const AccountPtr &account,
+ const QString &errorName, const QString &errorMessage);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-channel.cpp b/qt4/TelepathyQt4/pending-channel.cpp
new file mode 100644
index 000000000..c968ab7eb
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-channel.cpp
@@ -0,0 +1,555 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingChannel>
+
+#include "TelepathyQt4/_gen/pending-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/fake-handler-manager-internal.h"
+#include "TelepathyQt4/request-temporary-handler-internal.h"
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/HandledChannelNotifier>
+#include <TelepathyQt4/PendingChannelRequest>
+#include <TelepathyQt4/PendingReady>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingChannel::Private
+{
+ class FakeAccountFactory;
+
+ ConnectionPtr connection;
+ bool create;
+ bool yours;
+ QString channelType;
+ uint handleType;
+ uint handle;
+ QVariantMap immutableProperties;
+ ChannelPtr channel;
+
+ ClientRegistrarPtr cr;
+ SharedPtr<RequestTemporaryHandler> handler;
+ HandledChannelNotifier *notifier;
+ static uint numHandlers;
+};
+
+uint PendingChannel::Private::numHandlers = 0;
+
+class TELEPATHY_QT4_NO_EXPORT PendingChannel::Private::FakeAccountFactory : public AccountFactory
+{
+public:
+ static AccountFactoryPtr create(const AccountPtr &account)
+ {
+ return AccountFactoryPtr(new FakeAccountFactory(account));
+ }
+
+ ~FakeAccountFactory() { }
+
+ AccountPtr account() const { return mAccount; }
+
+protected:
+ AccountPtr construct(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const
+ {
+ if (mAccount->objectPath() != objectPath) {
+ warning() << "Account received by the fake factory is different from original account";
+ }
+ return mAccount;
+ }
+
+private:
+ FakeAccountFactory(const AccountPtr &account)
+ : AccountFactory(account->dbusConnection(), Features()),
+ mAccount(account)
+ {
+ }
+
+ AccountPtr mAccount;
+};
+
+/**
+ * \class PendingChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/pending-channel.h <TelepathyQt4/PendingChannel>
+ *
+ * \brief The PendingChannel class represents the parameters of and the reply to
+ * an asynchronous channel request.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is trough Connection or Account.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingChannel object that will fail immediately.
+ *
+ * \param connection Connection to use.
+ * \param errorName The error name.
+ * \param errorMessage The error message.
+ */
+PendingChannel::PendingChannel(const ConnectionPtr &connection, const QString &errorName,
+ const QString &errorMessage)
+ : PendingOperation(connection),
+ mPriv(new Private)
+{
+ mPriv->connection = connection;
+ mPriv->yours = false;
+ mPriv->handleType = 0;
+ mPriv->handle = 0;
+ mPriv->notifier = 0;
+ mPriv->create = false;
+
+ setFinishedWithError(errorName, errorMessage);
+}
+
+/**
+ * Construct a new PendingChannel object.
+ *
+ * \param connection Connection to use.
+ * \param request A dictionary containing the desirable properties.
+ * \param create Whether createChannel or ensureChannel should be called.
+ */
+PendingChannel::PendingChannel(const ConnectionPtr &connection,
+ const QVariantMap &request, bool create, int timeout)
+ : PendingOperation(connection),
+ mPriv(new Private)
+{
+ mPriv->connection = connection;
+ mPriv->yours = create;
+ mPriv->channelType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ mPriv->handleType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
+ mPriv->handle = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();
+ mPriv->notifier = 0;
+ mPriv->create = create;
+
+ Client::ConnectionInterfaceRequestsInterface *requestsInterface =
+ connection->interface<Client::ConnectionInterfaceRequestsInterface>();
+ if (create) {
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ requestsInterface->CreateChannel(request, timeout), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onConnectionCreateChannelFinished(QDBusPendingCallWatcher*)));
+ }
+ else {
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ requestsInterface->EnsureChannel(request, timeout), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onConnectionEnsureChannelFinished(QDBusPendingCallWatcher*)));
+ }
+}
+
+PendingChannel::PendingChannel(const AccountPtr &account,
+ const QVariantMap &request, const QDateTime &userActionTime,
+ bool create)
+ : PendingOperation(account),
+ mPriv(new Private)
+{
+ mPriv->yours = true;
+ mPriv->channelType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ mPriv->handleType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
+ mPriv->handle = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();
+
+ mPriv->cr = ClientRegistrar::create(
+ Private::FakeAccountFactory::create(account),
+ account->connectionFactory(),
+ account->channelFactory(),
+ account->contactFactory());
+ mPriv->handler = RequestTemporaryHandler::create(account);
+ mPriv->notifier = 0;
+ mPriv->create = create;
+
+ QString handlerName = QString(QLatin1String("TpQt4RaH_%1_%2"))
+ .arg(account->dbusConnection().baseService()
+ .replace(QLatin1String(":"), QLatin1String("_"))
+ .replace(QLatin1String("."), QLatin1String("_")))
+ .arg(Private::numHandlers++);
+ if (!mPriv->cr->registerClient(mPriv->handler, handlerName, false)) {
+ warning() << "Unable to register handler" << handlerName;
+ setFinishedWithError(TP_QT4_ERROR_NOT_AVAILABLE,
+ QLatin1String("Unable to register handler"));
+ return;
+ }
+
+ connect(mPriv->handler.data(),
+ SIGNAL(error(QString,QString)),
+ SLOT(onHandlerError(QString,QString)));
+ connect(mPriv->handler.data(),
+ SIGNAL(channelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onHandlerChannelReceived(Tp::ChannelPtr)));
+
+ handlerName = QString(QLatin1String("org.freedesktop.Telepathy.Client.%1")).arg(handlerName);
+
+ debug() << "Requesting channel through account using handler" << handlerName;
+ PendingChannelRequest *pcr;
+ if (create) {
+ pcr = account->createChannel(request, userActionTime, handlerName, ChannelRequestHints());
+ } else {
+ pcr = account->ensureChannel(request, userActionTime, handlerName, ChannelRequestHints());
+ }
+ connect(pcr,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAccountCreateChannelFinished(Tp::PendingOperation*)));
+}
+
+/**
+ * Construct a new PendingChannel object that will fail immediately.
+ *
+ * \param errorName The name of a D-Bus error.
+ * \param errorMessage The error message.
+ */
+PendingChannel::PendingChannel(const QString &errorName, const QString &errorMessage)
+ : PendingOperation(ConnectionPtr()), mPriv(new Private)
+{
+ setFinishedWithError(errorName, errorMessage);
+}
+
+/**
+ * Class destructor.
+ */
+PendingChannel::~PendingChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection through which the channel request was made.
+ *
+ * Note that if this channel request was created through Account, a null ConnectionPtr will be
+ * returned.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr PendingChannel::connection() const
+{
+ return mPriv->connection;
+}
+
+/**
+ * Return whether this channel belongs to this process.
+ *
+ * If \c false, the caller must assume that some other process is
+ * handling this channel; if \c true, the caller should handle it
+ * themselves or delegate it to another client.
+ *
+ * \return \c true if it belongs, \c false otherwise.
+ */
+bool PendingChannel::yours() const
+{
+ if (!isFinished()) {
+ warning() << "PendingChannel::yours called before finished, returning undefined value";
+ }
+ else if (!isValid()) {
+ warning() << "PendingChannel::yours called when not valid, returning undefined value";
+ }
+
+ return mPriv->yours;
+}
+
+/**
+ * Return the channel type specified in the channel request.
+ *
+ * \return The D-Bus interface name for the type of the channel.
+ */
+const QString &PendingChannel::channelType() const
+{
+ return mPriv->channelType;
+}
+
+/**
+ * If the channel request has finished, return the handle type of the resulting
+ * channel. Otherwise, return the handle type that was requested.
+ *
+ * (One example of a request producing a different target handle type is that
+ * on protocols like MSN, one-to-one conversations don't really exist, and if
+ * you request a text channel with handle type HandleTypeContact, what you
+ * will actually get is a text channel with handle type HandleTypeNone, with
+ * the requested contact as a member.)
+ *
+ * \return The target handle type as #HandleType.
+ * \sa targetHandle()
+ */
+uint PendingChannel::targetHandleType() const
+{
+ return mPriv->handleType;
+}
+
+/**
+ * If the channel request has finished, return the target handle of the
+ * resulting channel. Otherwise, return the target handle that was requested
+ * (which might be different in some situations - see targetHandleType()).
+ *
+ * \return An integer representing the target handle, which is of the type
+ * targetHandleType() indicates.
+ * \sa targetHandleType()
+ */
+uint PendingChannel::targetHandle() const
+{
+ return mPriv->handle;
+}
+
+/**
+ * If this channel request has finished, return the immutable properties of
+ * the resulting channel. Otherwise, return an empty map.
+ *
+ * The keys and values in this map are defined by the \telepathy_spec,
+ * or by third-party extensions to that specification.
+ * These are the properties that cannot change over the lifetime of the
+ * channel; they're announced in the result of the request, for efficiency.
+ * This map should be passed to the constructor of Channel or its subclasses
+ * (such as TextChannel).
+ *
+ * These properties can also be used to process channels in a way that does
+ * not require the creation of a Channel object - for instance, a
+ * ChannelDispatcher implementation should be able to classify and process
+ * channels based on their immutable properties, without needing to create
+ * Channel objects.
+ *
+ * \return The immutable properties as QVariantMap.
+ */
+QVariantMap PendingChannel::immutableProperties() const
+{
+ QVariantMap props = mPriv->immutableProperties;
+
+ // This is a reasonable guess - if it's Yours it's guaranteedly Requested by us, and if it's not
+ // it could be either Requested by somebody else but also an incoming channel just as well.
+ if (!props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"))) {
+ debug() << "CM didn't provide Requested in channel immutable props, guessing"
+ << mPriv->yours;
+ props[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")] =
+ mPriv->yours;
+ }
+
+ // Also, the spec says that if the channel was Requested by the local user, InitiatorHandle must
+ // be the Connection's self handle
+ if (!props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle"))) {
+ if (qdbus_cast<bool>(props.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")))) {
+ if (connection() && connection()->isReady(Connection::FeatureCore)) {
+ debug() << "CM didn't provide InitiatorHandle in channel immutable props, but we "
+ "know it's the conn's self handle (and have it)";
+ props[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle")] =
+ connection()->selfHandle();
+ }
+ }
+ }
+
+ return props;
+}
+
+/**
+ * Return the channel resulting from the channel request.
+ *
+ * \return A pointer to the Channel object.
+ */
+ChannelPtr PendingChannel::channel() const
+{
+ if (!isFinished()) {
+ warning() << "PendingChannel::channel called before finished, returning 0";
+ return ChannelPtr();
+ } else if (!isValid()) {
+ warning() << "PendingChannel::channel called when not valid, returning 0";
+ return ChannelPtr();
+ }
+
+ return mPriv->channel;
+}
+
+/**
+ * If this channel request has finished and was created through Account,
+ * return a HandledChannelNotifier object that will keep track of channel() being re-requested.
+ *
+ * \return A HandledChannelNotifier instance, or 0 if an error occurred.
+ */
+HandledChannelNotifier *PendingChannel::handledChannelNotifier() const
+{
+ if (!isFinished()) {
+ warning() << "PendingChannel::handledChannelNotifier called before finished, returning 0";
+ return 0;
+ } else if (!isValid()) {
+ warning() << "PendingChannel::handledChannelNotifier called when not valid, returning 0";
+ return 0;
+ }
+
+ if (mPriv->cr && !mPriv->notifier) {
+ mPriv->notifier = new HandledChannelNotifier(mPriv->cr, mPriv->handler);
+ }
+ return mPriv->notifier;
+}
+
+void PendingChannel::onConnectionCreateChannelFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QDBusObjectPath, QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ QString objectPath = reply.argumentAt<0>().path();
+ QVariantMap map = reply.argumentAt<1>();
+
+ debug() << "Got reply to Connection.CreateChannel - object path:" << objectPath;
+
+ PendingReady *channelReady =
+ connection()->channelFactory()->proxy(connection(), objectPath, map);
+ mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy());
+
+ mPriv->immutableProperties = map;
+ mPriv->channelType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ mPriv->handleType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
+ mPriv->handle = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();
+
+ connect(channelReady,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelReady(Tp::PendingOperation*)));
+ } else {
+ debug().nospace() << "CreateChannel failed:" <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingChannel::onConnectionEnsureChannelFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<bool, QDBusObjectPath, QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ mPriv->yours = reply.argumentAt<0>();
+ QString objectPath = reply.argumentAt<1>().path();
+ QVariantMap map = reply.argumentAt<2>();
+
+ debug() << "Got reply to Connection.EnsureChannel - object path:" << objectPath;
+
+ PendingReady *channelReady =
+ connection()->channelFactory()->proxy(connection(), objectPath, map);
+ mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy());
+
+ mPriv->immutableProperties = map;
+ mPriv->channelType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ mPriv->handleType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
+ mPriv->handle = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();
+
+ connect(channelReady,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelReady(Tp::PendingOperation*)));
+ } else {
+ debug().nospace() << "EnsureChannel failed:" <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingChannel::onChannelReady(PendingOperation *op)
+{
+ if (!op->isError()) {
+ setFinished();
+ } else {
+ debug() << "Making the channel ready for" << this << "failed with" << op->errorName()
+ << ":" << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ }
+}
+
+void PendingChannel::onHandlerError(const QString &errorName, const QString &errorMessage)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ warning() << "Creating/ensuring channel failed with" << errorName
+ << ":" << errorMessage;
+ setFinishedWithError(errorName, errorMessage);
+}
+
+void PendingChannel::onHandlerChannelReceived(const ChannelPtr &channel)
+{
+ if (isFinished()) {
+ warning() << "Handler received the channel but this operation already finished due "
+ "to failure in the channel request";
+ return;
+ }
+
+ mPriv->handleType = channel->targetHandleType();
+ mPriv->handle = channel->targetHandle();
+ mPriv->immutableProperties = channel->immutableProperties();
+ mPriv->channel = channel;
+
+ // register the CR in FakeHandlerManager so that at least one handler per bus stays alive
+ // until all channels requested using R&H gets invalidated/destroyed.
+ // This is important in case Mission Control happens to restart while
+ // the channel is still in use, since it will close each channel it
+ // doesn't find a handler for it.
+ FakeHandlerManager::instance()->registerClientRegistrar(mPriv->cr);
+
+ setFinished();
+}
+
+void PendingChannel::onAccountCreateChannelFinished(PendingOperation *op)
+{
+ if (isFinished()) {
+ if (isError()) {
+ warning() << "Creating/ensuring channel finished with a failure after the internal "
+ "handler already got a channel, ignoring";
+ }
+ return;
+ }
+
+ if (op->isError()) {
+ warning() << "Creating/ensuring channel failed with" << op->errorName()
+ << ":" << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ if (!mPriv->handler->isDBusHandlerInvoked()) {
+ // Our handler hasn't be called but the channel request is complete.
+ // That means another handler handled the channels so we don't own it.
+ if (mPriv->create) {
+ warning() << "Creating/ensuring channel failed with" << TP_QT4_ERROR_SERVICE_CONFUSED
+ << ":" << QLatin1String("CD.CreateChannel/WithHints returned successfully and "
+ "the handler didn't receive the channel yet");
+ setFinishedWithError(TP_QT4_ERROR_SERVICE_CONFUSED,
+ QLatin1String("CD.CreateChannel/WithHints returned successfully and "
+ "the handler didn't receive the channel yet"));
+ } else {
+ warning() << "Creating/ensuring channel failed with" << TP_QT4_ERROR_NOT_YOURS
+ << ":" << QLatin1String("Another handler is handling this channel");
+ setFinishedWithError(TP_QT4_ERROR_NOT_YOURS,
+ QLatin1String("Another handler is handling this channel"));
+ }
+ return;
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-channel.h b/qt4/TelepathyQt4/pending-channel.h
new file mode 100644
index 000000000..93781efa4
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-channel.h
@@ -0,0 +1,103 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/PendingOperation>
+
+#include <QString>
+#include <QVariantMap>
+
+#include <QDBusPendingCallWatcher>
+
+namespace Tp
+{
+
+class Connection;
+class HandledChannelNotifier;
+
+class TELEPATHY_QT4_EXPORT PendingChannel : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingChannel)
+
+public:
+ ~PendingChannel();
+
+ ConnectionPtr connection() const;
+
+ bool yours() const;
+
+ const QString &channelType() const;
+
+ uint targetHandleType() const;
+
+ uint targetHandle() const;
+
+ QVariantMap immutableProperties() const;
+
+ ChannelPtr channel() const;
+
+ HandledChannelNotifier *handledChannelNotifier() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onConnectionCreateChannelFinished(
+ QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionEnsureChannelFinished(
+ QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onChannelReady(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onHandlerError(const QString &errorName,
+ const QString &errorMessage);
+ TELEPATHY_QT4_NO_EXPORT void onHandlerChannelReceived(
+ const Tp::ChannelPtr &channel);
+ TELEPATHY_QT4_NO_EXPORT void onAccountCreateChannelFinished(
+ Tp::PendingOperation *op);
+
+private:
+ friend class Account;
+ friend class ConnectionLowlevel;
+
+ TELEPATHY_QT4_NO_EXPORT PendingChannel(const ConnectionPtr &connection,
+ const QString &errorName, const QString &errorMessage);
+ TELEPATHY_QT4_NO_EXPORT PendingChannel(const ConnectionPtr &connection,
+ const QVariantMap &request, bool create, int timeout = -1);
+ TELEPATHY_QT4_NO_EXPORT PendingChannel(const AccountPtr &account,
+ const QVariantMap &request, const QDateTime &userActionTime,
+ bool create);
+ TELEPATHY_QT4_NO_EXPORT PendingChannel(const QString &errorName,
+ const QString &errorMessage);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-connection.cpp b/qt4/TelepathyQt4/pending-connection.cpp
new file mode 100644
index 000000000..c72429ee6
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-connection.cpp
@@ -0,0 +1,171 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingConnection>
+
+#include "TelepathyQt4/_gen/pending-connection.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDBusObjectPath>
+#include <QDBusPendingCallWatcher>
+#include <QDBusPendingReply>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingConnection::Private
+{
+ ConnectionPtr connection;
+};
+
+/**
+ * \class PendingConnection
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/pending-connection.h <TelepathyQt4/PendingConnection>
+ *
+ * \brief The PendingConnection class represents the parameters of and the reply
+ * to an asynchronous connection request.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is via ConnectionManager.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingConnection object.
+ *
+ * \param manager ConnectionManager to use.
+ * \param protocol Name of the protocol to create the connection for.
+ * \param parameters Connection parameters.
+ */
+PendingConnection::PendingConnection(const ConnectionManagerPtr &manager,
+ const QString &protocol, const QVariantMap &parameters)
+ : PendingOperation(manager),
+ mPriv(new Private)
+{
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ manager->baseInterface()->RequestConnection(protocol,
+ parameters), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Construct a new PendingConnection object that will fail immediately.
+ *
+ * \param error Name of the error to fail with.
+ * \param errorMessage Detail message for the error.
+ */
+PendingConnection::PendingConnection(const QString &error, const QString &errorMessage)
+ : PendingOperation(ConnectionManagerPtr()),
+ mPriv(new Private)
+{
+ setFinishedWithError(error, errorMessage);
+}
+
+/**
+ * Class destructor.
+ */
+PendingConnection::~PendingConnection()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection manager through which the request was made.
+ *
+ * \return A pointer to the ConnectionManager object.
+ */
+ConnectionManagerPtr PendingConnection::manager() const
+{
+ return ConnectionManagerPtr(qobject_cast<ConnectionManager*>((ConnectionManager*) _object().data()));
+}
+
+/**
+ * Return the connection resulting from the connection request.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr PendingConnection::connection() const
+{
+ if (!isFinished()) {
+ warning() << "PendingConnection::connection called before finished, returning 0";
+ return ConnectionPtr();
+ } else if (!isValid()) {
+ warning() << "PendingConnection::connection called when not valid, returning 0";
+ return ConnectionPtr();
+ }
+
+ return mPriv->connection;
+}
+
+void PendingConnection::onCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QString, QDBusObjectPath> reply = *watcher;
+
+ if (!reply.isError()) {
+ QString busName = reply.argumentAt<0>();
+ QString objectPath = reply.argumentAt<1>().path();
+
+ debug() << "Got reply to ConnectionManager.CreateConnection - bus name:" <<
+ busName << "- object path:" << objectPath;
+
+ PendingReady *readyOp = manager()->connectionFactory()->proxy(busName,
+ objectPath, manager()->channelFactory(), manager()->contactFactory());
+ mPriv->connection = ConnectionPtr::qObjectCast(readyOp->proxy());
+ connect(readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onConnectionBuilt(Tp::PendingOperation*)));
+ } else {
+ debug().nospace() <<
+ "CreateConnection failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingConnection::onConnectionBuilt(Tp::PendingOperation *op)
+{
+ Q_ASSERT(op->isFinished());
+
+ if (op->isError()) {
+ warning() << "Making connection ready using the factory failed:" <<
+ op->errorName() << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ } else {
+ setFinished();
+ debug() << "New connection" << mPriv->connection->objectPath() << "built";
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-connection.h b/qt4/TelepathyQt4/pending-connection.h
new file mode 100644
index 000000000..eca3e7301
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-connection.h
@@ -0,0 +1,73 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_connection_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_connection_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/PendingOperation>
+
+#include <QString>
+#include <QVariantMap>
+
+class QDBusPendingCallWatcher;
+
+namespace Tp
+{
+
+class ConnectionManager;
+
+class TELEPATHY_QT4_EXPORT PendingConnection : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingConnection);
+
+public:
+ ~PendingConnection();
+
+ ConnectionManagerPtr manager() const;
+
+ ConnectionPtr connection() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionBuilt(Tp::PendingOperation *op);
+
+private:
+ friend class ConnectionManagerLowlevel;
+
+ TELEPATHY_QT4_NO_EXPORT PendingConnection(const ConnectionManagerPtr &manager,
+ const QString &protocol, const QVariantMap &parameters);
+ TELEPATHY_QT4_NO_EXPORT PendingConnection(const QString &error, const QString &errorMessage);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-contact-attributes.cpp b/qt4/TelepathyQt4/pending-contact-attributes.cpp
new file mode 100644
index 000000000..4fcbff4e1
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-contact-attributes.cpp
@@ -0,0 +1,219 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingContactAttributes>
+
+#include "TelepathyQt4/_gen/pending-contact-attributes.moc.hpp"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include "TelepathyQt4/debug-internal.h"
+
+namespace Tp
+{
+
+/**
+ * \class PendingContactAttributes
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/pending-contact-attributes.h <TelepathyQt4/PendingContactAttributes>
+ *
+ * \brief The PendingContactAttributes class represents the parameters of and
+ * the reply to an asynchronous request for raw contact attributes, as used in
+ * the ConnectionLowlevel::contactAttributes() low-level convenience method wrapping the
+ * Client::ConnectionInterfaceContactsInterface::GetContactAttributes() D-Bus
+ * method.
+ *
+ * See \ref async_model
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT PendingContactAttributes::Private
+{
+ UIntList contactsRequested;
+ QStringList interfacesRequested;
+ bool shouldReference;
+ ReferencedHandles validHandles;
+ UIntList invalidHandles;
+ ContactAttributesMap attributes;
+};
+
+PendingContactAttributes::PendingContactAttributes(const ConnectionPtr &connection,
+ const UIntList &handles, const QStringList &interfaces, bool reference)
+ : PendingOperation(connection),
+ mPriv(new Private)
+{
+ mPriv->contactsRequested = handles;
+ mPriv->interfacesRequested = interfaces;
+ mPriv->shouldReference = reference;
+}
+
+/**
+ * Class destructor.
+ */
+PendingContactAttributes::~PendingContactAttributes()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection through which the request was made.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr PendingContactAttributes::connection() const
+{
+ return ConnectionPtr(qobject_cast<Connection*>((Connection*) _object().data()));
+}
+
+/**
+ * Return the contacts for which attributes were requested.
+ *
+ * \return Reference to a list with the handles of the contacts.
+ */
+const UIntList &PendingContactAttributes::contactsRequested() const
+{
+ return mPriv->contactsRequested;
+}
+
+/**
+ * Return the interfaces the corresponding attributes of which were requested.
+ *
+ * \return Reference to a list of D-Bus interface names.
+ */
+const QStringList &PendingContactAttributes::interfacesRequested() const
+{
+ return mPriv->interfacesRequested;
+}
+
+/**
+ * Return whether it was requested that the contact handles should be referenced in addition to
+ * fetching their attributes. This corresponds to the <code>reference</code> argument to
+ * Connection::contactAttributes().
+ *
+ * \return Whether the handles should be referenced or not.
+ */
+bool PendingContactAttributes::shouldReference() const
+{
+ return mPriv->shouldReference;
+}
+
+/**
+ * If referencing the handles was requested (as indicated by shouldReference()), returns the
+ * now-referenced handles resulting from the operation. If the operation has not (yet) finished
+ * successfully (isFinished() returns <code>false</code>), or referencing was not requested, the
+ * return value is undefined.
+ *
+ * Even if referencing was requested, the list will not always contain all of the handles in
+ * contactsRequested(), only the ones which were valid. The valid handles will be in the same order
+ * as in contactsRequested(), though.
+ *
+ * \return ReferencedHandles instance containing the handles.
+ */
+ReferencedHandles PendingContactAttributes::validHandles() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContactAttributes::validHandles() called before finished";
+ } else if (isError()) {
+ warning() << "PendingContactAttributes::validHandles() called when errored";
+ } else if (!shouldReference()) {
+ warning() << "PendingContactAttributes::validHandles() called but weren't asked to"
+ << "reference handles";
+ }
+
+ return mPriv->validHandles;
+}
+
+/**
+ * Return the handles which were found to be invalid while processing the operation. If the
+ * operation has not (yet) finished successfully (isFinished() returns <code>false</code>), the
+ * return value is undefined.
+ *
+ * \return A list with the invalid handles.
+ */
+UIntList PendingContactAttributes::invalidHandles() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContactAttributes::validHandles() called before finished";
+ } else if (isError()) {
+ warning() << "PendingContactAttributes::validHandles() called when errored";
+ }
+
+ return mPriv->invalidHandles;
+}
+
+/**
+ * Return a dictionary mapping the valid contact handles in contactsRequested() (when also
+ * referencing, this means the contents of validHandles()) to contact attributes. If the operation
+ * has not (yet) finished successfully (isFinished() returns <code>false</code>), the return value
+ * is undefined.
+ *
+ * \return Mapping from handles to variant maps containing the attributes.
+ */
+ContactAttributesMap PendingContactAttributes::attributes() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContactAttributes::validHandles() called before finished";
+ } else if (isError()) {
+ warning() << "PendingContactAttributes::validHandles() called when errored";
+ }
+
+ return mPriv->attributes;
+}
+
+void PendingContactAttributes::onCallFinished(QDBusPendingCallWatcher* watcher)
+{
+ QDBusPendingReply<ContactAttributesMap> reply = *watcher;
+
+ if (reply.isError()) {
+ debug().nospace() << "GetCAs: error " << reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ } else {
+ mPriv->attributes = reply.value();
+
+ UIntList validHandles;
+ foreach (uint contact, mPriv->contactsRequested) {
+ if (mPriv->attributes.contains(contact)) {
+ validHandles << contact;
+ } else {
+ mPriv->invalidHandles << contact;
+ }
+ }
+
+ if (shouldReference()) {
+ mPriv->validHandles = ReferencedHandles(connection(), HandleTypeContact,
+ validHandles);
+ }
+
+ setFinished();
+ }
+
+ connection()->handleRequestLanded(HandleTypeContact);
+
+ watcher->deleteLater();
+}
+
+void PendingContactAttributes::failImmediately(const QString &error, const QString &errorMessage)
+{
+ setFinishedWithError(error, errorMessage);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-contact-attributes.h b/qt4/TelepathyQt4/pending-contact-attributes.h
new file mode 100644
index 000000000..e44a3693b
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-contact-attributes.h
@@ -0,0 +1,77 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_contact_attributes_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_contact_attributes_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class ReferencedHandles;
+
+class TELEPATHY_QT4_EXPORT PendingContactAttributes : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingContactAttributes)
+
+public:
+ ~PendingContactAttributes();
+
+ ConnectionPtr connection() const;
+
+ const UIntList &contactsRequested() const;
+ const QStringList &interfacesRequested() const;
+ bool shouldReference() const;
+
+ ReferencedHandles validHandles() const;
+ UIntList invalidHandles() const;
+ ContactAttributesMap attributes() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher);
+
+private:
+ friend class ConnectionLowlevel;
+
+ TELEPATHY_QT4_NO_EXPORT PendingContactAttributes(const ConnectionPtr &connection,
+ const UIntList &handles,
+ const QStringList &interfaces, bool reference);
+
+ TELEPATHY_QT4_NO_EXPORT void failImmediately(const QString &error, const QString &errorMessage);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-contact-info.cpp b/qt4/TelepathyQt4/pending-contact-info.cpp
new file mode 100644
index 000000000..87d6a39ed
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-contact-info.cpp
@@ -0,0 +1,128 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingContactInfo>
+
+#include "TelepathyQt4/_gen/pending-contact-info.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactManager>
+
+#include <QDBusPendingReply>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingContactInfo::Private
+{
+ Contact::InfoFields info;
+};
+
+/**
+ * \class PendingContactInfo
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/pending-contact-info.h <TelepathyQt4/PendingContactInfo>
+ *
+ * \brief The PendingContactInfo class represents the parameters of and the
+ * reply to an asynchronous contact info request.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is via Contact.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingContactInfo object.
+ *
+ * \param contact Contact to use.
+ */
+PendingContactInfo::PendingContactInfo(const ContactPtr &contact)
+ : PendingOperation(contact),
+ mPriv(new Private)
+{
+ ConnectionPtr connection = contact->manager()->connection();
+ Client::ConnectionInterfaceContactInfoInterface *contactInfoInterface =
+ connection->interface<Client::ConnectionInterfaceContactInfoInterface>();
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ contactInfoInterface->RequestContactInfo(
+ contact->handle()[0]), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingContactInfo::~PendingContactInfo()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the contact through which the request was made.
+ *
+ * \return A pointer to the Contact object.
+ */
+ContactPtr PendingContactInfo::contact() const
+{
+ return ContactPtr(qobject_cast<Contact*>((Contact*) _object().data()));
+}
+
+/**
+ * Return the information for contact().
+ *
+ * \return The contact infor as a Contact::InfoFields object.
+ */
+Contact::InfoFields PendingContactInfo::infoFields() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContactInfo::info called before finished";
+ } else if (!isValid()) {
+ warning() << "PendingContactInfo::info called when not valid";
+ }
+
+ return mPriv->info;
+}
+
+void PendingContactInfo::onCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<Tp::ContactInfoFieldList> reply = *watcher;
+
+ if (!reply.isError()) {
+ mPriv->info = Contact::InfoFields(reply.value());
+ debug() << "Got reply to ContactInfo.RequestContactInfo";
+ setFinished();
+ } else {
+ debug().nospace() <<
+ "ContactInfo.RequestContactInfo failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-contact-info.h b/qt4/TelepathyQt4/pending-contact-info.h
new file mode 100644
index 000000000..fb2e23623
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-contact-info.h
@@ -0,0 +1,66 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_contact_info_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_contact_info_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+class QDBusPendingCallWatcher;
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT PendingContactInfo : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingContactInfo);
+
+public:
+ ~PendingContactInfo();
+
+ ContactPtr contact() const;
+
+ Contact::InfoFields infoFields() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher);
+
+private:
+ friend class Contact;
+
+ TELEPATHY_QT4_NO_EXPORT PendingContactInfo(const ContactPtr &contact);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-contacts.cpp b/qt4/TelepathyQt4/pending-contacts.cpp
new file mode 100644
index 000000000..061147b5d
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-contacts.cpp
@@ -0,0 +1,468 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingContacts>
+#include "TelepathyQt4/_gen/pending-contacts.moc.hpp"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingContactAttributes>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include "TelepathyQt4/debug-internal.h"
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingContacts::Private
+{
+ enum RequestType
+ {
+ ForHandles,
+ ForIdentifiers,
+ Upgrade
+ };
+
+ Private(PendingContacts *parent, const ContactManagerPtr &manager, const UIntList &handles,
+ const Features &features, const Features &missingFeatures,
+ const QMap<uint, ContactPtr> &satisfyingContacts)
+ : parent(parent),
+ manager(manager),
+ features(features),
+ missingFeatures(missingFeatures),
+ satisfyingContacts(satisfyingContacts),
+ requestType(ForHandles),
+ handles(handles),
+ nested(0)
+ {
+ }
+
+ Private(PendingContacts *parent, const ContactManagerPtr &manager, const QStringList &identifiers,
+ const Features &features)
+ : parent(parent),
+ manager(manager),
+ features(features),
+ requestType(ForIdentifiers),
+ identifiers(identifiers),
+ nested(0)
+ {
+ }
+
+ Private(PendingContacts *parent,
+ const ContactManagerPtr &manager, const QList<ContactPtr> &contactsToUpgrade,
+ const Features &features)
+ : parent(parent),
+ manager(manager),
+ features(features),
+ requestType(Upgrade),
+ contactsToUpgrade(contactsToUpgrade),
+ nested(0)
+ {
+ }
+
+ void setFinished();
+
+ // Public object
+ PendingContacts *parent;
+
+ // Generic parameters
+ ContactManagerPtr manager;
+ Features features;
+ Features missingFeatures;
+ QMap<uint, ContactPtr> satisfyingContacts;
+
+ // Request type specific parameters
+ RequestType requestType;
+ UIntList handles;
+ QStringList identifiers;
+ QList<ContactPtr> contactsToUpgrade;
+ PendingContacts *nested;
+
+ // Results
+ QList<ContactPtr> contacts;
+ UIntList invalidHandles;
+ QStringList validIds;
+ QHash<QString, QPair<QString, QString> > invalidIds;
+
+ ReferencedHandles handlesToInspect;
+};
+
+void PendingContacts::Private::setFinished()
+{
+ ConnectionLowlevelPtr connLowlevel = manager->connection()->lowlevel();
+ UIntList handles = invalidHandles;
+ foreach (uint handle, handles) {
+ if (connLowlevel->hasContactId(handle)) {
+ satisfyingContacts.insert(handle, manager->ensureContact(handle,
+ connLowlevel->contactId(handle), missingFeatures));
+ invalidHandles.removeOne(handle);
+ }
+ }
+
+ parent->setFinished();
+}
+
+/**
+ * \class PendingContacts
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/pending-contacts.h <TelepathyQt4/PendingContacts>
+ *
+ * \brief The PendingContacts class is used by ContactManager when
+ * creating/updating Contact objects.
+ *
+ * See \ref async_model
+ */
+
+PendingContacts::PendingContacts(const ContactManagerPtr &manager,
+ const UIntList &handles,
+ const Features &features,
+ const Features &missingFeatures,
+ const QStringList &interfaces,
+ const QMap<uint, ContactPtr> &satisfyingContacts,
+ const QSet<uint> &otherContacts,
+ const QString &errorName,
+ const QString &errorMessage)
+ : PendingOperation(manager->connection()),
+ mPriv(new Private(this, manager, handles, features, missingFeatures, satisfyingContacts))
+{
+ if (!errorName.isEmpty()) {
+ setFinishedWithError(errorName, errorMessage);
+ return;
+ }
+
+ if (!otherContacts.isEmpty()) {
+ ConnectionPtr conn = manager->connection();
+ if (conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) {
+ PendingContactAttributes *attributes =
+ conn->lowlevel()->contactAttributes(otherContacts.toList(),
+ interfaces, true);
+
+ connect(attributes,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAttributesFinished(Tp::PendingOperation*)));
+ } else {
+ // fallback to just create the contacts
+ PendingHandles *handles = conn->lowlevel()->referenceHandles(HandleTypeContact,
+ otherContacts.toList());
+ connect(handles,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onReferenceHandlesFinished(Tp::PendingOperation*)));
+ }
+ } else {
+ allAttributesFetched();
+ }
+}
+
+PendingContacts::PendingContacts(const ContactManagerPtr &manager,
+ const QStringList &identifiers, const Features &features,
+ const QString &errorName, const QString &errorMessage)
+ : PendingOperation(manager->connection()),
+ mPriv(new Private(this, manager, identifiers, features))
+{
+ if (!errorName.isEmpty()) {
+ setFinishedWithError(errorName, errorMessage);
+ return;
+ }
+
+ ConnectionPtr conn = manager->connection();
+ PendingHandles *handles = conn->lowlevel()->requestHandles(HandleTypeContact, identifiers);
+
+ connect(handles,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onRequestHandlesFinished(Tp::PendingOperation*)));
+}
+
+PendingContacts::PendingContacts(const ContactManagerPtr &manager,
+ const QList<ContactPtr> &contacts, const Features &features,
+ const QString &errorName, const QString &errorMessage)
+ : PendingOperation(manager->connection()),
+ mPriv(new Private(this, manager, contacts, features))
+{
+ if (!errorName.isEmpty()) {
+ setFinishedWithError(errorName, errorMessage);
+ return;
+ }
+
+ UIntList handles;
+ foreach (const ContactPtr &contact, contacts) {
+ handles.push_back(contact->handle()[0]);
+ }
+
+ mPriv->nested = manager->contactsForHandles(handles, features);
+ connect(mPriv->nested,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onNestedFinished(Tp::PendingOperation*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingContacts::~PendingContacts()
+{
+ delete mPriv;
+}
+
+ContactManagerPtr PendingContacts::manager() const
+{
+ return mPriv->manager;
+}
+
+Features PendingContacts::features() const
+{
+ return mPriv->features;
+}
+
+bool PendingContacts::isForHandles() const
+{
+ return mPriv->requestType == Private::ForHandles;
+}
+
+UIntList PendingContacts::handles() const
+{
+ if (!isForHandles()) {
+ warning() << "Tried to get handles from" << this << "which is not for handles!";
+ }
+
+ return mPriv->handles;
+}
+
+bool PendingContacts::isForIdentifiers() const
+{
+ return mPriv->requestType == Private::ForIdentifiers;
+}
+
+QStringList PendingContacts::identifiers() const
+{
+ if (!isForIdentifiers()) {
+ warning() << "Tried to get identifiers from" << this << "which is not for identifiers!";
+ }
+
+ return mPriv->identifiers;
+}
+
+bool PendingContacts::isUpgrade() const
+{
+ return mPriv->requestType == Private::Upgrade;
+}
+
+QList<ContactPtr> PendingContacts::contactsToUpgrade() const
+{
+ if (!isUpgrade()) {
+ warning() << "Tried to get contacts to upgrade from" << this << "which is not an upgrade!";
+ }
+
+ return mPriv->contactsToUpgrade;
+}
+
+QList<ContactPtr> PendingContacts::contacts() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContacts::contacts() called before finished";
+ } else if (isError()) {
+ warning() << "PendingContacts::contacts() called when errored";
+ }
+
+ return mPriv->contacts;
+}
+
+UIntList PendingContacts::invalidHandles() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContacts::invalidHandles() called before finished";
+ } else if (isError()) {
+ warning() << "PendingContacts::invalidHandles() called when errored";
+ } else if (!isForHandles()) {
+ warning() << "PendingContacts::invalidHandles() called for" << this << "which is for IDs!";
+ }
+
+ return mPriv->invalidHandles;
+}
+
+QStringList PendingContacts::validIdentifiers() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContacts::validIdentifiers called before finished";
+ } else if (!isValid()) {
+ warning() << "PendingContacts::validIdentifiers called when not valid";
+ }
+
+ return mPriv->validIds;
+}
+
+QHash<QString, QPair<QString, QString> > PendingContacts::invalidIdentifiers() const
+{
+ if (!isFinished()) {
+ warning() << "PendingContacts::invalidIdentifiers called before finished";
+ }
+
+ return mPriv->invalidIds;
+}
+
+void PendingContacts::onAttributesFinished(PendingOperation *operation)
+{
+ PendingContactAttributes *pendingAttributes =
+ qobject_cast<PendingContactAttributes *>(operation);
+
+ if (pendingAttributes->isError()) {
+ debug() << "PendingAttrs error" << pendingAttributes->errorName()
+ << "message" << pendingAttributes->errorMessage();
+ setFinishedWithError(pendingAttributes->errorName(), pendingAttributes->errorMessage());
+ return;
+ }
+
+ ReferencedHandles validHandles = pendingAttributes->validHandles();
+ ContactAttributesMap attributes = pendingAttributes->attributes();
+
+ foreach (uint handle, mPriv->handles) {
+ if (!mPriv->satisfyingContacts.contains(handle)) {
+ int indexInValid = validHandles.indexOf(handle);
+ if (indexInValid >= 0) {
+ ReferencedHandles referencedHandle = validHandles.mid(indexInValid, 1);
+ QVariantMap handleAttributes = attributes[handle];
+ mPriv->satisfyingContacts.insert(handle, manager()->ensureContact(referencedHandle,
+ mPriv->missingFeatures, handleAttributes));
+ } else {
+ mPriv->invalidHandles.push_back(handle);
+ }
+ }
+ }
+
+ allAttributesFetched();
+}
+
+void PendingContacts::onRequestHandlesFinished(PendingOperation *operation)
+{
+ PendingHandles *pendingHandles = qobject_cast<PendingHandles *>(operation);
+
+ mPriv->validIds = pendingHandles->validNames();
+ mPriv->invalidIds = pendingHandles->invalidNames();
+
+ if (pendingHandles->isError()) {
+ debug() << "RequestHandles error" << operation->errorName()
+ << "message" << operation->errorMessage();
+ setFinishedWithError(operation->errorName(), operation->errorMessage());
+ return;
+ }
+
+ mPriv->nested = manager()->contactsForHandles(pendingHandles->handles(), features());
+ connect(mPriv->nested,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onNestedFinished(Tp::PendingOperation*)));
+}
+
+void PendingContacts::onReferenceHandlesFinished(PendingOperation *operation)
+{
+ PendingHandles *pendingHandles = qobject_cast<PendingHandles *>(operation);
+
+ if (pendingHandles->isError()) {
+ debug() << "ReferenceHandles error" << operation->errorName()
+ << "message" << operation->errorMessage();
+ setFinishedWithError(operation->errorName(), operation->errorMessage());
+ return;
+ }
+
+ ReferencedHandles validHandles = pendingHandles->handles();
+ UIntList invalidHandles = pendingHandles->invalidHandles();
+ ConnectionPtr conn = mPriv->manager->connection();
+ mPriv->handlesToInspect = ReferencedHandles(conn, HandleTypeContact, UIntList());
+ foreach (uint handle, mPriv->handles) {
+ if (!mPriv->satisfyingContacts.contains(handle)) {
+ int indexInValid = validHandles.indexOf(handle);
+ if (indexInValid >= 0) {
+ ReferencedHandles referencedHandle = validHandles.mid(indexInValid, 1);
+ mPriv->handlesToInspect.append(referencedHandle);
+ } else {
+ mPriv->invalidHandles.push_back(handle);
+ }
+ }
+ }
+
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ conn->baseInterface()->InspectHandles(HandleTypeContact,
+ mPriv->handlesToInspect.toList()),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onInspectHandlesFinished(QDBusPendingCallWatcher*)));
+}
+
+void PendingContacts::onNestedFinished(PendingOperation *operation)
+{
+ Q_ASSERT(operation == mPriv->nested);
+
+ if (operation->isError()) {
+ debug() << " error" << operation->errorName()
+ << "message" << operation->errorMessage();
+ setFinishedWithError(operation->errorName(), operation->errorMessage());
+ return;
+ }
+
+ mPriv->contacts = mPriv->nested->contacts();
+ mPriv->nested = 0;
+ mPriv->setFinished();
+}
+
+void PendingContacts::onInspectHandlesFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QStringList> reply = *watcher;
+
+ if (reply.isError()) {
+ debug().nospace() << "InspectHandles: error " << reply.error().name() << ": "
+ << reply.error().message();
+ setFinishedWithError(reply.error());
+ return;
+ }
+
+ QStringList names = reply.value();
+ int i = 0;
+ ConnectionPtr conn = mPriv->manager->connection();
+ foreach (uint handle, mPriv->handlesToInspect) {
+ QVariantMap handleAttributes;
+ handleAttributes.insert(QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id"),
+ names[i++]);
+ ReferencedHandles referencedHandle(conn, HandleTypeContact,
+ UIntList() << handle);
+ mPriv->satisfyingContacts.insert(handle, manager()->ensureContact(referencedHandle,
+ mPriv->missingFeatures, handleAttributes));
+ }
+
+ allAttributesFetched();
+
+ watcher->deleteLater();
+}
+
+void PendingContacts::allAttributesFetched()
+{
+ foreach (uint handle, mPriv->handles) {
+ if (mPriv->satisfyingContacts.contains(handle)) {
+ mPriv->contacts.push_back(mPriv->satisfyingContacts[handle]);
+ }
+ }
+
+ mPriv->setFinished();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-contacts.h b/qt4/TelepathyQt4/pending-contacts.h
new file mode 100644
index 000000000..2ea8a4e75
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-contacts.h
@@ -0,0 +1,108 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_contacts_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_contacts_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/PendingOperation>
+
+#include <QHash>
+#include <QList>
+#include <QMap>
+#include <QSet>
+#include <QStringList>
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Contact>
+
+namespace Tp
+{
+
+class ContactManager;
+
+class TELEPATHY_QT4_EXPORT PendingContacts : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingContacts);
+
+public:
+ ~PendingContacts();
+
+ ContactManagerPtr manager() const;
+ Features features() const;
+
+ bool isForHandles() const;
+ UIntList handles() const;
+
+ bool isForIdentifiers() const;
+ QStringList identifiers() const;
+
+ bool isUpgrade() const;
+ QList<ContactPtr> contactsToUpgrade() const;
+
+ QList<ContactPtr> contacts() const;
+ UIntList invalidHandles() const;
+ QStringList validIdentifiers() const;
+ QHash<QString, QPair<QString, QString> > invalidIdentifiers() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onAttributesFinished(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onRequestHandlesFinished(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onReferenceHandlesFinished(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onNestedFinished(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onInspectHandlesFinished(QDBusPendingCallWatcher *);
+
+private:
+ friend class ContactManager;
+
+ // If errorName is non-empty, these will fail instantly
+ TELEPATHY_QT4_NO_EXPORT PendingContacts(const ContactManagerPtr &manager, const UIntList &handles,
+ const Features &features,
+ const Features &missingFeatures,
+ const QStringList &interfaces,
+ const QMap<uint, ContactPtr> &satisfyingContacts,
+ const QSet<uint> &otherContacts,
+ const QString &errorName = QString(),
+ const QString &errorMessage = QString());
+ TELEPATHY_QT4_NO_EXPORT PendingContacts(const ContactManagerPtr &manager, const QStringList &identifiers,
+ const Features &features,
+ const QString &errorName = QString(),
+ const QString &errorMessage = QString());
+ TELEPATHY_QT4_NO_EXPORT PendingContacts(const ContactManagerPtr &manager, const QList<ContactPtr> &contacts,
+ const Features &features,
+ const QString &errorName = QString(),
+ const QString &errorMessage = QString());
+
+ TELEPATHY_QT4_NO_EXPORT void allAttributesFetched();
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-handles.cpp b/qt4/TelepathyQt4/pending-handles.cpp
new file mode 100644
index 000000000..a797fa03f
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-handles.cpp
@@ -0,0 +1,490 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingHandles>
+
+#include "TelepathyQt4/_gen/pending-handles.moc.hpp"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include "TelepathyQt4/debug-internal.h"
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingHandles::Private
+{
+ HandleType handleType;
+ bool isRequest;
+ QStringList namesRequested;
+ UIntList handlesToReference;
+ ReferencedHandles handles;
+ ReferencedHandles alreadyHeld;
+ UIntList invalidHandles;
+ QStringList validNames;
+ QHash<QString, QPair<QString, QString> > invalidNames;
+
+ // one to one requests (ids)
+ QHash<QDBusPendingCallWatcher *, uint> handlesForWatchers;
+ QHash<QDBusPendingCallWatcher *, QString> idsForWatchers;
+ QHash<QString, uint> handlesForIds;
+ int requestsFinished;
+};
+
+/**
+ * \class PendingHandles
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/pending-handles.h <TelepathyQt4/PendingHandles>
+ *
+ * \brief The PendingHandles class represents the parameters of and the reply to
+ * an asynchronous handle request/hold.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is to use Connection::requestHandles() or Connection::referenceHandles().
+ *
+ * See \ref async_model
+ */
+
+PendingHandles::PendingHandles(const ConnectionPtr &connection, HandleType handleType,
+ const QStringList &names)
+ : PendingOperation(connection),
+ mPriv(new Private)
+{
+ debug() << "PendingHandles(request)";
+
+ mPriv->handleType = handleType;
+ mPriv->isRequest = true;
+ mPriv->namesRequested = names;
+ mPriv->requestsFinished = 0;
+
+ // try to request all handles at once
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ connection->baseInterface()->RequestHandles(mPriv->handleType, names),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onRequestHandlesFinished(QDBusPendingCallWatcher*)));
+}
+
+PendingHandles::PendingHandles(const ConnectionPtr &connection, HandleType handleType,
+ const UIntList &handles, const UIntList &alreadyHeld,
+ const UIntList &notYetHeld)
+ : PendingOperation(connection),
+ mPriv(new Private)
+{
+ debug() << "PendingHandles(reference)";
+
+ mPriv->handleType = handleType;
+ mPriv->isRequest = false;
+ mPriv->handlesToReference = handles;
+ mPriv->alreadyHeld = ReferencedHandles(connection, mPriv->handleType, alreadyHeld);
+ mPriv->requestsFinished = 0;
+
+ if (notYetHeld.isEmpty()) {
+ debug() << " All handles already held, finishing up instantly";
+ mPriv->handles = mPriv->alreadyHeld;
+ setFinished();
+ } else {
+ debug() << " Calling HoldHandles";
+
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ connection->baseInterface()->HoldHandles(mPriv->handleType, notYetHeld),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onHoldHandlesFinished(QDBusPendingCallWatcher*)));
+ }
+}
+
+PendingHandles::PendingHandles(const QString &errorName, const QString &errorMessage)
+ : PendingOperation(ConnectionPtr()), mPriv(new Private)
+{
+ setFinishedWithError(errorName, errorMessage);
+}
+
+/**
+ * Class destructor.
+ */
+PendingHandles::~PendingHandles()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the connection through which the operation was made.
+ *
+ * \return A pointer to the Connection object.
+ */
+ConnectionPtr PendingHandles::connection() const
+{
+ return ConnectionPtr(qobject_cast<Connection*>((Connection*) _object().data()));
+}
+
+/**
+ * Return the handle type specified in the operation.
+ *
+ * \return The target handle type as #HandleType.
+ */
+HandleType PendingHandles::handleType() const
+{
+ return mPriv->handleType;
+}
+
+/**
+ * Return whether the operation was a handle request (as opposed to a
+ * reference of existing handles).
+ *
+ * \return \c true if the operation was a request (== !isReference()), \c false otherwise.
+ * \sa isReference()
+ */
+bool PendingHandles::isRequest() const
+{
+ return mPriv->isRequest;
+}
+
+/**
+ * Return whether the operation was a handle reference (as opposed to a
+ * request for new handles).
+ *
+ * \return \c true if the operation was a reference (== !isRequest()), \c false otherwise.
+ * \sa isRequest()
+ */
+bool PendingHandles::isReference() const
+{
+ return !mPriv->isRequest;
+}
+
+/**
+ * If the operation was a request (as returned by isRequest()), returns the
+ * names of the entities for which handles were requested for. Otherwise,
+ * returns an empty list.
+ *
+ * \return Reference to a list of the names of the entities.
+ */
+const QStringList &PendingHandles::namesRequested() const
+{
+ return mPriv->namesRequested;
+}
+
+QStringList PendingHandles::validNames() const
+{
+ if (!isFinished()) {
+ warning() << "PendingHandles::validNames called before finished";
+ return QStringList();
+ } else if (!isValid()) {
+ warning() << "PendingHandles::validNames called when not valid";
+ return QStringList();
+ }
+
+ return mPriv->validNames;
+}
+
+QHash<QString, QPair<QString, QString> > PendingHandles::invalidNames() const
+{
+ if (!isFinished()) {
+ warning() << "PendingHandles::invalidNames called before finished";
+ return QHash<QString, QPair<QString, QString> >();
+ }
+
+ return mPriv->invalidNames;
+}
+
+/**
+ * If the operation was a reference (as returned by isReference()), returns
+ * the handles which were to be referenced. Otherwise, returns an empty
+ * list.
+ *
+ * \return Reference to a list of the handles specified to be referenced.
+ */
+const UIntList &PendingHandles::handlesToReference() const
+{
+ return mPriv->handlesToReference;
+}
+
+/**
+ * Return the now-referenced handles resulting from the operation. If the
+ * operation has not (yet) finished successfully (isFinished() returns
+ * <code>false</code>), the return value is undefined.
+ *
+ * For requests of new handles, <code>handles()[i]</code> will be the handle
+ * corresponding to the entity name <code>namesToRequest()[i]</code>. For
+ * references of existing handles, <code>handles()[i] ==
+ * handlesToReference()[i]</code> will be true for any <code>i</code>.
+ *
+ * \return ReferencedHandles object containing the handles.
+ */
+ReferencedHandles PendingHandles::handles() const
+{
+ if (!isFinished()) {
+ warning() << "PendingHandles::handles() called before finished";
+ return ReferencedHandles();
+ } else if (!isValid()) {
+ warning() << "PendingHandles::handles() called when not valid";
+ return ReferencedHandles();
+ }
+
+ return mPriv->handles;
+}
+
+UIntList PendingHandles::invalidHandles() const
+{
+ if (!isFinished()) {
+ warning() << "PendingHandles::invalidHandles called before finished";
+ }
+
+ return mPriv->invalidHandles;
+}
+
+void PendingHandles::onRequestHandlesFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<UIntList> reply = *watcher;
+
+ if (reply.isError()) {
+ QDBusError error = reply.error();
+ if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) {
+ // do not fallback
+ foreach (const QString &name, mPriv->namesRequested) {
+ mPriv->invalidNames.insert(name,
+ QPair<QString, QString>(error.name(),
+ error.message()));
+ }
+ setFinishedWithError(error);
+ connection()->handleRequestLanded(mPriv->handleType);
+ watcher->deleteLater();
+ return;
+ }
+
+ if (mPriv->namesRequested.size() == 1) {
+ debug().nospace() << " Failure: error " <<
+ reply.error().name() << ": " <<
+ reply.error().message();
+
+ mPriv->invalidNames.insert(mPriv->namesRequested.first(),
+ QPair<QString, QString>(error.name(),
+ error.message()));
+ setFinished();
+ connection()->handleRequestLanded(mPriv->handleType);
+ watcher->deleteLater();
+ return;
+ }
+
+ // try to request one handles at a time
+ foreach (const QString &name, mPriv->namesRequested) {
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ connection()->baseInterface()->RequestHandles(
+ mPriv->handleType,
+ QStringList() << name),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onRequestHandlesFallbackFinished(QDBusPendingCallWatcher*)));
+ mPriv->idsForWatchers.insert(watcher, name);
+ }
+ } else {
+ debug() << "Received reply to RequestHandles";
+ mPriv->handles = ReferencedHandles(connection(),
+ mPriv->handleType, reply.value());
+ mPriv->validNames.append(mPriv->namesRequested);
+ setFinished();
+ connection()->handleRequestLanded(mPriv->handleType);
+ }
+}
+
+void PendingHandles::onHoldHandlesFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<void> reply = *watcher;
+
+ debug() << "Received reply to HoldHandles";
+
+ if (reply.isError()) {
+ debug().nospace() << " Failure: error " <<
+ reply.error().name() << ": " <<
+ reply.error().message();
+
+ QDBusError error = reply.error();
+ if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) {
+ // do not fallback
+ mPriv->invalidHandles = mPriv->handlesToReference;
+ setFinishedWithError(error);
+ watcher->deleteLater();
+ return;
+ }
+
+ if (mPriv->handlesToReference.size() == 1) {
+ debug().nospace() << " Failure: error " <<
+ reply.error().name() << ": " <<
+ reply.error().message();
+
+ mPriv->invalidHandles = mPriv->handlesToReference;
+ setFinished();
+ watcher->deleteLater();
+ return;
+ }
+
+ // try to request one handles at a time
+ foreach (uint handle, mPriv->handlesToReference) {
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ connection()->baseInterface()->HoldHandles(
+ mPriv->handleType,
+ UIntList() << handle),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onHoldHandlesFallbackFinished(QDBusPendingCallWatcher*)));
+ mPriv->handlesForWatchers.insert(watcher, handle);
+ }
+ } else {
+ mPriv->handles = ReferencedHandles(connection(),
+ mPriv->handleType, mPriv->handlesToReference);
+ setFinished();
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingHandles::onRequestHandlesFallbackFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<UIntList> reply = *watcher;
+
+ Q_ASSERT(mPriv->idsForWatchers.contains(watcher));
+ QString id = mPriv->idsForWatchers.value(watcher);
+
+ debug() << "Received reply to RequestHandles(" << id << ")";
+
+ if (reply.isError()) {
+ debug().nospace() << " Failure: error " << reply.error().name() << ": "
+ << reply.error().message();
+
+ // if the error is disconnected for example, fail immediately
+ QDBusError error = reply.error();
+ if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) {
+ foreach (const QString &name, mPriv->namesRequested) {
+ mPriv->invalidNames.insert(name,
+ QPair<QString, QString>(error.name(),
+ error.message()));
+ }
+ setFinishedWithError(error);
+ connection()->handleRequestLanded(mPriv->handleType);
+ watcher->deleteLater();
+ return;
+ }
+
+ mPriv->invalidNames.insert(id,
+ QPair<QString, QString>(reply.error().name(),
+ reply.error().message()));
+ } else {
+ Q_ASSERT(reply.value().size() == 1);
+ uint handle = reply.value().first();
+ mPriv->handlesForIds.insert(id, handle);
+ }
+
+ if (++mPriv->requestsFinished == mPriv->namesRequested.size()) {
+ if (mPriv->handlesForIds.size() == 0) {
+ // all requests failed
+ setFinished();
+ } else {
+ // all requests either failed or finished successfully
+
+ // we need to return the handles in the same order as requested
+ UIntList handles;
+ foreach (const QString &name, mPriv->namesRequested) {
+ if (!mPriv->invalidNames.contains(name)) {
+ Q_ASSERT(mPriv->handlesForIds.contains(name));
+ handles.append(mPriv->handlesForIds.value(name));
+ mPriv->validNames.append(name);
+ }
+ }
+ mPriv->handles = ReferencedHandles(connection(),
+ mPriv->handleType, handles);
+
+ setFinished();
+ }
+
+ debug() << " namesRequested:" << mPriv->namesRequested;
+ debug() << " invalidNames :" << mPriv->invalidNames;
+ debug() << " validNames :" << mPriv->validNames;
+
+ connection()->handleRequestLanded(mPriv->handleType);
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingHandles::onHoldHandlesFallbackFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<void> reply = *watcher;
+
+ Q_ASSERT(mPriv->handlesForWatchers.contains(watcher));
+ uint handle = mPriv->handlesForWatchers.value(watcher);
+
+ debug() << "Received reply to HoldHandles(" << handle << ")";
+
+ if (reply.isError()) {
+ debug().nospace() << " Failure: error " << reply.error().name() << ": "
+ << reply.error().message();
+
+ // if the error is disconnected for example, fail immediately
+ QDBusError error = reply.error();
+ if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) &&
+ error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) {
+ mPriv->invalidHandles = mPriv->handlesToReference;
+ setFinishedWithError(error);
+ watcher->deleteLater();
+ return;
+ }
+
+ mPriv->invalidHandles.append(handle);
+ }
+
+ if (++mPriv->requestsFinished == mPriv->namesRequested.size()) {
+ // we need to return the handles in the same order as requested
+ UIntList handles;
+ foreach (uint handle, mPriv->handlesToReference) {
+ if (!mPriv->invalidHandles.contains(handle)) {
+ handles.append(handle);
+ }
+ }
+
+ if (handles.size() != 0) {
+ mPriv->handles = ReferencedHandles(connection(),
+ mPriv->handleType, handles);
+ }
+
+ setFinished();
+ }
+
+ watcher->deleteLater();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-handles.h b/qt4/TelepathyQt4/pending-handles.h
new file mode 100644
index 000000000..5541511f9
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-handles.h
@@ -0,0 +1,96 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_handles_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_handles_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+#include <QHash>
+#include <QString>
+#include <QStringList>
+
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class PendingHandles;
+class ReferencedHandles;
+
+class TELEPATHY_QT4_EXPORT PendingHandles : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingHandles)
+
+public:
+ ~PendingHandles();
+
+ ConnectionPtr connection() const;
+
+ HandleType handleType() const;
+
+ bool isRequest() const;
+
+ bool isReference() const;
+
+ const QStringList &namesRequested() const;
+
+ QStringList validNames() const;
+
+ QHash<QString, QPair<QString, QString> > invalidNames() const;
+
+ const UIntList &handlesToReference() const;
+
+ ReferencedHandles handles() const;
+
+ UIntList invalidHandles() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onRequestHandlesFinished(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onHoldHandlesFinished(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onRequestHandlesFallbackFinished(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onHoldHandlesFallbackFinished(QDBusPendingCallWatcher *watcher);
+
+private:
+ friend class ConnectionLowlevel;
+
+ TELEPATHY_QT4_NO_EXPORT PendingHandles(const ConnectionPtr &connection, HandleType handleType,
+ const QStringList &names);
+ TELEPATHY_QT4_NO_EXPORT PendingHandles(const ConnectionPtr &connection, HandleType handleType,
+ const UIntList &handles, const UIntList &alreadyHeld, const UIntList &notYetHeld);
+ TELEPATHY_QT4_NO_EXPORT PendingHandles(const QString &errorName, const QString &errorMessage);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-operation.cpp b/qt4/TelepathyQt4/pending-operation.cpp
new file mode 100644
index 000000000..288247a33
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-operation.cpp
@@ -0,0 +1,424 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingOperation>
+
+#define IN_TELEPATHY_QT4_HEADER
+#include "simple-pending-operations.h"
+#undef IN_TELEPATHY_QT4_HEADER
+
+#include "TelepathyQt4/_gen/pending-operation.moc.hpp"
+#include "TelepathyQt4/_gen/simple-pending-operations.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <QDBusPendingCall>
+#include <QDBusPendingCallWatcher>
+#include <QTimer>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingOperation::Private
+{
+ Private(const SharedPtr<RefCounted> &object)
+ : object(object),
+ finished(false)
+ {
+ }
+
+ SharedPtr<RefCounted> object;
+ QString errorName;
+ QString errorMessage;
+ bool finished;
+};
+
+/**
+ * \class PendingOperation
+ * \headerfile TelepathyQt4/pending-operation.h <TelepathyQt4/PendingOperation>
+ *
+ * \brief The PendingOperation class is a base class for pending asynchronous
+ * operations.
+ *
+ * This class represents an incomplete asynchronous operation, such as a
+ * D-Bus method call. When the operation has finished, it emits
+ * finished(). The slot or slots connected to the finished() signal may obtain
+ * additional information from the pending operation.
+ *
+ * In simple cases, like a D-Bus method with no 'out' arguments or for which
+ * all 'out' arguments are to be ignored (so the possible results are
+ * success with no extra information, or failure with an error code), the
+ * trivial subclass PendingVoid can be used.
+ *
+ * For pending operations that produce a result, another subclass of
+ * PendingOperation can be used, with additional methods that provide that
+ * result to the library user.
+ *
+ * After finished() is emitted, the PendingOperation is automatically
+ * deleted using deleteLater(), so library users must not explicitly
+ * delete this object.
+ *
+ * The design is loosely based on KDE's KJob.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingOperation object.
+ *
+ * \param object The object on which this pending operation takes place.
+ */
+PendingOperation::PendingOperation(const SharedPtr<RefCounted> &object)
+ : QObject(),
+ mPriv(new Private(object))
+{
+}
+
+/**
+ * Class destructor.
+ */
+PendingOperation::~PendingOperation()
+{
+ if (!mPriv->finished) {
+ warning() << this <<
+ "still pending when it was deleted - finished will "
+ "never be emitted";
+ }
+
+ delete mPriv;
+}
+
+/**
+ * Return the object on which this pending operation takes place.
+ *
+ * \return A pointer to a RefCounted object.
+ * \deprecated Will be made protected in the next API break, because using it outside the
+ * PendingOperation requires unsafe type conversions, and it's not always clear just which
+ * object the operation "takes place on", and we don't want to commit to keeping the objects
+ * fixed.
+ */
+SharedPtr<RefCounted> PendingOperation::object() const
+{
+ return _object();
+}
+
+// Temporary, to allow internal access to the object not warn of deprecation
+SharedPtr<RefCounted> PendingOperation::_object() const
+{
+ return mPriv->object;
+}
+
+void PendingOperation::emitFinished()
+{
+ Q_ASSERT(mPriv->finished);
+ emit finished(this);
+ deleteLater();
+}
+
+/**
+ * Record that this pending operation has finished successfully, and
+ * emit the finished() signal next time the event loop runs.
+ */
+void PendingOperation::setFinished()
+{
+ if (mPriv->finished) {
+ if (!mPriv->errorName.isEmpty()) {
+ warning() << this << "trying to finish with success, but already"
+ " failed with" << mPriv->errorName << ":" << mPriv->errorMessage;
+ } else {
+ warning() << this << "trying to finish with success, but already"
+ " succeeded";
+ }
+ return;
+ }
+
+ mPriv->finished = true;
+ Q_ASSERT(isValid());
+ QTimer::singleShot(0, this, SLOT(emitFinished()));
+}
+
+/**
+ * Record that this pending operation has finished with an error, and
+ * emit the finished() signal next time the event loop runs.
+ *
+ * \param name The D-Bus error name, which must be non-empty.
+ * \param message The debugging message.
+ */
+void PendingOperation::setFinishedWithError(const QString &name,
+ const QString &message)
+{
+ if (mPriv->finished) {
+ if (mPriv->errorName.isEmpty()) {
+ warning() << this << "trying to fail with" << name <<
+ "but already failed with" << errorName() << ":" <<
+ errorMessage();
+ } else {
+ warning() << this << "trying to fail with" << name <<
+ "but already succeeded";
+ }
+ return;
+ }
+
+ if (name.isEmpty()) {
+ warning() << this << "should be given a non-empty error name";
+ mPriv->errorName = QLatin1String("org.freedesktop.Telepathy.Qt4.ErrorHandlingError");
+ } else {
+ mPriv->errorName = name;
+ }
+
+ mPriv->errorMessage = message;
+ mPriv->finished = true;
+ Q_ASSERT(isError());
+ QTimer::singleShot(0, this, SLOT(emitFinished()));
+}
+
+/**
+ * Record that this pending operation has finished with an error, and
+ * emit the finished() signal next time the event loop runs.
+ *
+ * \param error The error.
+ * \sa finished()
+ */
+void PendingOperation::setFinishedWithError(const QDBusError &error)
+{
+ setFinishedWithError(error.name(), error.message());
+}
+
+/**
+ * Return whether or not the request completed successfully. If the
+ * request has not yet finished processing (isFinished() returns
+ * \c false), this cannot yet be known, and \c false
+ * will be returned.
+ *
+ * Equivalent to <code>(isFinished() && !isError())</code>.
+ *
+ * \return \c true if the request has finished processing and
+ * has completed successfully, \c false otherwise.
+ */
+bool PendingOperation::isValid() const
+{
+ return (mPriv->finished && mPriv->errorName.isEmpty());
+}
+
+/**
+ * Return whether or not the request has finished processing.
+ *
+ * The signal finished() is emitted when this changes from \c false
+ * to true.
+ *
+ * Equivalent to <code>(isValid() || isError())</code>.
+ *
+ * \return \c true if the request has finished, \c false otherwise.
+ * \sa finished()
+ */
+bool PendingOperation::isFinished() const
+{
+ return mPriv->finished;
+}
+
+/**
+ * Return whether or not the request resulted in an error.
+ *
+ * If the request has not yet finished processing (isFinished() returns
+ * \c false), this cannot yet be known, and \c false
+ * will be returned.
+ *
+ * Equivalent to <code>(isFinished() && !isValid())</code>.
+ *
+ * \return \c true if the request has finished processing and
+ * has resulted in an error, \c false otherwise.
+ */
+bool PendingOperation::isError() const
+{
+ return (mPriv->finished && !mPriv->errorName.isEmpty());
+}
+
+/**
+ * If isError() returns \c true, returns the D-Bus error with which
+ * the operation failed. If the operation succeeded or has not yet
+ * finished, returns an empty string.
+ *
+ * \return A D-Bus error name, or an empty string.
+ */
+QString PendingOperation::errorName() const
+{
+ return mPriv->errorName;
+}
+
+/**
+ * If isError() would return \c true, returns a debugging message associated
+ * with the error, which may be an empty string. Otherwise, return an
+ * empty string.
+ *
+ * \return A debugging message, or an empty string.
+ */
+QString PendingOperation::errorMessage() const
+{
+ return mPriv->errorMessage;
+}
+
+/**
+ * \fn void PendingOperation::finished(Tp::PendingOperation* operation)
+ *
+ * Emitted when the pending operation finishes, i.e. when isFinished()
+ * changes from \c false to \c true.
+ *
+ * \param operation This operation object, from which further information
+ * may be obtained.
+ */
+
+/**
+ * \class PendingSuccess
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-pending-operations.h <TelepathyQt4/PendingSuccess>
+ *
+ * \brief The PendingSuccess class represents PendingOperation that is always
+ * successful.
+ */
+
+/**
+ * \class PendingFailure
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-pending-operations.h <TelepathyQt4/PendingFailure>
+ *
+ * \brief The PendingFailure class represents a PendingOperation that always
+ * fails with the error passed to the constructor.
+ */
+
+/**
+ * \class PendingVoid
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-pending-operations.h <TelepathyQt4/PendingVoid>
+ *
+ * \brief The PendingVoid class is a generic subclass of PendingOperation
+ * representing a pending D-Bus method call that does not return anything
+ * (or returns a result that is not interesting).
+ */
+
+/**
+ * Construct a new PendingVoid object.
+ *
+ * \param object The object on which this pending operation takes place.
+ * \param call A pending call as returned by the auto-generated low level
+ * Telepathy API; if the method returns anything, the return
+ * value(s) will be ignored.
+ */
+PendingVoid::PendingVoid(QDBusPendingCall call, const SharedPtr<RefCounted> &object)
+ : PendingOperation(object)
+{
+ connect(new QDBusPendingCallWatcher(call),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(watcherFinished(QDBusPendingCallWatcher*)));
+}
+
+void PendingVoid::watcherFinished(QDBusPendingCallWatcher *watcher)
+{
+ if (watcher->isError()) {
+ setFinishedWithError(watcher->error());
+ } else {
+ setFinished();
+ }
+
+ watcher->deleteLater();
+}
+
+struct TELEPATHY_QT4_NO_EXPORT PendingComposite::Private
+{
+ Private(bool failOnFirstError, uint nOperations)
+ : failOnFirstError(failOnFirstError),
+ error(false),
+ nOperations(nOperations),
+ nOperationsFinished(0)
+ {
+ }
+
+ bool failOnFirstError;
+ bool error;
+ QString errorName;
+ QString errorMessage;
+ uint nOperations;
+ uint nOperationsFinished;
+};
+
+/**
+ * \class PendingComposite
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-pending-operations.h <TelepathyQt4/PendingComposite>
+ *
+ * \brief The PendingComposite class is a PendingOperation that can be used
+ * to track multiple pending operations at once.
+ */
+
+PendingComposite::PendingComposite(const QList<PendingOperation*> &operations,
+ const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mPriv(new Private(true, operations.size()))
+{
+ foreach (PendingOperation *operation, operations) {
+ connect(operation,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onOperationFinished(Tp::PendingOperation*)));
+ }
+}
+
+PendingComposite::PendingComposite(const QList<PendingOperation*> &operations,
+ bool failOnFirstError, const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mPriv(new Private(failOnFirstError, operations.size()))
+{
+ foreach (PendingOperation *operation, operations) {
+ connect(operation,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onOperationFinished(Tp::PendingOperation*)));
+ }
+}
+
+PendingComposite::~PendingComposite()
+{
+ delete mPriv;
+}
+
+void PendingComposite::onOperationFinished(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ if (mPriv->failOnFirstError) {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ } else if (!mPriv->error) {
+ /* only save the first error that will be used on setFinishedWithError when all
+ * pending operations finish */
+ mPriv->error = true;
+ mPriv->errorName = op->errorName();
+ mPriv->errorMessage = op->errorMessage();
+ }
+ }
+
+ if (++mPriv->nOperationsFinished == mPriv->nOperations) {
+ if (!mPriv->error) {
+ setFinished();
+ } else {
+ setFinishedWithError(mPriv->errorName, mPriv->errorMessage);
+ }
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-operation.h b/qt4/TelepathyQt4/pending-operation.h
new file mode 100644
index 000000000..5e0bf7377
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-operation.h
@@ -0,0 +1,89 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_operation_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_operation_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/RefCounted>
+#include <TelepathyQt4/SharedPtr>
+
+#include <QObject>
+
+class QDBusError;
+class QDBusPendingCall;
+class QDBusPendingCallWatcher;
+
+namespace Tp
+{
+
+class ReadinessHelper;
+
+class TELEPATHY_QT4_EXPORT PendingOperation : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingOperation)
+
+public:
+ virtual ~PendingOperation();
+
+ TELEPATHY_QT4_DEPRECATED SharedPtr<RefCounted> object() const;
+
+ bool isFinished() const;
+
+ bool isValid() const;
+
+ bool isError() const;
+ QString errorName() const;
+ QString errorMessage() const;
+
+Q_SIGNALS:
+ void finished(Tp::PendingOperation *operation);
+
+protected:
+ PendingOperation(const SharedPtr<RefCounted> &object);
+ TELEPATHY_QT4_NO_EXPORT SharedPtr<RefCounted> _object() const; // TODO: turn this into _object()
+
+protected Q_SLOTS:
+ void setFinished();
+ void setFinishedWithError(const QString &name, const QString &message);
+ void setFinishedWithError(const QDBusError &error);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void emitFinished();
+
+private:
+ friend class ContactManager;
+ friend class ReadinessHelper;
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-ready.cpp b/qt4/TelepathyQt4/pending-ready.cpp
new file mode 100644
index 000000000..ac497e20e
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-ready.cpp
@@ -0,0 +1,148 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingReady>
+
+#include "TelepathyQt4/_gen/pending-ready.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/DBusProxy>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingReady::Private
+{
+ Private(const DBusProxyPtr &proxy,
+ const Features &requestedFeatures)
+ : proxy(proxy),
+ requestedFeatures(requestedFeatures)
+ {
+ }
+
+ DBusProxyPtr proxy;
+ Features requestedFeatures;
+};
+
+/**
+ * \class PendingReady
+ * \ingroup utils
+ * \headerfile TelepathyQt4/pending-ready.h <TelepathyQt4/PendingReady>
+ *
+ * \brief The PendingReady class represents the features requested and the reply
+ * to a request for an object to become ready.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is via ReadyObject::becomeReady() or a DBusProxyFactory subclass.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingReady object.
+ *
+ * \todo Actually make it do the prepare ops. Currently they aren't taken into account in any way.
+ *
+ * \param object The object that will become ready.
+ * \param requestedFeatures Features to be made ready on the object.
+ */
+PendingReady::PendingReady(const SharedPtr<RefCounted> &object,
+ const Features &requestedFeatures)
+ : PendingOperation(object),
+ mPriv(new Private(DBusProxyPtr(dynamic_cast<DBusProxy*>((DBusProxy*) object.data())),
+ requestedFeatures))
+{
+ // This is a PendingReady created by ReadinessHelper, and will be set ready by it - so should
+ // not do anything ourselves here.
+}
+
+/**
+ * Construct a new PendingReady object.
+ *
+ * \todo Actually make it do the prepare ops. Currently they aren't taken into account in any way.
+ *
+ * \param factory The factory the request was made with.
+ * \param proxy The proxy that will become ready.
+ * \param requestedFeatures Features to be made ready on the object.
+ */
+PendingReady::PendingReady(const SharedPtr<DBusProxyFactory> &factory,
+ const DBusProxyPtr &proxy,
+ const Features &requestedFeatures)
+ : PendingOperation(factory),
+ mPriv(new Private(proxy, requestedFeatures))
+{
+ if (requestedFeatures.isEmpty()) {
+ setFinished();
+ return;
+ }
+
+ connect(proxy->becomeReady(requestedFeatures),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onNestedFinished(Tp::PendingOperation*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingReady::~PendingReady()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the proxy that should become ready.
+ *
+ * \return A pointer to the DBusProxy object if the operation was
+ * created by a proxy object or a DBusProxyFactory,
+ * otherwise a null DBusProxyPtr.
+ */
+DBusProxyPtr PendingReady::proxy() const
+{
+ return mPriv->proxy;
+}
+
+/**
+ * Return the features that were requested to become ready on the
+ * object.
+ *
+ * \return The requested features as a set of Feature objects.
+ */
+Features PendingReady::requestedFeatures() const
+{
+ return mPriv->requestedFeatures;
+}
+
+void PendingReady::onNestedFinished(Tp::PendingOperation *nested)
+{
+ Q_ASSERT(nested->isFinished());
+
+ if (nested->isValid()) {
+ setFinished();
+ } else {
+ warning() << "Nested PendingReady for" << _object() << "failed with"
+ << nested->errorName() << ":" << nested->errorMessage();
+ setFinishedWithError(nested->errorName(), nested->errorMessage());
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-ready.h b/qt4/TelepathyQt4/pending-ready.h
new file mode 100644
index 000000000..af6d63ba4
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-ready.h
@@ -0,0 +1,71 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_ready_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_ready_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/DBusProxyFactory>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/ReadinessHelper>
+#include <TelepathyQt4/SharedPtr>
+
+#include <QSet>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT PendingReady: public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingReady);
+
+public:
+ ~PendingReady();
+
+ DBusProxyPtr proxy() const;
+
+ Features requestedFeatures() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onNestedFinished(Tp::PendingOperation *);
+
+private:
+ friend class Connection;
+ friend class DBusProxyFactory;
+ friend class ReadinessHelper;
+
+ TELEPATHY_QT4_NO_EXPORT PendingReady(const SharedPtr<RefCounted> &object, const Features &requestedFeatures);
+ TELEPATHY_QT4_NO_EXPORT PendingReady(const SharedPtr<DBusProxyFactory> &factory,
+ const DBusProxyPtr &proxy, const Features &requestedFeatures);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-send-message.cpp b/qt4/TelepathyQt4/pending-send-message.cpp
new file mode 100644
index 000000000..a13d8385e
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-send-message.cpp
@@ -0,0 +1,153 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingSendMessage>
+
+#include "TelepathyQt4/_gen/pending-send-message.moc.hpp"
+
+#include <TelepathyQt4/ContactMessenger>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/TextChannel>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingSendMessage::Private
+{
+ Private(const Message &message)
+ : message(message)
+ {
+ }
+
+ QString token;
+ Message message;
+};
+
+/**
+ * \class PendingSendMessage
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/pending-send-message.h <TelepathyQt4/PendingSendMessage>
+ *
+ * \brief The PendingSendMessage class represents the parameters of and the
+ * reply to an asynchronous message send request.
+ *
+ * See \ref async_model
+ */
+
+PendingSendMessage::PendingSendMessage(const TextChannelPtr &channel, const Message &message)
+ : PendingOperation(channel),
+ mPriv(new Private(message))
+{
+}
+
+PendingSendMessage::PendingSendMessage(const ContactMessengerPtr &messenger, const Message &message)
+ : PendingOperation(messenger),
+ mPriv(new Private(message))
+{
+}
+
+PendingSendMessage::~PendingSendMessage()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the channel used to send the message if this instance was created using
+ * TextChannel. If it was created using ContactMessenger, return a null TextChannelPtr.
+ *
+ * \return A pointer to the TextChannel object, or a null TextChannelPtr if created using
+ * ContactMessenger.
+ */
+TextChannelPtr PendingSendMessage::channel() const
+{
+ return TextChannelPtr(qobject_cast<TextChannel*>((TextChannel*) _object().data()));
+}
+
+/**
+ * Return the contact messenger used to send the message if this instance was created using
+ * ContactMessenger. If it was created using TextChannel, return a null ContactMessengerPtr.
+ *
+ * \return A pointer to the ContactMessenger object, or a null ContactMessengerPtr if created using
+ * TextChannel.
+ */
+ContactMessengerPtr PendingSendMessage::messenger() const
+{
+ return ContactMessengerPtr(qobject_cast<ContactMessenger*>((ContactMessenger*) _object().data()));
+}
+
+QString PendingSendMessage::sentMessageToken() const
+{
+ return mPriv->token;
+}
+
+Message PendingSendMessage::message() const
+{
+ return mPriv->message;
+}
+
+void PendingSendMessage::onTextSent(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<> reply = *watcher;
+
+ if (reply.isError()) {
+ setFinishedWithError(reply.error());
+ } else {
+ setFinished();
+ }
+ watcher->deleteLater();
+}
+
+void PendingSendMessage::onMessageSent(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QString> reply = *watcher;
+
+ if (reply.isError()) {
+ setFinishedWithError(reply.error());
+ } else {
+ mPriv->token = reply.value();
+ setFinished();
+ }
+ watcher->deleteLater();
+}
+
+void PendingSendMessage::onCDMessageSent(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<QString> reply = *watcher;
+
+ if (reply.isError()) {
+ QDBusError error = reply.error();
+ if (error.name() == TP_QT4_DBUS_ERROR_UNKNOWN_METHOD ||
+ error.name() == TP_QT4_DBUS_ERROR_UNKNOWN_INTERFACE) {
+ setFinishedWithError(TP_QT4_ERROR_NOT_IMPLEMENTED,
+ QLatin1String("Channel Dispatcher implementation (e.g. mission-control), "
+ "does not support interface CD.I.Messages"));
+ } else {
+ setFinishedWithError(error);
+ }
+ } else {
+ mPriv->token = reply.value();
+ setFinished();
+ }
+ watcher->deleteLater();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-send-message.h b/qt4/TelepathyQt4/pending-send-message.h
new file mode 100644
index 000000000..96d39500a
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-send-message.h
@@ -0,0 +1,77 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_send_message_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_send_message_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+class QDBusPendingCallWatcher;
+class QString;
+
+namespace Tp
+{
+
+class Message;
+
+class TELEPATHY_QT4_EXPORT PendingSendMessage : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingSendMessage)
+
+public:
+ ~PendingSendMessage();
+
+ TextChannelPtr channel() const;
+
+ ContactMessengerPtr messenger() const;
+
+ QString sentMessageToken() const;
+ Message message() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onTextSent(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onMessageSent(QDBusPendingCallWatcher *watcher);
+ TELEPATHY_QT4_NO_EXPORT void onCDMessageSent(QDBusPendingCallWatcher *watcher);
+
+private:
+ friend class TextChannel;
+ friend class ContactMessenger;
+
+ TELEPATHY_QT4_NO_EXPORT PendingSendMessage(const TextChannelPtr &channel,
+ const Message &message);
+ TELEPATHY_QT4_NO_EXPORT PendingSendMessage(const ContactMessengerPtr &messenger,
+ const Message &message);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-stream-tube-connection.cpp b/qt4/TelepathyQt4/pending-stream-tube-connection.cpp
new file mode 100644
index 000000000..30c4c4887
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-stream-tube-connection.cpp
@@ -0,0 +1,272 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <TelepathyQt4/PendingStreamTubeConnection>
+
+#include "TelepathyQt4/_gen/pending-stream-tube-connection.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/Types>
+#include "TelepathyQt4/types-internal.h"
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingStreamTubeConnection::Private
+{
+ Private(PendingStreamTubeConnection *parent);
+ ~Private();
+
+ // Public object
+ PendingStreamTubeConnection *parent;
+
+ IncomingStreamTubeChannelPtr tube;
+ SocketAddressType type;
+ QHostAddress hostAddress;
+ quint16 port;
+ QString socketPath;
+ bool requiresCredentials;
+ uchar credentialByte;
+};
+
+PendingStreamTubeConnection::Private::Private(PendingStreamTubeConnection *parent)
+ : parent(parent),
+ requiresCredentials(false),
+ credentialByte(0)
+{
+
+}
+
+PendingStreamTubeConnection::Private::~Private()
+{
+}
+
+/**
+ * \class PendingStreamTubeConnection
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/incoming-stream-tube-channel.h <TelepathyQt4/PendingStreamTubeConnection>
+ *
+ * \brief The PendingStreamTubeConnection class represents an asynchronous
+ * operation for accepting an incoming stream tube.
+ *
+ * See \ref async_model
+ */
+
+PendingStreamTubeConnection::PendingStreamTubeConnection(
+ PendingVariant *acceptOperation,
+ SocketAddressType type,
+ bool requiresCredentials,
+ uchar credentialByte,
+ const IncomingStreamTubeChannelPtr &channel)
+ : PendingOperation(channel),
+ mPriv(new Private(this))
+{
+ mPriv->tube = channel;
+ mPriv->type = type;
+ mPriv->requiresCredentials = requiresCredentials;
+ mPriv->credentialByte = credentialByte;
+
+ /* keep track of channel invalidation */
+ connect(channel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ debug() << "Calling StreamTube.Accept";
+ if (acceptOperation->isFinished()) {
+ onAcceptFinished(acceptOperation);
+ } else {
+ connect(acceptOperation,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAcceptFinished(Tp::PendingOperation*)));
+ }
+}
+
+PendingStreamTubeConnection::PendingStreamTubeConnection(
+ const QString& errorName,
+ const QString& errorMessage,
+ const IncomingStreamTubeChannelPtr &channel)
+ : PendingOperation(channel),
+ mPriv(new PendingStreamTubeConnection::Private(this))
+{
+ setFinishedWithError(errorName, errorMessage);
+}
+
+/**
+ * Class destructor.
+ */
+PendingStreamTubeConnection::~PendingStreamTubeConnection()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the type of the opened stream tube socket.
+ *
+ * \return The socket type as #SocketAddressType.
+ * \see localAddress(), ipAddress()
+ */
+SocketAddressType PendingStreamTubeConnection::addressType() const
+{
+ return mPriv->tube->addressType();
+}
+
+/**
+ * Return the local address of the opened stream tube socket.
+ *
+ * This method will return a meaningful value only if the incoming stream tube
+ * was accepted as an Unix socket.
+ *
+ * \return Unix socket address if using an Unix socket,
+ * or an undefined value otherwise.
+ * \see addressType(), ipAddress()
+ */
+QString PendingStreamTubeConnection::localAddress() const
+{
+ return mPriv->tube->localAddress();
+}
+
+/**
+ * Return the IP address/port combination of the opened stream tube socket.
+ *
+ * This method will return a meaningful value only if the incoming stream tube
+ * was accepted as a TCP socket.
+ *
+ * \return Pair of IP address as QHostAddress and port if using a TCP socket,
+ * or an undefined value otherwise.
+ * \see addressType(), localAddress()
+ */
+QPair<QHostAddress, quint16> PendingStreamTubeConnection::ipAddress() const
+{
+ return mPriv->tube->ipAddress();
+}
+
+/**
+ * Return whether sending a credential byte once connecting to the socket is required.
+ *
+ * Note that if this method returns \c true, one should send a SCM_CREDS or SCM_CREDENTIALS
+ * and the credentialByte() once connected. If SCM_CREDS or SCM_CREDENTIALS cannot be sent,
+ * the credentialByte() should still be sent.
+ *
+ * \return \c true if sending credentials is required, \c false otherwise.
+ * \sa credentialByte()
+ */
+bool PendingStreamTubeConnection::requiresCredentials() const
+{
+ return mPriv->requiresCredentials;
+}
+
+/**
+ * Return the credential byte to send once connecting to the socket if requiresCredentials() is \c
+ * true.
+ *
+ * \return The credential byte.
+ * \sa requiresCredentials()
+ */
+uchar PendingStreamTubeConnection::credentialByte() const
+{
+ return mPriv->credentialByte;
+}
+
+void PendingStreamTubeConnection::onChannelInvalidated(DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ Q_UNUSED(proxy);
+
+ if (isFinished()) {
+ return;
+ }
+
+ warning().nospace() << "StreamTube.Accept failed because channel was invalidated with " <<
+ errorName << ": " << errorMessage;
+
+ setFinishedWithError(errorName, errorMessage);
+}
+
+void PendingStreamTubeConnection::onAcceptFinished(PendingOperation *op)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ if (op->isError()) {
+ warning().nospace() << "StreamTube.Accept failed with " <<
+ op->errorName() << ": " << op->errorMessage();
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ debug() << "StreamTube.Accept returned successfully";
+
+ PendingVariant *pv = qobject_cast<PendingVariant *>(op);
+ // Build the address
+ if (mPriv->type == SocketAddressTypeIPv4) {
+ SocketAddressIPv4 addr = qdbus_cast<SocketAddressIPv4>(pv->result());
+ debug().nospace() << "Got address " << addr.address << ":" << addr.port;
+ mPriv->hostAddress = QHostAddress(addr.address);
+ mPriv->port = addr.port;
+ } else if (mPriv->type == SocketAddressTypeIPv6) {
+ SocketAddressIPv6 addr = qdbus_cast<SocketAddressIPv6>(pv->result());
+ debug().nospace() << "Got address " << addr.address << ":" << addr.port;
+ mPriv->hostAddress = QHostAddress(addr.address);
+ mPriv->port = addr.port;
+ } else {
+ // Unix socket
+ mPriv->socketPath = QLatin1String(qdbus_cast<QByteArray>(pv->result()));
+ debug() << "Got socket " << mPriv->socketPath;
+ }
+
+ // It might have been already opened - check
+ if (mPriv->tube->state() == TubeChannelStateOpen) {
+ onTubeStateChanged(mPriv->tube->state());
+ } else {
+ // Wait until the tube gets opened on the other side
+ connect(mPriv->tube.data(), SIGNAL(stateChanged(Tp::TubeChannelState)),
+ this, SLOT(onTubeStateChanged(Tp::TubeChannelState)));
+ }
+}
+
+void PendingStreamTubeConnection::onTubeStateChanged(TubeChannelState state)
+{
+ debug() << "Tube state changed to " << state;
+ if (state == TubeChannelStateOpen) {
+ // The tube is ready, populate its properties
+ if (mPriv->type == SocketAddressTypeIPv4 || mPriv->type == SocketAddressTypeIPv6) {
+ mPriv->tube->setIpAddress(qMakePair<QHostAddress, quint16>(mPriv->hostAddress,
+ mPriv->port));
+ } else {
+ // Unix socket
+ mPriv->tube->setLocalAddress(mPriv->socketPath);
+ }
+
+ // Mark the operation as finished
+ setFinished();
+ } else if (state != TubeChannelStateLocalPending) {
+ // Something happened
+ setFinishedWithError(QLatin1String("Connection refused"),
+ QLatin1String("The connection to this tube was refused"));
+ }
+}
+
+}
diff --git a/qt4/TelepathyQt4/pending-stream-tube-connection.h b/qt4/TelepathyQt4/pending-stream-tube-connection.h
new file mode 100644
index 000000000..6f968b1d3
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-stream-tube-connection.h
@@ -0,0 +1,81 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_stream_tube_connection_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_stream_tube_connection_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+#include <QPair>
+
+class QHostAddress;
+
+namespace Tp
+{
+
+class PendingVariant;
+class IncomingStreamTubeChannel;
+
+class TELEPATHY_QT4_EXPORT PendingStreamTubeConnection : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingStreamTubeConnection)
+
+public:
+ virtual ~PendingStreamTubeConnection();
+
+ SocketAddressType addressType() const;
+
+ QPair<QHostAddress, quint16> ipAddress() const;
+ QString localAddress() const;
+
+ bool requiresCredentials() const;
+ uchar credentialByte() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onChannelInvalidated(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage);
+ TELEPATHY_QT4_NO_EXPORT void onAcceptFinished(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onTubeStateChanged(Tp::TubeChannelState state);
+
+private:
+ TELEPATHY_QT4_NO_EXPORT PendingStreamTubeConnection(PendingVariant *acceptOperation,
+ SocketAddressType type, bool requiresCredentials, uchar credentialByte,
+ const IncomingStreamTubeChannelPtr &channel);
+ TELEPATHY_QT4_NO_EXPORT PendingStreamTubeConnection(
+ const QString &errorName, const QString &errorMessage,
+ const IncomingStreamTubeChannelPtr &channel);
+
+ struct Private;
+ friend class IncomingStreamTubeChannel;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+#endif // TP_PENDING_STREAM_TUBE_CONNECTION_H
diff --git a/qt4/TelepathyQt4/pending-string-list.cpp b/qt4/TelepathyQt4/pending-string-list.cpp
new file mode 100644
index 000000000..c231906f1
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-string-list.cpp
@@ -0,0 +1,100 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingStringList>
+
+#include "TelepathyQt4/_gen/pending-string-list.moc.hpp"
+#include "TelepathyQt4/debug-internal.h"
+
+#include <QDBusPendingReply>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingStringList::Private
+{
+ QStringList result;
+};
+
+/**
+ * \class PendingStringList
+ * \ingroup utils
+ * \headerfile TelepathyQt4/pending-string-list.h <TelepathyQt4/PendingStringList>
+ *
+ * \brief The PendingStringList class is a generic subclass of PendingOperation
+ * representing a pending D-Bus method call that returns a string list.
+ *
+ * See \ref async_model
+ */
+
+PendingStringList::PendingStringList(const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mPriv(new Private)
+{
+}
+
+PendingStringList::PendingStringList(QDBusPendingCall call, const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mPriv(new Private)
+{
+ connect(new QDBusPendingCallWatcher(call),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this,
+ SLOT(watcherFinished(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingStringList::~PendingStringList()
+{
+ delete mPriv;
+}
+
+QStringList PendingStringList::result() const
+{
+ return mPriv->result;
+}
+
+void PendingStringList::setResult(const QStringList &result)
+{
+ mPriv->result = result;
+}
+
+void PendingStringList::watcherFinished(QDBusPendingCallWatcher* watcher)
+{
+ QDBusPendingReply<QStringList> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to PendingStringList call";
+ setResult(reply.value());
+ setFinished();
+ } else {
+ debug().nospace() << "PendingStringList call failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-string-list.h b/qt4/TelepathyQt4/pending-string-list.h
new file mode 100644
index 000000000..be4589606
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-string-list.h
@@ -0,0 +1,63 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_string_list_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_string_list_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/PendingOperation>
+
+#include <QStringList>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT PendingStringList : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingStringList);
+
+public:
+ PendingStringList(const SharedPtr<RefCounted> &object);
+ PendingStringList(QDBusPendingCall call, const SharedPtr<RefCounted> &object);
+ ~PendingStringList();
+
+ QStringList result() const;
+
+protected:
+ void setResult(const QStringList &result);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher *watcher);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-variant-map.cpp b/qt4/TelepathyQt4/pending-variant-map.cpp
new file mode 100644
index 000000000..10620361f
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-variant-map.cpp
@@ -0,0 +1,91 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingVariantMap>
+
+#include "TelepathyQt4/_gen/pending-variant-map.moc.hpp"
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Global>
+
+#include <QDBusPendingReply>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingVariantMap::Private
+{
+ QVariantMap result;
+};
+
+/**
+ * \class PendingVariantMap
+ * \ingroup utils
+ * \headerfile TelepathyQt4/pending-variant-map.h <TelepathyQt4/PendingVariantMap>
+ *
+ * \brief The PendingVariantMap class is a generic subclass of PendingOperation
+ * representing a pending D-Bus method call that returns a variant map.
+ *
+ * See \ref async_model
+ */
+
+PendingVariantMap::PendingVariantMap(QDBusPendingCall call, const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mPriv(new Private)
+{
+ connect(new QDBusPendingCallWatcher(call),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this,
+ SLOT(watcherFinished(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingVariantMap::~PendingVariantMap()
+{
+ delete mPriv;
+}
+
+QVariantMap PendingVariantMap::result() const
+{
+ return mPriv->result;
+}
+
+void PendingVariantMap::watcherFinished(QDBusPendingCallWatcher* watcher)
+{
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to PendingVariantMap call";
+ mPriv->result = reply.value();
+ setFinished();
+ } else {
+ debug().nospace() << "PendingVariantMap call failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-variant-map.h b/qt4/TelepathyQt4/pending-variant-map.h
new file mode 100644
index 000000000..fbe30c4f8
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-variant-map.h
@@ -0,0 +1,60 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009-2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_variant_map_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_variant_map_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/PendingOperation>
+
+#include <QVariant>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT PendingVariantMap : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingVariantMap);
+
+public:
+ PendingVariantMap(QDBusPendingCall call, const SharedPtr<RefCounted> &object);
+ ~PendingVariantMap();
+
+ QVariantMap result() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/pending-variant.cpp b/qt4/TelepathyQt4/pending-variant.cpp
new file mode 100644
index 000000000..a970fbb6d
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-variant.cpp
@@ -0,0 +1,91 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/PendingVariant>
+
+#include "TelepathyQt4/_gen/pending-variant.moc.hpp"
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Global>
+
+#include <QDBusPendingReply>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT PendingVariant::Private
+{
+ QVariant result;
+};
+
+/**
+ * \class PendingVariant
+ * \ingroup utils
+ * \headerfile TelepathyQt4/pending-variant.h <TelepathyQt4/PendingVariant>
+ *
+ * \brief The PendingVariant class is a generic subclass of PendingOperation
+ * representing a pending D-Bus method call that returns a variant.
+ *
+ * See \ref async_model
+ */
+
+PendingVariant::PendingVariant(QDBusPendingCall call, const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mPriv(new Private)
+{
+ connect(new QDBusPendingCallWatcher(call),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ this,
+ SLOT(watcherFinished(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingVariant::~PendingVariant()
+{
+ delete mPriv;
+}
+
+QVariant PendingVariant::result() const
+{
+ return mPriv->result;
+}
+
+void PendingVariant::watcherFinished(QDBusPendingCallWatcher* watcher)
+{
+ QDBusPendingReply<QDBusVariant> reply = *watcher;
+
+ if (!reply.isError()) {
+ debug() << "Got reply to PendingVariant call";
+ mPriv->result = reply.value().variant();
+ setFinished();
+ } else {
+ debug().nospace() << "PendingVariant call failed: " <<
+ reply.error().name() << ": " << reply.error().message();
+ setFinishedWithError(reply.error());
+ }
+
+ watcher->deleteLater();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/pending-variant.h b/qt4/TelepathyQt4/pending-variant.h
new file mode 100644
index 000000000..4cab528ee
--- /dev/null
+++ b/qt4/TelepathyQt4/pending-variant.h
@@ -0,0 +1,60 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_variant_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_variant_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/PendingOperation>
+
+#include <QVariant>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT PendingVariant : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingVariant);
+
+public:
+ PendingVariant(QDBusPendingCall call, const SharedPtr<RefCounted> &object);
+ ~PendingVariant();
+
+ QVariant result() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/presence.cpp b/qt4/TelepathyQt4/presence.cpp
new file mode 100644
index 000000000..3810616e9
--- /dev/null
+++ b/qt4/TelepathyQt4/presence.cpp
@@ -0,0 +1,335 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Presence>
+
+#include "TelepathyQt4/debug-internal.h"
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT Presence::Private : public QSharedData
+{
+ Private(const SimplePresence &sp)
+ : sp(sp)
+ {
+ }
+
+ Private(ConnectionPresenceType type, const QString &status, const QString &statusMessage)
+ {
+ sp.type = type;
+ sp.status = status;
+ sp.statusMessage = statusMessage;
+ }
+
+ SimplePresence sp;
+};
+
+/**
+ * \class Presence
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/presence.h <TelepathyQt4/Presence>
+ *
+ * \brief The Presence class represents a Telepathy simple presence.
+ */
+
+Presence::Presence()
+{
+}
+
+Presence::Presence(const SimplePresence &sp)
+ : mPriv(new Private(sp))
+{
+}
+
+Presence::Presence(ConnectionPresenceType type, const QString &status, const QString &statusMessage)
+ : mPriv(new Private(type, status, statusMessage))
+{
+}
+
+Presence::Presence(const Presence &other)
+ : mPriv(other.mPriv)
+{
+}
+
+Presence::~Presence()
+{
+}
+
+Presence Presence::available(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeAvailable, QLatin1String("available"), statusMessage);
+}
+
+Presence Presence::away(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeAway, QLatin1String("away"), statusMessage);
+}
+
+Presence Presence::brb(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeAway, QLatin1String("brb"), statusMessage);
+}
+
+Presence Presence::busy(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeBusy, QLatin1String("busy"), statusMessage);
+}
+
+Presence Presence::xa(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeExtendedAway, QLatin1String("xa"), statusMessage);
+}
+
+Presence Presence::hidden(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeHidden, QLatin1String("hidden"), statusMessage);
+}
+
+Presence Presence::offline(const QString &statusMessage)
+{
+ return Presence(ConnectionPresenceTypeOffline, QLatin1String("offline"), statusMessage);
+}
+
+Presence &Presence::operator=(const Presence &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool Presence::operator==(const Presence &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return true;
+ }
+ return false;
+ }
+
+ return mPriv->sp == other.mPriv->sp;
+}
+
+bool Presence::operator!=(const Presence &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return false;
+ }
+ return true;
+ }
+
+ return mPriv->sp != other.mPriv->sp;
+}
+
+ConnectionPresenceType Presence::type() const
+{
+ if (!isValid()) {
+ return ConnectionPresenceTypeUnknown;
+ }
+
+ return (ConnectionPresenceType) mPriv->sp.type;
+}
+
+QString Presence::status() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->sp.status;
+}
+
+QString Presence::statusMessage() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->sp.statusMessage;
+}
+
+void Presence::setStatus(const SimplePresence &value)
+{
+ if (!isValid()) {
+ mPriv = new Private(value);
+ return;
+ }
+
+ mPriv->sp = value;
+}
+
+void Presence::setStatus(ConnectionPresenceType type, const QString &status,
+ const QString &statusMessage)
+{
+ if (!isValid()) {
+ mPriv = new Private(type, status, statusMessage);
+ return;
+ }
+
+ mPriv->sp.type = type;
+ mPriv->sp.status = status;
+ mPriv->sp.statusMessage = statusMessage;
+}
+
+SimplePresence Presence::barePresence() const
+{
+ if (!isValid()) {
+ return SimplePresence();
+ }
+
+ return mPriv->sp;
+}
+
+struct TELEPATHY_QT4_NO_EXPORT PresenceSpec::Private : public QSharedData
+{
+ Private(const QString &status, const SimpleStatusSpec &spec)
+ : status(status),
+ spec(spec)
+ {
+ }
+
+ QString status;
+ SimpleStatusSpec spec;
+};
+
+/**
+ * \class PresenceSpec
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/presence.h <TelepathyQt4/PresenceSpec>
+ *
+ * \brief The PresenceSpec class represents a Telepathy presence information
+ * supported by a protocol.
+ */
+
+PresenceSpec::PresenceSpec()
+{
+}
+
+PresenceSpec::PresenceSpec(const QString &status, const SimpleStatusSpec &spec)
+ : mPriv(new Private(status, spec))
+{
+}
+
+PresenceSpec::PresenceSpec(const PresenceSpec &other)
+ : mPriv(other.mPriv)
+{
+}
+
+PresenceSpec::~PresenceSpec()
+{
+}
+
+PresenceSpec &PresenceSpec::operator=(const PresenceSpec &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool PresenceSpec::operator==(const PresenceSpec &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return true;
+ }
+ return false;
+ }
+
+ return (mPriv->status == other.mPriv->status) &&
+ (mPriv->spec == other.mPriv->spec);
+}
+
+bool PresenceSpec::operator!=(const PresenceSpec &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return false;
+ }
+ return true;
+ }
+
+ return (mPriv->status != other.mPriv->status) &&
+ (mPriv->spec != other.mPriv->spec);
+}
+
+bool PresenceSpec::operator<(const PresenceSpec &other) const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ if (!other.isValid()) {
+ return true;
+ }
+
+ return (mPriv->status < other.mPriv->status);
+}
+
+Presence PresenceSpec::presence(const QString &statusMessage) const
+{
+ if (!isValid()) {
+ return Presence();
+ }
+
+ if (!canHaveStatusMessage() && !statusMessage.isEmpty()) {
+ warning() << "Passing a status message to PresenceSpec with "
+ "canHaveStatusMessage() being false";
+ }
+
+ return Presence((ConnectionPresenceType) mPriv->spec.type, mPriv->status, statusMessage);
+}
+
+bool PresenceSpec::maySetOnSelf() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return mPriv->spec.maySetOnSelf;
+}
+
+bool PresenceSpec::canHaveStatusMessage() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return mPriv->spec.canHaveMessage;
+}
+
+SimpleStatusSpec PresenceSpec::bareSpec() const
+{
+ if (!isValid()) {
+ return SimpleStatusSpec();
+ }
+
+ return mPriv->spec;
+}
+
+/**
+ * \class PresenceSpecList
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/presence.h <TelepathyQt4/PresenceSpecList>
+ *
+ * \brief The PresenceSpecList class represents a list of PresenceSpec.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/presence.h b/qt4/TelepathyQt4/presence.h
new file mode 100644
index 000000000..d12fb5ecf
--- /dev/null
+++ b/qt4/TelepathyQt4/presence.h
@@ -0,0 +1,135 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_presence_h_HEADER_GUARD_
+#define _TelepathyQt4_presence_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT Presence
+{
+public:
+ Presence();
+ Presence(const SimplePresence &sp);
+ Presence(ConnectionPresenceType type, const QString &status, const QString &statusMessage);
+ Presence(const Presence &other);
+ ~Presence();
+
+ static Presence available(const QString &statusMessage = QString());
+ static Presence away(const QString &statusMessage = QString());
+ static Presence brb(const QString &statusMessage = QString());
+ static Presence busy(const QString &statusMessage = QString());
+ static Presence xa(const QString &statusMessage = QString());
+ static Presence hidden(const QString &statusMessage = QString());
+ static Presence offline(const QString &statusMessage = QString());
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ Presence &operator=(const Presence &other);
+ bool operator==(const Presence &other) const;
+ bool operator!=(const Presence &other) const;
+
+ ConnectionPresenceType type() const;
+ QString status() const;
+ QString statusMessage() const;
+ void setStatus(const SimplePresence &value);
+ void setStatus(ConnectionPresenceType type, const QString &status,
+ const QString &statusMessage);
+
+ SimplePresence barePresence() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT PresenceSpec
+{
+public:
+ PresenceSpec();
+ PresenceSpec(const QString &status, const SimpleStatusSpec &spec);
+ PresenceSpec(const PresenceSpec &other);
+ ~PresenceSpec();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ PresenceSpec &operator=(const PresenceSpec &other);
+ bool operator==(const PresenceSpec &other) const;
+ bool operator!=(const PresenceSpec &other) const;
+ bool operator<(const PresenceSpec &other) const;
+
+ Presence presence(const QString &statusMessage = QString()) const;
+ bool maySetOnSelf() const;
+ bool canHaveStatusMessage() const;
+
+ SimpleStatusSpec bareSpec() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT PresenceSpecList : public QList<PresenceSpec>
+{
+public:
+ PresenceSpecList() { }
+ PresenceSpecList(const SimpleStatusSpecMap &specMap)
+ {
+ SimpleStatusSpecMap::const_iterator i = specMap.constBegin();
+ SimpleStatusSpecMap::const_iterator end = specMap.end();
+ for (; i != end; ++i) {
+ QString status = i.key();
+ SimpleStatusSpec spec = i.value();
+ append(PresenceSpec(status, spec));
+ }
+ }
+ PresenceSpecList(const QList<PresenceSpec> &other)
+ : QList<PresenceSpec>(other)
+ {
+ }
+
+ QMap<QString, PresenceSpec> toMap() const
+ {
+ QMap<QString, PresenceSpec> ret;
+ Q_FOREACH (const PresenceSpec &spec, *this) {
+ ret.insert(spec.presence().status(), spec);
+ }
+ return ret;
+ }
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::Presence);
+Q_DECLARE_METATYPE(Tp::PresenceSpec);
+
+#endif
diff --git a/qt4/TelepathyQt4/profile-manager.cpp b/qt4/TelepathyQt4/profile-manager.cpp
new file mode 100644
index 000000000..4c283f478
--- /dev/null
+++ b/qt4/TelepathyQt4/profile-manager.cpp
@@ -0,0 +1,332 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ProfileManager>
+
+#include "TelepathyQt4/_gen/profile-manager.moc.hpp"
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingStringList>
+#include <TelepathyQt4/Profile>
+#include <TelepathyQt4/ReadinessHelper>
+
+#include <QFile>
+#include <QFileInfo>
+#include <QString>
+#include <QStringList>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ProfileManager::Private
+{
+ Private(ProfileManager *parent, const QDBusConnection &bus);
+
+ static void introspectMain(Private *self);
+ static void introspectFakeProfiles(Private *self);
+
+ ProfileManager *parent;
+ ReadinessHelper *readinessHelper;
+ QDBusConnection bus;
+ QHash<QString, ProfilePtr> profiles;
+ QList<ConnectionManagerPtr> cms;
+};
+
+ProfileManager::Private::Private(ProfileManager *parent, const QDBusConnection &bus)
+ : parent(parent),
+ readinessHelper(parent->readinessHelper()),
+ bus(bus)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMain,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ ReadinessHelper::Introspectable introspectableFakeProfiles(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << FeatureCore, // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectFakeProfiles,
+ this);
+ introspectables[FeatureFakeProfiles] = introspectableFakeProfiles;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+void ProfileManager::Private::introspectMain(ProfileManager::Private *self)
+{
+ QStringList searchDirs = Profile::searchDirs();
+
+ foreach (const QString searchDir, searchDirs) {
+ QDir dir(searchDir);
+ dir.setFilter(QDir::Files);
+
+ QFileInfoList list = dir.entryInfoList();
+ for (int i = 0; i < list.size(); ++i) {
+ QFileInfo fi = list.at(i);
+
+ if (fi.completeSuffix() != QLatin1String("profile")) {
+ continue;
+ }
+
+ QString fileName = fi.absoluteFilePath();
+ QString serviceName = fi.baseName();
+
+ if (self->profiles.contains(serviceName)) {
+ debug() << "Profile for service" << serviceName << "already "
+ "exists. Ignoring profile file:" << fileName;
+ continue;
+ }
+
+ ProfilePtr profile = Profile::createForFileName(fileName);
+ if (!profile->isValid()) {
+ continue;
+ }
+
+ if (profile->type() != QLatin1String("IM")) {
+ debug() << "Ignoring profile for service" << serviceName <<
+ ": type != IM. Profile file:" << fileName;
+ continue;
+ }
+
+ debug() << "Found profile for service" << serviceName <<
+ "- profile file:" << fileName;
+ self->profiles.insert(serviceName, profile);
+ }
+ }
+
+ self->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+}
+
+void ProfileManager::Private::introspectFakeProfiles(ProfileManager::Private *self)
+{
+ PendingStringList *pendingCmNames = ConnectionManager::listNames(self->bus);
+ self->parent->connect(pendingCmNames,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onCmNamesRetrieved(Tp::PendingOperation *)));
+}
+
+/**
+ * \class ProfileManager
+ * \headerfile TelepathyQt4/profile-manager.h <TelepathyQt4/ProfileManager>
+ *
+ * \brief The ProfileManager class provides helper methods to retrieve Profile
+ * objects.
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the ProfileManager
+ * object usable.
+ *
+ * Note that this feature must be enabled in order to use all ProfileManager methods.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature ProfileManager::FeatureCore = Feature(QLatin1String(ProfileManager::staticMetaObject.className()), 0, true);
+
+/**
+ * Enabling this feature will make ProfileManager create fake Profile objects to all protocols
+ * supported on the installed connection managers, even if they don't have .profile files installed
+ * making use of them.
+ *
+ * Fake profiles are identified by Profile::isFake() returning \c true.
+ *
+ * The fake profile will contain the following info:
+ * - Profile::type() will return "IM"
+ * - Profile::provider() will return an empty string
+ * - Profile::serviceName() will return cmName-protocolName
+ * - Profile::name() and Profile::protocolName() will return protocolName
+ * - Profile::iconName() will return "im-protocolName"
+ * - Profile::cmName() will return cmName
+ * - Profile::parameters() will return a list matching CM default parameters for protocol with name
+ * protocolName.
+ * - Profile::presences() will return an empty list and
+ * Profile::allowOtherPresences() will return \c true, meaning that CM
+ * presences should be used
+ * - Profile::unsupportedChannelClassSpecs() will return an empty list
+ *
+ * Where cmName and protocolName are the name of the connection manager and the name of the protocol
+ * for which this fake Profile is created, respectively.
+ */
+const Feature ProfileManager::FeatureFakeProfiles = Feature(QLatin1String(ProfileManager::staticMetaObject.className()), 1);
+
+/**
+ * Create a new ProfileManager object.
+ */
+ProfileManagerPtr ProfileManager::create(const QDBusConnection &bus)
+{
+ return ProfileManagerPtr(new ProfileManager(bus));
+}
+
+/**
+ * Construct a new ProfileManager object.
+ */
+ProfileManager::ProfileManager(const QDBusConnection &bus)
+ : Object(),
+ ReadyObject(this, FeatureCore),
+ mPriv(new Private(this, bus))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ProfileManager::~ProfileManager()
+{
+ delete mPriv;
+}
+
+/**
+ * Return a list of all available profiles.
+ *
+ * \return A list of all available profiles.
+ */
+QList<ProfilePtr> ProfileManager::profiles() const
+{
+ return mPriv->profiles.values();
+}
+
+/**
+ * Return a list of all available profiles for a given connection manager.
+ *
+ * \param cmName Connection manager name.
+ * \return A list of all available profiles for a given connection manager.
+ */
+QList<ProfilePtr> ProfileManager::profilesForCM(const QString &cmName) const
+{
+ QList<ProfilePtr> ret;
+ foreach (const ProfilePtr &profile, mPriv->profiles) {
+ if (profile->cmName() == cmName) {
+ ret << profile;
+ }
+ }
+ return ret;
+}
+
+/**
+ * Return a list of all available profiles for a given \a protocol.
+ *
+ * \param protocolName Protocol name.
+ * \return A list of all available profiles for a given \a protocol.
+ */
+QList<ProfilePtr> ProfileManager::profilesForProtocol(
+ const QString &protocolName) const
+{
+ QList<ProfilePtr> ret;
+ foreach (const ProfilePtr &profile, mPriv->profiles) {
+ if (profile->protocolName() == protocolName) {
+ ret << profile;
+ }
+ }
+ return ret;
+}
+
+/**
+ * Return the profile for a given \a service.
+ *
+ * \param serviceName Service name.
+ * \return The profile for \a service.
+ */
+ProfilePtr ProfileManager::profileForService(const QString &serviceName) const
+{
+ return mPriv->profiles.value(serviceName);
+}
+
+void ProfileManager::onCmNamesRetrieved(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ warning().nospace() << "Getting available CMs failed with " <<
+ op->errorName() << ":" << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, false,
+ op->errorName(), op->errorMessage());
+ return;
+ }
+
+ PendingStringList *pendingCmNames = qobject_cast<PendingStringList *>(op);
+ QStringList cmNames(pendingCmNames->result());
+ if (cmNames.isEmpty()) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, true);
+ return;
+ }
+
+ QList<PendingOperation *> ops;
+ foreach (const QString &cmName, cmNames) {
+ ConnectionManagerPtr cm = ConnectionManager::create(mPriv->bus, cmName);
+ mPriv->cms.append(cm);
+ ops.append(cm->becomeReady());
+ }
+
+ PendingComposite *pc = new PendingComposite(ops, false, ProfileManagerPtr(this));
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onCMsReady(Tp::PendingOperation *)));
+}
+
+void ProfileManager::onCMsReady(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ warning() << "Failed introspecting all CMs, trying to create fake profiles anyway";
+ }
+
+ ProfilePtr profile;
+ foreach (const ConnectionManagerPtr &cm, mPriv->cms) {
+ if (!cm->isReady()) {
+ continue;
+ }
+
+ foreach (const QString &protocolName, cm->supportedProtocols()) {
+ /* check if there is a profile whose service name is protocolName, and if found,
+ * check if the profile is for cm, if not check if there is a profile whose service
+ * name is cm-protocolName, and if not found create one named cm-protocolName. */
+ profile = profileForService(protocolName);
+ if (profile && profile->cmName() == cm->name()) {
+ continue;
+ }
+
+ QString serviceName = QString(QLatin1String("%1-%2")).arg(cm->name()).arg(protocolName);
+ profile = profileForService(serviceName);
+ if (profile) {
+ continue;
+ }
+
+ profile = ProfilePtr(new Profile(
+ serviceName,
+ cm->name(),
+ protocolName,
+ cm->protocol(protocolName)));
+ mPriv->profiles.insert(serviceName, profile);
+ }
+ }
+
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, true);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/profile-manager.h b/qt4/TelepathyQt4/profile-manager.h
new file mode 100644
index 000000000..450c3d715
--- /dev/null
+++ b/qt4/TelepathyQt4/profile-manager.h
@@ -0,0 +1,75 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_profile_manager_h_HEADER_GUARD_
+#define _TelepathyQt4_profile_manager_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/Profile>
+#include <TelepathyQt4/ReadyObject>
+#include <TelepathyQt4/Types>
+
+#include <QDBusConnection>
+#include <QObject>
+
+namespace Tp
+{
+
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT ProfileManager : public Object, public ReadyObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ProfileManager);
+
+public:
+ static const Feature FeatureCore;
+ static const Feature FeatureFakeProfiles;
+
+ static ProfileManagerPtr create(const QDBusConnection &bus = QDBusConnection::sessionBus());
+
+ ~ProfileManager();
+
+ QList<ProfilePtr> profiles() const;
+ QList<ProfilePtr> profilesForCM(const QString &cmName) const;
+ QList<ProfilePtr> profilesForProtocol(const QString &protocolName) const;
+ ProfilePtr profileForService(const QString &serviceName) const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onCmNamesRetrieved(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onCMsReady(Tp::PendingOperation *op);
+
+private:
+ ProfileManager(const QDBusConnection &bus);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/profile.cpp b/qt4/TelepathyQt4/profile.cpp
new file mode 100644
index 000000000..153f602ba
--- /dev/null
+++ b/qt4/TelepathyQt4/profile.cpp
@@ -0,0 +1,1216 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Profile>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ManagerFile>
+#include <TelepathyQt4/Utils>
+#include <TelepathyQt4/ProtocolInfo>
+#include <TelepathyQt4/ProtocolParameter>
+
+#include <QFile>
+#include <QFileInfo>
+#include <QStringList>
+#include <QXmlAttributes>
+#include <QXmlDefaultHandler>
+#include <QXmlInputSource>
+#include <QXmlSimpleReader>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT Profile::Private
+{
+ Private();
+
+ void setServiceName(const QString &serviceName);
+ void setFileName(const QString &fileName);
+
+ void lookupProfile();
+ bool parse(QFile *file);
+ void invalidate();
+
+ struct Data
+ {
+ Data();
+
+ void clear();
+
+ QString type;
+ QString provider;
+ QString name;
+ QString iconName;
+ QString cmName;
+ QString protocolName;
+ Profile::ParameterList parameters;
+ bool allowOtherPresences;
+ Profile::PresenceList presences;
+ RequestableChannelClassSpecList unsupportedChannelClassSpecs;
+ };
+
+ class XmlHandler;
+
+ QString serviceName;
+ bool valid;
+ bool fake;
+ bool allowNonIMType;
+ Data data;
+};
+
+Profile::Private::Data::Data()
+ : allowOtherPresences(false)
+{
+}
+
+void Profile::Private::Data::clear()
+{
+ type = QString();
+ provider = QString();
+ name = QString();
+ iconName = QString();
+ protocolName = QString();
+ parameters = Profile::ParameterList();
+ allowOtherPresences = false;
+ presences = Profile::PresenceList();
+ unsupportedChannelClassSpecs = RequestableChannelClassSpecList();
+}
+
+
+class TELEPATHY_QT4_NO_EXPORT Profile::Private::XmlHandler :
+ public QXmlDefaultHandler
+{
+public:
+ XmlHandler(const QString &serviceName, bool allowNonIMType, Profile::Private::Data *outputData);
+
+ bool startElement(const QString &namespaceURI, const QString &localName,
+ const QString &qName, const QXmlAttributes &attributes);
+ bool endElement(const QString &namespaceURI, const QString &localName,
+ const QString &qName);
+ bool characters(const QString &str);
+ bool fatalError(const QXmlParseException &exception);
+ QString errorString() const;
+
+private:
+ bool attributeValueAsBoolean(const QXmlAttributes &attributes,
+ const QString &qName);
+
+ QString mServiceName;
+ bool allowNonIMType;
+ Profile::Private::Data *mData;
+ QStack<QString> mElements;
+ QString mCurrentText;
+ Profile::Parameter mCurrentParameter;
+ RequestableChannelClass mCurrentCC;
+ QString mCurrentPropertyName;
+ QString mCurrentPropertyType;
+ QString mErrorString;
+ bool mMetServiceTag;
+
+ static const QString xmlNs;
+
+ static const QString elemService;
+ static const QString elemName;
+ static const QString elemParams;
+ static const QString elemParam;
+ static const QString elemPresences;
+ static const QString elemPresence;
+ static const QString elemUnsupportedCCs;
+ static const QString elemCC;
+ static const QString elemProperty;
+
+ static const QString elemAttrId;
+ static const QString elemAttrName;
+ static const QString elemAttrType;
+ static const QString elemAttrProvider;
+ static const QString elemAttrManager;
+ static const QString elemAttrProtocol;
+ static const QString elemAttrIcon;
+ static const QString elemAttrLabel;
+ static const QString elemAttrMandatory;
+ static const QString elemAttrAllowOthers;
+ static const QString elemAttrMessage;
+ static const QString elemAttrDisabled;
+};
+
+const QString Profile::Private::XmlHandler::xmlNs = QLatin1String("http://telepathy.freedesktop.org/wiki/service-profile-v1");
+
+const QString Profile::Private::XmlHandler::elemService = QLatin1String("service");
+const QString Profile::Private::XmlHandler::elemName = QLatin1String("name");
+const QString Profile::Private::XmlHandler::elemParams = QLatin1String("parameters");
+const QString Profile::Private::XmlHandler::elemParam = QLatin1String("parameter");
+const QString Profile::Private::XmlHandler::elemPresences = QLatin1String("presences");
+const QString Profile::Private::XmlHandler::elemPresence = QLatin1String("presence");
+const QString Profile::Private::XmlHandler::elemUnsupportedCCs = QLatin1String("unsupported-channel-classes");
+const QString Profile::Private::XmlHandler::elemCC = QLatin1String("channel-class");
+const QString Profile::Private::XmlHandler::elemProperty = QLatin1String("property");
+
+const QString Profile::Private::XmlHandler::elemAttrId = QLatin1String("id");
+const QString Profile::Private::XmlHandler::elemAttrName = QLatin1String("name");
+const QString Profile::Private::XmlHandler::elemAttrType = QLatin1String("type");
+const QString Profile::Private::XmlHandler::elemAttrProvider = QLatin1String("provider");
+const QString Profile::Private::XmlHandler::elemAttrManager = QLatin1String("manager");
+const QString Profile::Private::XmlHandler::elemAttrProtocol = QLatin1String("protocol");
+const QString Profile::Private::XmlHandler::elemAttrLabel = QLatin1String("label");
+const QString Profile::Private::XmlHandler::elemAttrMandatory = QLatin1String("mandatory");
+const QString Profile::Private::XmlHandler::elemAttrAllowOthers = QLatin1String("allow-others");
+const QString Profile::Private::XmlHandler::elemAttrIcon = QLatin1String("icon");
+const QString Profile::Private::XmlHandler::elemAttrMessage = QLatin1String("message");
+const QString Profile::Private::XmlHandler::elemAttrDisabled = QLatin1String("disabled");
+
+Profile::Private::XmlHandler::XmlHandler(const QString &serviceName,
+ bool allowNonIMType,
+ Profile::Private::Data *outputData)
+ : mServiceName(serviceName),
+ allowNonIMType(allowNonIMType),
+ mData(outputData),
+ mMetServiceTag(false)
+{
+}
+
+bool Profile::Private::XmlHandler::startElement(const QString &namespaceURI,
+ const QString &localName, const QString &qName,
+ const QXmlAttributes &attributes)
+{
+ if (!mMetServiceTag && qName != elemService) {
+ mErrorString = QLatin1String("the file is not a profile file");
+ return false;
+ }
+
+ if (namespaceURI != xmlNs) {
+ // ignore all elements with unknown xmlns
+ debug() << "Ignoring unknown xmlns" << namespaceURI;
+ return true;
+ }
+
+#define CHECK_ELEMENT_IS_CHILD_OF(parentElement) \
+ if (mElements.top() != parentElement) { \
+ mErrorString = QString(QLatin1String("element '%1' is not a " \
+ "child of element '%2'")) \
+ .arg(qName) \
+ .arg(parentElement); \
+ return false; \
+ }
+#define CHECK_ELEMENT_ATTRIBUTES_COUNT(value) \
+ if (attributes.count() != value) { \
+ mErrorString = QString(QLatin1String("element '%1' contains more " \
+ "than %2 attributes")) \
+ .arg(qName) \
+ .arg(value); \
+ return false; \
+ }
+#define CHECK_ELEMENT_HAS_ATTRIBUTE(attribute) \
+ if (attributes.index(attribute) == -1) { \
+ mErrorString = QString(QLatin1String("mandatory attribute '%1' " \
+ "missing on element '%2'")) \
+ .arg(attribute) \
+ .arg(qName); \
+ return false; \
+ }
+#define CHECK_ELEMENT_ATTRIBUTES(allowedAttrs) \
+ for (int i = 0; i < attributes.count(); ++i) { \
+ bool valid = false; \
+ QString attrName = attributes.qName(i); \
+ foreach (const QString &allowedAttr, allowedAttrs) { \
+ if (attrName == allowedAttr) { \
+ valid = true; \
+ break; \
+ } \
+ } \
+ if (!valid) { \
+ mErrorString = QString(QLatin1String("invalid attribute '%1' on " \
+ "element '%2'")) \
+ .arg(attrName) \
+ .arg(qName); \
+ return false; \
+ } \
+ }
+
+ if (qName == elemService) {
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrId);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrType);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrManager);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrProtocol);
+
+ QStringList allowedAttrs = QStringList() <<
+ elemAttrId << elemAttrType << elemAttrManager <<
+ elemAttrProtocol << elemAttrProvider << elemAttrIcon;
+ CHECK_ELEMENT_ATTRIBUTES(allowedAttrs);
+
+ if (attributes.value(elemAttrId) != mServiceName) {
+ mErrorString = QString(QLatin1String("the '%1' attribute of the "
+ "element '%2' does not match the file name"))
+ .arg(elemAttrId)
+ .arg(elemService);
+ return false;
+ }
+
+ mMetServiceTag = true;
+ mData->type = attributes.value(elemAttrType);
+ if (mData->type != QLatin1String("IM") && !allowNonIMType) {
+ mErrorString = QString(QLatin1String("unknown value of element "
+ "'type': %1"))
+ .arg(mCurrentText);
+ return false;
+ }
+ mData->provider = attributes.value(elemAttrProvider);
+ mData->cmName = attributes.value(elemAttrManager);
+ mData->protocolName = attributes.value(elemAttrProtocol);
+ mData->iconName = attributes.value(elemAttrIcon);
+ } else if (qName == elemParams) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemService);
+ CHECK_ELEMENT_ATTRIBUTES_COUNT(0);
+ } else if (qName == elemParam) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemParams);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrName);
+ QStringList allowedAttrs = QStringList() << elemAttrName <<
+ elemAttrType << elemAttrMandatory << elemAttrLabel;
+ CHECK_ELEMENT_ATTRIBUTES(allowedAttrs);
+
+ QString paramType = attributes.value(elemAttrType);
+ if (paramType.isEmpty()) {
+ paramType = QLatin1String("s");
+ }
+ mCurrentParameter.setName(attributes.value(elemAttrName));
+ mCurrentParameter.setDBusSignature(QDBusSignature(paramType));
+ mCurrentParameter.setLabel(attributes.value(elemAttrLabel));
+ mCurrentParameter.setMandatory(attributeValueAsBoolean(attributes,
+ elemAttrMandatory));
+ } else if (qName == elemPresences) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemService);
+ QStringList allowedAttrs = QStringList() << elemAttrAllowOthers;
+ CHECK_ELEMENT_ATTRIBUTES(allowedAttrs);
+
+ mData->allowOtherPresences = attributeValueAsBoolean(attributes,
+ elemAttrAllowOthers);
+ } else if (qName == elemPresence) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemPresences);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrId);
+ QStringList allowedAttrs = QStringList() << elemAttrId <<
+ elemAttrLabel << elemAttrIcon << elemAttrMessage <<
+ elemAttrDisabled;
+ CHECK_ELEMENT_ATTRIBUTES(allowedAttrs);
+
+ mData->presences.append(Profile::Presence(
+ attributes.value(elemAttrId),
+ attributes.value(elemAttrLabel),
+ attributes.value(elemAttrIcon),
+ attributes.value(elemAttrMessage),
+ attributeValueAsBoolean(attributes, elemAttrDisabled)));
+ } else if (qName == elemUnsupportedCCs) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemService);
+ CHECK_ELEMENT_ATTRIBUTES_COUNT(0);
+ } else if (qName == elemCC) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemUnsupportedCCs);
+ CHECK_ELEMENT_ATTRIBUTES_COUNT(0);
+ } else if (qName == elemProperty) {
+ CHECK_ELEMENT_IS_CHILD_OF(elemCC);
+ CHECK_ELEMENT_ATTRIBUTES_COUNT(2);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrName);
+ CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrType);
+
+ mCurrentPropertyName = attributes.value(elemAttrName);
+ mCurrentPropertyType = attributes.value(elemAttrType);
+ } else {
+ if (qName != elemName) {
+ Tp::warning() << "Ignoring unknown element" << qName;
+ } else {
+ // check if we are inside <service>
+ CHECK_ELEMENT_IS_CHILD_OF(elemService);
+ // no element here supports attributes
+ CHECK_ELEMENT_ATTRIBUTES_COUNT(0);
+ }
+ }
+
+#undef CHECK_ELEMENT_IS_CHILD_OF
+#undef CHECK_ELEMENT_ATTRIBUTES_COUNT
+#undef CHECK_ELEMENT_HAS_ATTRIBUTE
+#undef CHECK_ELEMENT_ATTRIBUTES
+
+ mElements.push(qName);
+ mCurrentText.clear();
+ return true;
+}
+
+bool Profile::Private::XmlHandler::endElement(const QString &namespaceURI,
+ const QString &localName, const QString &qName)
+{
+ if (namespaceURI != xmlNs) {
+ // ignore all elements with unknown xmlns
+ debug() << "Ignoring unknown xmlns" << namespaceURI;
+ return true;
+ } else if (qName == elemName) {
+ mData->name = mCurrentText;
+ } else if (qName == elemParam) {
+ mCurrentParameter.setValue(ManagerFile::parseValueWithDBusSignature(mCurrentText,
+ mCurrentParameter.dbusSignature().signature()));
+ mData->parameters.append(Profile::Parameter(mCurrentParameter));
+ } else if (qName == elemCC) {
+ mData->unsupportedChannelClassSpecs.append(RequestableChannelClassSpec(mCurrentCC));
+ mCurrentCC.fixedProperties.clear();
+ } else if (qName == elemProperty) {
+ mCurrentCC.fixedProperties[mCurrentPropertyName] =
+ ManagerFile::parseValueWithDBusSignature(mCurrentText,
+ mCurrentPropertyType);
+ }
+
+ mElements.pop();
+ return true;
+}
+
+bool Profile::Private::XmlHandler::characters(const QString &str)
+{
+ mCurrentText += str;
+ return true;
+}
+
+bool Profile::Private::XmlHandler::fatalError(
+ const QXmlParseException &exception)
+{
+ mErrorString = QString(QLatin1String("parse error at line %1, column %2: "
+ "%3"))
+ .arg(exception.lineNumber())
+ .arg(exception.columnNumber())
+ .arg(exception.message());
+ return false;
+}
+
+QString Profile::Private::XmlHandler::errorString() const
+{
+ return mErrorString;
+}
+
+bool Profile::Private::XmlHandler::attributeValueAsBoolean(
+ const QXmlAttributes &attributes, const QString &qName)
+{
+ QString tmpStr = attributes.value(qName);
+ if (tmpStr == QLatin1String("1") ||
+ tmpStr == QLatin1String("true")) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+Profile::Private::Private()
+ : valid(false),
+ fake(false),
+ allowNonIMType(false)
+{
+}
+
+void Profile::Private::setServiceName(const QString &serviceName_)
+{
+ invalidate();
+
+ allowNonIMType = false;
+ serviceName = serviceName_;
+ lookupProfile();
+}
+
+void Profile::Private::setFileName(const QString &fileName)
+{
+ invalidate();
+
+ allowNonIMType = true;
+ QFileInfo fi(fileName);
+ serviceName = fi.baseName();
+
+ debug() << "Loading profile file" << fileName;
+
+ QFile file(fileName);
+ if (!file.exists()) {
+ warning() << QString(QLatin1String("Error parsing profile file %1: file does not exist"))
+ .arg(file.fileName());
+ return;
+ }
+
+ if (!file.open(QFile::ReadOnly)) {
+ warning() << QString(QLatin1String("Error parsing profile file %1: "
+ "cannot open file for readonly access"))
+ .arg(file.fileName());
+ return;
+ }
+
+ if (parse(&file)) {
+ debug() << "Profile file" << fileName << "loaded successfully";
+ }
+}
+
+void Profile::Private::lookupProfile()
+{
+ debug() << "Searching profile for service" << serviceName;
+
+ QStringList searchDirs = Profile::searchDirs();
+ bool found = false;
+ foreach (const QString searchDir, searchDirs) {
+ QString fileName = searchDir + serviceName + QLatin1String(".profile");
+
+ QFile file(fileName);
+ if (!file.exists()) {
+ continue;
+ }
+
+ if (!file.open(QFile::ReadOnly)) {
+ continue;
+ }
+
+ if (parse(&file)) {
+ debug() << "Profile for service" << serviceName << "found:" << fileName;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ debug() << "Cannot find valid profile for service" << serviceName;
+ }
+}
+
+bool Profile::Private::parse(QFile *file)
+{
+ invalidate();
+
+ fake = false;
+ QFileInfo fi(file->fileName());
+ XmlHandler xmlHandler(serviceName, allowNonIMType, &data);
+
+ QXmlSimpleReader xmlReader;
+ xmlReader.setContentHandler(&xmlHandler);
+ xmlReader.setErrorHandler(&xmlHandler);
+
+ QXmlInputSource xmlInputSource(file);
+ if (!xmlReader.parse(xmlInputSource)) {
+ warning() << QString(QLatin1String("Error parsing profile file %1: %2"))
+ .arg(file->fileName())
+ .arg(xmlHandler.errorString());
+ invalidate();
+ return false;
+ }
+
+ valid = true;
+ return true;
+}
+
+void Profile::Private::invalidate()
+{
+ valid = false;
+ data.clear();
+}
+
+/**
+ * \class Profile
+ * \ingroup utils
+ * \headerfile TelepathyQt4/profile.h <TelepathyQt4/Profile>
+ *
+ * \brief The Profile class provides an easy way to read Telepathy profile
+ * files according to http://telepathy.freedesktop.org/wiki/service-profile-v1.
+ *
+ * Note that profiles with xml element <type> different than "IM" are considered
+ * invalid.
+ */
+
+/**
+ * Create a new Profile object used to read .profiles compliant files.
+ *
+ * \param serviceName The profile service name.
+ * \return A ProfilePtr object pointing to the newly created Profile object.
+ */
+ProfilePtr Profile::createForServiceName(const QString &serviceName)
+{
+ ProfilePtr profile = ProfilePtr(new Profile());
+ profile->setServiceName(serviceName);
+ return profile;
+}
+
+/**
+ * Create a new Profile object used to read .profiles compliant files.
+ *
+ * \param fileName The profile file name.
+ * \return A ProfilePtr object pointing to the newly created Profile object.
+ */
+ProfilePtr Profile::createForFileName(const QString &fileName)
+{
+ ProfilePtr profile = ProfilePtr(new Profile());
+ profile->setFileName(fileName);
+ return profile;
+}
+
+/**
+ * Construct a new Profile object used to read .profiles compliant files.
+ *
+ * \param serviceName The profile service name.
+ */
+Profile::Profile()
+ : mPriv(new Private())
+{
+}
+
+/**
+ * Construct a fake profile using the given \a serviceName, \a cmName,
+ * \a protocolName and \a protocolInfo.
+ *
+ * - isFake() will return \c true
+ * - type() will return "IM"
+ * - provider() will return an empty string
+ * - serviceName() will return \a serviceName
+ * - name() and protocolName() will return \a protocolName
+ * - iconName() will return "im-\a protocolName"
+ * - cmName() will return \a cmName
+ * - parameters() will return a list matching CM default parameters
+ * - presences() will return an empty list and allowOtherPresences will return
+ * \c true, meaning that CM presences should be used
+ * - unsupportedChannelClassSpecs() will return an empty list
+ *
+ * \param serviceName The service name.
+ * \param cmName The connection manager name.
+ * \param protocolName The protocol name.
+ * \param protocolInfo The protocol info for the protocol \a protocolName.
+ */
+Profile::Profile(const QString &serviceName, const QString &cmName,
+ const QString &protocolName, const ProtocolInfo &protocolInfo)
+ : mPriv(new Private())
+{
+ mPriv->serviceName = serviceName;
+
+ mPriv->data.type = QString(QLatin1String("IM"));
+ // provider is empty
+ mPriv->data.name = protocolName;
+ mPriv->data.iconName = QString(QLatin1String("im-%1")).arg(protocolName);
+ mPriv->data.cmName = cmName;
+ mPriv->data.protocolName = protocolName;
+
+ foreach (const ProtocolParameter &protocolParam, protocolInfo.parameters()) {
+ if (!protocolParam.defaultValue().isNull()) {
+ mPriv->data.parameters.append(Profile::Parameter(
+ protocolParam.name(),
+ protocolParam.dbusSignature(),
+ protocolParam.defaultValue(),
+ QString(), // label
+ false)); // mandatory
+ }
+ }
+
+ // parameters will be the same as CM parameters
+ // set allow others to true meaning that the standard CM presences are
+ // supported
+ mPriv->data.allowOtherPresences = true;
+ // presences will be the same as CM presences
+ // unsupported channel classes is empty
+
+ mPriv->valid = true;
+ mPriv->fake = true;
+}
+
+/**
+ * Class destructor.
+ */
+Profile::~Profile()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the unique name of the service to which this profile applies.
+ *
+ * \return The unique name of the service.
+ */
+QString Profile::serviceName() const
+{
+ return mPriv->serviceName;
+}
+
+/**
+ * Return whether this profile is valid.
+ *
+ * \return \c true if valid, otherwise \c false.
+ */
+bool Profile::isValid() const
+{
+ return mPriv->valid;
+}
+
+/**
+ * Return whether this profile is fake.
+ *
+ * Fake profiles are profiles created for services not providing a .profile
+ * file.
+ *
+ * \return \c true if fake, otherwise \c false.
+ */
+bool Profile::isFake() const
+{
+ return mPriv->fake;
+}
+
+/**
+ * Return the type of the service to which this profile applies.
+ *
+ * In general, services of interest of Telepathy should be of type 'IM'.
+ * Other service types exist but are unlikely to affect Telepathy in any way.
+ *
+ * \return The type of the service.
+ */
+QString Profile::type() const
+{
+ return mPriv->data.type;
+}
+
+/**
+ * Return the name of the vendor/organisation/provider who actually runs the
+ * service to which this profile applies.
+ *
+ * \return The provider of the service.
+ */
+QString Profile::provider() const
+{
+ return mPriv->data.provider;
+}
+
+/**
+ * Return the human-readable name for the service to which this profile applies.
+ *
+ * \return The Human-readable name of the service.
+ */
+QString Profile::name() const
+{
+ return mPriv->data.name;
+}
+
+/**
+ * Return the base name of the icon for the service to which this profile
+ * applies.
+ *
+ * \return The base name of the icon for the service.
+ */
+QString Profile::iconName() const
+{
+ return mPriv->data.iconName;
+}
+
+/**
+ * Return the connection manager name for the service to which this profile
+ * applies.
+ *
+ * \return The connection manager name for the service.
+ */
+QString Profile::cmName() const
+{
+ return mPriv->data.cmName;
+}
+
+/**
+ * Return the protocol name for the service to which this profile applies.
+ *
+ * \return The protocol name for the service.
+ */
+QString Profile::protocolName() const
+{
+ return mPriv->data.protocolName;
+}
+
+/**
+ * Return a list of parameters defined for the service to which this profile
+ * applies.
+ *
+ * \return A list of Profile::Parameter.
+ */
+Profile::ParameterList Profile::parameters() const
+{
+ return mPriv->data.parameters;
+}
+
+/**
+ * Return whether this profile defines the parameter named \a name.
+ *
+ * \return \c true if parameter is defined, otherwise \c false.
+ */
+bool Profile::hasParameter(const QString &name) const
+{
+ foreach (const Parameter &parameter, mPriv->data.parameters) {
+ if (parameter.name() == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return the parameter for a given \a name.
+ *
+ * \return A Profile::Parameter.
+ */
+Profile::Parameter Profile::parameter(const QString &name) const
+{
+ foreach (const Parameter &parameter, mPriv->data.parameters) {
+ if (parameter.name() == name) {
+ return parameter;
+ }
+ }
+ return Profile::Parameter();
+}
+
+/**
+ * Return whether the standard CM presences not defined in presences() are
+ * supported.
+ *
+ * \return \c true if standard CM presences are supported, otherwise \c false.
+ */
+bool Profile::allowOtherPresences() const
+{
+ return mPriv->data.allowOtherPresences;
+}
+
+/**
+ * Return a list of presences defined for the service to which this profile
+ * applies.
+ *
+ * \return A list of Profile::Presence.
+ */
+Profile::PresenceList Profile::presences() const
+{
+ return mPriv->data.presences;
+}
+
+/**
+ * Return whether this profile defines the presence with id \a id.
+ *
+ * \return \c true if presence is defined, otherwise \c false.
+ */
+bool Profile::hasPresence(const QString &id) const
+{
+ foreach (const Presence &presence, mPriv->data.presences) {
+ if (presence.id() == id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return the presence for a given \a id.
+ *
+ * \return A Profile::Presence.
+ */
+Profile::Presence Profile::presence(const QString &id) const
+{
+ foreach (const Presence &presence, mPriv->data.presences) {
+ if (presence.id() == id) {
+ return presence;
+ }
+ }
+ return Profile::Presence();
+}
+
+/**
+ * A list of channel classes not supported by the service to which this profile
+ * applies.
+ *
+ * \return A list of RequestableChannelClassSpec.
+ */
+RequestableChannelClassSpecList Profile::unsupportedChannelClassSpecs() const
+{
+ return mPriv->data.unsupportedChannelClassSpecs;
+}
+
+void Profile::setServiceName(const QString &serviceName)
+{
+ mPriv->setServiceName(serviceName);
+}
+
+void Profile::setFileName(const QString &fileName)
+{
+ mPriv->setFileName(fileName);
+}
+
+QStringList Profile::searchDirs()
+{
+ QStringList ret;
+
+ QString xdgDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"));
+ if (xdgDataHome.isEmpty()) {
+ ret << QDir::homePath() + QLatin1String("/.local/share/data/telepathy/profiles/");
+ } else {
+ ret << xdgDataHome + QLatin1String("/telepathy/profiles/");
+ }
+
+ QString xdgDataDirsEnv = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS"));
+ if (xdgDataDirsEnv.isEmpty()) {
+ ret << QLatin1String("/usr/local/share/telepathy/profiles/");
+ ret << QLatin1String("/usr/share/telepathy/profiles/");
+ } else {
+ QStringList xdgDataDirs = xdgDataDirsEnv.split(QLatin1Char(':'));
+ foreach (const QString xdgDataDir, xdgDataDirs) {
+ ret << xdgDataDir + QLatin1String("/telepathy/profiles/");
+ }
+ }
+
+ return ret;
+}
+
+
+struct TELEPATHY_QT4_NO_EXPORT Profile::Parameter::Private
+{
+ QString name;
+ QDBusSignature dbusSignature;
+ QVariant value;
+ QString label;
+ bool mandatory;
+};
+
+/**
+ * \class Profile::Parameter
+ * \ingroup utils
+ * \headerfile TelepathyQt4/profile.h <TelepathyQt4/Profile>
+ *
+ * \brief The Profile::Parameter class represents a parameter defined in
+ * .profile files.
+ */
+
+/**
+ * Construct a new Profile::Parameter object.
+ */
+Profile::Parameter::Parameter()
+ : mPriv(new Private)
+{
+ mPriv->mandatory = false;
+}
+
+/**
+ * Construct a new Profile::Parameter object that is a copy of \a other.
+ */
+Profile::Parameter::Parameter(const Parameter &other)
+ : mPriv(new Private)
+{
+ mPriv->name = other.mPriv->name;
+ mPriv->dbusSignature = other.mPriv->dbusSignature;
+ mPriv->value = other.mPriv->value;
+ mPriv->label = other.mPriv->label;
+ mPriv->mandatory = other.mPriv->mandatory;
+}
+
+/**
+ * Construct a new Profile::Parameter object.
+ *
+ * \param name The parameter name.
+ * \param dbusSignature The parameter D-Bus signature.
+ * \param value The parameter value.
+ * \param label The parameter label.
+ * \param mandatory Whether this parameter is mandatory.
+ */
+Profile::Parameter::Parameter(const QString &name,
+ const QDBusSignature &dbusSignature,
+ const QVariant &value,
+ const QString &label,
+ bool mandatory)
+ : mPriv(new Private)
+{
+ mPriv->name = name;
+ mPriv->dbusSignature = dbusSignature;
+ mPriv->value = value;
+ mPriv->label = label;
+ mPriv->mandatory = mandatory;
+}
+
+/**
+ * Class destructor.
+ */
+Profile::Parameter::~Parameter()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the name of this parameter.
+ *
+ * \return The name of this parameter.
+ */
+QString Profile::Parameter::name() const
+{
+ return mPriv->name;
+}
+
+void Profile::Parameter::setName(const QString &name)
+{
+ mPriv->name = name;
+}
+
+/**
+ * Return the D-Bus signature of this parameter.
+ *
+ * \return The D-Bus signature of this parameter.
+ */
+QDBusSignature Profile::Parameter::dbusSignature() const
+{
+ return mPriv->dbusSignature;
+}
+
+void Profile::Parameter::setDBusSignature(const QDBusSignature &dbusSignature)
+{
+ mPriv->dbusSignature = dbusSignature;
+}
+
+/**
+ * Return the QVariant::Type of this parameter, constructed using
+ * dbusSignature().
+ *
+ * \return The QVariant::Type of this parameter.
+ */
+QVariant::Type Profile::Parameter::type() const
+{
+ return ManagerFile::variantTypeFromDBusSignature(mPriv->dbusSignature.signature());
+}
+
+/**
+ * Return the value of this parameter.
+ *
+ * If mandatory() returns \c true, the value must not be modified and should be
+ * used as is when creating accounts for this profile.
+ *
+ * \return The value of this parameter.
+ */
+QVariant Profile::Parameter::value() const
+{
+ return mPriv->value;
+}
+
+void Profile::Parameter::setValue(const QVariant &value)
+{
+ mPriv->value = value;
+}
+
+/**
+ * Return the human-readable label of this parameter.
+ *
+ * \return The human-readable label of this parameter.
+ */
+QString Profile::Parameter::label() const
+{
+ return mPriv->label;
+}
+
+void Profile::Parameter::setLabel(const QString &label)
+{
+ mPriv->label = label;
+}
+
+/**
+ * Return whether this parameter is mandatory, or whether the value returned by
+ * value() should be used as is when creating accounts for this profile.
+ *
+ * \return \c true if mandatory, otherwise \c false.
+ */
+bool Profile::Parameter::isMandatory() const
+{
+ return mPriv->mandatory;
+}
+
+void Profile::Parameter::setMandatory(bool mandatory)
+{
+ mPriv->mandatory = mandatory;
+}
+
+Profile::Parameter &Profile::Parameter::operator=(const Profile::Parameter &other)
+{
+ mPriv->name = other.mPriv->name;
+ mPriv->dbusSignature = other.mPriv->dbusSignature;
+ mPriv->value = other.mPriv->value;
+ mPriv->label = other.mPriv->label;
+ mPriv->mandatory = other.mPriv->mandatory;
+ return *this;
+}
+
+
+struct TELEPATHY_QT4_NO_EXPORT Profile::Presence::Private
+{
+ QString id;
+ QString label;
+ QString iconName;
+ QString message;
+ bool disabled;
+};
+
+/**
+ * \class Profile::Presence
+ * \ingroup utils
+ * \headerfile TelepathyQt4/profile.h <TelepathyQt4/Profile>
+ *
+ * \brief The Profile::Presence class represents a presence defined in
+ * .profile files.
+ */
+
+/**
+ * Construct a new Profile::Presence object.
+ */
+Profile::Presence::Presence()
+ : mPriv(new Private)
+{
+ mPriv->disabled = false;
+}
+
+/**
+ * Construct a new Profile::Presence object that is a copy of \a other.
+ */
+Profile::Presence::Presence(const Presence &other)
+ : mPriv(new Private)
+{
+ mPriv->id = other.mPriv->id;
+ mPriv->label = other.mPriv->label;
+ mPriv->iconName = other.mPriv->iconName;
+ mPriv->message = other.mPriv->message;
+ mPriv->disabled = other.mPriv->disabled;
+}
+
+/**
+ * Construct a new Profile::Presence object.
+ *
+ * \param id The presence id.
+ * \param label The presence label.
+ * \param iconName The presence icon name.
+ * \param message The presence message.
+ * \param disabled Whether this presence is supported.
+ */
+Profile::Presence::Presence(const QString &id,
+ const QString &label,
+ const QString &iconName,
+ const QString &message,
+ bool disabled)
+ : mPriv(new Private)
+{
+ mPriv->id = id;
+ mPriv->label = label;
+ mPriv->iconName = iconName;
+ mPriv->message = message;
+ mPriv->disabled = disabled;
+}
+
+/**
+ * Class destructor.
+ */
+Profile::Presence::~Presence()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the Telepathy presence id for this presence.
+ *
+ * \return The Telepathy presence id for this presence.
+ */
+QString Profile::Presence::id() const
+{
+ return mPriv->id;
+}
+
+void Profile::Presence::setId(const QString &id)
+{
+ mPriv->id = id;
+}
+
+/**
+ * Return the label that should be used for this presence.
+ *
+ * \return The label for this presence.
+ */
+QString Profile::Presence::label() const
+{
+ return mPriv->label;
+}
+
+void Profile::Presence::setLabel(const QString &label)
+{
+ mPriv->label = label;
+}
+
+/**
+ * Return the icon name of this presence.
+ *
+ * \return The icon name of this presence.
+ */
+QString Profile::Presence::iconName() const
+{
+ return mPriv->iconName;
+}
+
+void Profile::Presence::setIconName(const QString &iconName)
+{
+ mPriv->iconName = iconName;
+}
+
+/**
+ * Return whether user-defined text-message can be attached to this presence.
+ *
+ * \return \c true if user-defined text-message can be attached to this presence, \c false
+ * otherwise.
+ */
+bool Profile::Presence::canHaveStatusMessage() const
+{
+ if (mPriv->message == QLatin1String("1") ||
+ mPriv->message == QLatin1String("true")) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * \deprecated Use canHaveStatusMessage() instead.
+ */
+QString Profile::Presence::message() const
+{
+ return mPriv->message;
+}
+
+void Profile::Presence::setMessage(const QString &message)
+{
+ mPriv->message = message;
+}
+
+/**
+ * Return whether this presence is supported for the service to which this
+ * profile applies.
+ *
+ * \return \c true if supported, otherwise \c false.
+ */
+bool Profile::Presence::isDisabled() const
+{
+ return mPriv->disabled;
+}
+
+void Profile::Presence::setDisabled(bool disabled)
+{
+ mPriv->disabled = disabled;
+}
+
+Profile::Presence &Profile::Presence::operator=(const Profile::Presence &other)
+{
+ mPriv->id = other.mPriv->id;
+ mPriv->label = other.mPriv->label;
+ mPriv->iconName = other.mPriv->iconName;
+ mPriv->message = other.mPriv->message;
+ mPriv->disabled = other.mPriv->disabled;
+ return *this;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/profile.h b/qt4/TelepathyQt4/profile.h
new file mode 100644
index 000000000..cb4661b8d
--- /dev/null
+++ b/qt4/TelepathyQt4/profile.h
@@ -0,0 +1,176 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_profile_h_HEADER_GUARD_
+#define _TelepathyQt4_profile_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/RequestableChannelClassSpec>
+#include <TelepathyQt4/Types>
+
+#include <QDBusSignature>
+#include <QObject>
+#include <QString>
+#include <QVariant>
+
+namespace Tp
+{
+
+class ProtocolInfo;
+
+class TELEPATHY_QT4_EXPORT Profile : public RefCounted
+{
+ Q_DISABLE_COPY(Profile);
+
+public:
+ static ProfilePtr createForServiceName(const QString &serviceName);
+ static ProfilePtr createForFileName(const QString &fileName);
+
+ ~Profile();
+
+ QString serviceName() const;
+
+ bool isValid() const;
+
+ bool isFake() const;
+
+ QString type() const;
+ QString provider() const;
+ QString name() const;
+ QString iconName() const;
+ QString cmName() const;
+ QString protocolName() const;
+
+ class Parameter
+ {
+ public:
+ Parameter();
+ Parameter(const Parameter &other);
+ Parameter(const QString &name,
+ const QDBusSignature &dbusSignature,
+ const QVariant &value,
+ const QString &label,
+ bool mandatory);
+ ~Parameter();
+
+ QString name() const;
+ QDBusSignature dbusSignature() const;
+ QVariant::Type type() const;
+ QVariant value() const;
+ QString label() const;
+
+ bool isMandatory() const;
+
+ // TODO Add matches(Tp::Presence) method
+
+ Parameter &operator=(const Parameter &other);
+
+ private:
+ friend class Profile;
+
+ TELEPATHY_QT4_NO_EXPORT void setName(const QString &name);
+ TELEPATHY_QT4_NO_EXPORT void setDBusSignature(const QDBusSignature &dbusSignature);
+ TELEPATHY_QT4_NO_EXPORT void setValue(const QVariant &value);
+ TELEPATHY_QT4_NO_EXPORT void setLabel(const QString &label);
+ TELEPATHY_QT4_NO_EXPORT void setMandatory(bool mandatory);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+ };
+ typedef QList<Parameter> ParameterList;
+
+ ParameterList parameters() const;
+ bool hasParameter(const QString &name) const;
+ Parameter parameter(const QString &name) const;
+
+ class Presence
+ {
+ public:
+ Presence();
+ Presence(const Presence &other);
+ Presence(const QString &id,
+ const QString &label,
+ const QString &iconName,
+ const QString &message,
+ bool disabled);
+ ~Presence();
+
+ QString id() const;
+ QString label() const;
+ QString iconName() const;
+ bool canHaveStatusMessage() const;
+ TELEPATHY_QT4_DEPRECATED QString message() const;
+
+ bool isDisabled() const;
+
+ Presence &operator=(const Presence &other);
+
+ private:
+ friend class Profile;
+
+ TELEPATHY_QT4_NO_EXPORT void setId(const QString &id);
+ TELEPATHY_QT4_NO_EXPORT void setLabel(const QString &label);
+ TELEPATHY_QT4_NO_EXPORT void setIconName(const QString &iconName);
+ TELEPATHY_QT4_NO_EXPORT void setMessage(const QString &message);
+ TELEPATHY_QT4_NO_EXPORT void setDisabled(bool disabled);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+ };
+ typedef QList<Presence> PresenceList;
+
+ bool allowOtherPresences() const;
+ PresenceList presences() const;
+ bool hasPresence(const QString &id) const;
+ Presence presence(const QString &id) const;
+
+ RequestableChannelClassSpecList unsupportedChannelClassSpecs() const;
+
+private:
+ friend class Account;
+ friend class ProfileManager;
+
+ TELEPATHY_QT4_NO_EXPORT Profile();
+ TELEPATHY_QT4_NO_EXPORT Profile(const QString &serviceName, const QString &cmName,
+ const QString &protocolName, const ProtocolInfo &protocolInfo);
+
+ TELEPATHY_QT4_NO_EXPORT void setServiceName(const QString &serviceName);
+ TELEPATHY_QT4_NO_EXPORT void setFileName(const QString &fileName);
+
+ TELEPATHY_QT4_NO_EXPORT static QStringList searchDirs();
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::Profile::Parameter);
+Q_DECLARE_METATYPE(Tp::Profile::Presence);
+
+#endif
diff --git a/qt4/TelepathyQt4/properties.cpp b/qt4/TelepathyQt4/properties.cpp
new file mode 100644
index 000000000..2cdb4350f
--- /dev/null
+++ b/qt4/TelepathyQt4/properties.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Properties>
+
+#include "TelepathyQt4/_gen/cli-properties-body.hpp"
+#include "TelepathyQt4/_gen/cli-properties.moc.hpp"
diff --git a/qt4/TelepathyQt4/properties.h b/qt4/TelepathyQt4/properties.h
new file mode 100644
index 000000000..32b33848c
--- /dev/null
+++ b/qt4/TelepathyQt4/properties.h
@@ -0,0 +1,51 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_properties_h_HEADER_GUARD_
+#define _TelepathyQt4_properties_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+/**
+ * \addtogroup clientsideproxies Client-side proxies
+ *
+ * Proxy objects representing remote service objects accessed via D-Bus.
+ *
+ * In addition to providing direct access to methods, signals and properties
+ * exported by the remote objects, some of these proxies offer features like
+ * automatic inspection of remote object capabilities, property tracking,
+ * backwards compatibility helpers for older services and other utilities.
+ */
+
+/**
+ * \defgroup clientprops Telepathy Properties proxy
+ * \ingroup clientsideproxies
+ *
+ * Proxy object representing the Telepathy Properties interface on remote
+ * objects.
+ */
+
+#include <TelepathyQt4/_gen/cli-properties.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/properties.xml b/qt4/TelepathyQt4/properties.xml
new file mode 100644
index 000000000..2f59b89cf
--- /dev/null
+++ b/qt4/TelepathyQt4/properties.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>The Telepathy properties interface</tp:title>
+
+<xi:include href="../spec/Properties_Interface.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/protocol-info.cpp b/qt4/TelepathyQt4/protocol-info.cpp
new file mode 100644
index 000000000..e7552b499
--- /dev/null
+++ b/qt4/TelepathyQt4/protocol-info.cpp
@@ -0,0 +1,374 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ProtocolInfo>
+
+#include <TelepathyQt4/ConnectionCapabilities>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ProtocolInfo::Private : public QSharedData
+{
+ Private()
+ {
+ }
+
+ Private(const QString &cmName, const QString &name)
+ : cmName(cmName),
+ name(name),
+ iconName(QString(QLatin1String("im-%1")).arg(name))
+ {
+ }
+
+ QString cmName;
+ QString name;
+ ProtocolParameterList params;
+ ConnectionCapabilities caps;
+ QString vcardField;
+ QString englishName;
+ QString iconName;
+ PresenceSpecList statuses;
+ AvatarSpec avatarRequirements;
+};
+
+/**
+ * \class ProtocolInfo
+ * \ingroup clientcm
+ * \headerfile TelepathyQt4/protocol-info.h <TelepathyQt4/ProtocolInfo>
+ *
+ * \brief The ProtocolInfo class represents a <a
+ * href="http://telepathy.freedesktop.org/spec/Protocol.html">Telepathy Protocol</a>.
+ */
+
+ProtocolInfo::ProtocolInfo()
+{
+}
+
+/**
+ * Construct a new ProtocolInfo object.
+ *
+ * \param cmName Connection manager name.
+ * \param name Protocol name.
+ */
+ProtocolInfo::ProtocolInfo(const QString &cmName, const QString &name)
+ : mPriv(new Private(cmName, name))
+{
+}
+
+ProtocolInfo::ProtocolInfo(const ProtocolInfo &other)
+ : mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+ProtocolInfo::~ProtocolInfo()
+{
+}
+
+ProtocolInfo &ProtocolInfo::operator=(const ProtocolInfo &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+/**
+ * Return the short name of the connection manager (e.g. "gabble") for this protocol.
+ *
+ * \return The name of the connection manager for this protocol.
+ */
+QString ProtocolInfo::cmName() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->cmName;
+}
+
+/**
+ * Return the string identifying this protocol as described in the \telepathy_spec
+ * (e.g. "jabber").
+ *
+ * This identifier is not intended to be displayed to users directly; user
+ * interfaces are responsible for mapping them to localized strings.
+ *
+ * \return A string identifying this protocol.
+ */
+QString ProtocolInfo::name() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->name;
+}
+
+/**
+ * Return all supported parameters for this protocol. The parameters' names
+ * may either be the well-known strings specified by the \telepathy_spec
+ * (e.g. "account" and "password"), or implementation-specific strings.
+ *
+ * \return A list of parameters for this protocol.
+ */
+ProtocolParameterList ProtocolInfo::parameters() const
+{
+ if (!isValid()) {
+ return ProtocolParameterList();
+ }
+
+ return mPriv->params;
+}
+
+/**
+ * Return whether a given parameter can be passed to the connection
+ * manager when creating a connection to this protocol.
+ *
+ * \param name The name of a parameter.
+ * \return true if the given parameter exists.
+ */
+bool ProtocolInfo::hasParameter(const QString &name) const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ foreach (const ProtocolParameter &param, mPriv->params) {
+ if (param.name() == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return whether it might be possible to register new accounts on this
+ * protocol, by setting the special parameter named
+ * <code>register</code> to <code>true</code>.
+ *
+ * \return The same thing as hasParameter("register").
+ * \sa hasParameter()
+ */
+bool ProtocolInfo::canRegister() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return hasParameter(QLatin1String("register"));
+}
+
+/**
+ * Return the capabilities that are expected to be available from a connection
+ * to this protocol, i.e. those for which Connection::createChannel() can
+ * reasonably be expected to succeed.
+ * User interfaces can use this information to show or hide UI components.
+ *
+ * @return An object representing the capabilities expected to be available from
+ * a connection to this protocol.
+ */
+ConnectionCapabilities ProtocolInfo::capabilities() const
+{
+ if (!isValid()) {
+ return ConnectionCapabilities();
+ }
+
+ return mPriv->caps;
+}
+
+/**
+ * Return the name of the most common vCard field used for this protocol's
+ * contact identifiers, normalized to lower case.
+ *
+ * One valid use of this field is to answer the question: given a contact's
+ * vCard containing an X-JABBER field, how can you communicate with the contact?
+ * By iterating through protocols looking for an x-jabber VCardField, one can
+ * build up a list of protocols that handle x-jabber, then offer the user a list
+ * of accounts for those protocols and/or the option to create a new account for
+ * one of those protocols.
+ * It is not necessarily valid to interpret contacts' identifiers as values of
+ * this vCard field. For instance, telepathy-sofiasip supports contacts whose
+ * identifiers are of the form sip:jenny@example.com or tel:8675309, which would
+ * not normally both be represented by any single vCard field.
+ *
+ * \return The most common vCard field used for this protocol's contact
+ * identifiers, or an empty string if there is no such field.
+ */
+QString ProtocolInfo::vcardField() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->vcardField;
+}
+
+/**
+ * Return the English-language name of this protocol, such as "AIM" or "Yahoo!".
+ *
+ * The name can be used as a fallback if an application doesn't have a localized name for this
+ * protocol.
+ *
+ * If the manager file or the CM service doesn't specify the english name, it is inferred from this
+ * protocol name, such that for example "google-talk" becomes "Google Talk", but "local-xmpp"
+ * becomes "Local Xmpp".
+ *
+ * \return An English-language name for this protocol.
+ */
+QString ProtocolInfo::englishName() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->englishName;
+}
+
+/**
+ * Return the name of an icon for this protocol in the system's icon theme, such as "im-msn".
+ *
+ * If the manager file or the CM service doesn't specify the icon name, "im-<protocolname>" is
+ * assumed.
+ *
+ * \return The likely name of an icon for this protocol.
+ */
+QString ProtocolInfo::iconName() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->iconName;
+}
+
+/**
+ * Return a list of PresenceSpec representing the possible presence statuses
+ * from a connection to this protocol.
+ *
+ * \return A list of PresenceSpec representing the possible presence statuses
+ * from a connection to this protocol.
+ */
+PresenceSpecList ProtocolInfo::allowedPresenceStatuses() const
+{
+ if (!isValid()) {
+ return PresenceSpecList();
+ }
+
+ return mPriv->statuses;
+}
+
+/**
+ * Return the requirements (size limits, supported MIME types, etc)
+ * for avatars used on to this protocol.
+ *
+ * \return The requirements for avatars used on this protocol.
+ */
+AvatarSpec ProtocolInfo::avatarRequirements() const
+{
+ if (!isValid()) {
+ return AvatarSpec();
+ }
+
+ return mPriv->avatarRequirements;
+}
+
+void ProtocolInfo::addParameter(const ParamSpec &spec)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ QVariant defaultValue;
+ if (spec.flags & ConnMgrParamFlagHasDefault) {
+ defaultValue = spec.defaultValue.variant();
+ }
+
+ uint flags = spec.flags;
+ if (spec.name.endsWith(QLatin1String("password"))) {
+ flags |= ConnMgrParamFlagSecret;
+ }
+
+ ProtocolParameter param(spec.name,
+ QDBusSignature(spec.signature),
+ defaultValue,
+ (ConnMgrParamFlag) flags);
+ mPriv->params.append(param);
+}
+
+void ProtocolInfo::setVCardField(const QString &vcardField)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->vcardField = vcardField;
+}
+
+void ProtocolInfo::setEnglishName(const QString &englishName)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->englishName = englishName;
+}
+
+void ProtocolInfo::setIconName(const QString &iconName)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->iconName = iconName;
+}
+
+void ProtocolInfo::setRequestableChannelClasses(
+ const RequestableChannelClassList &caps)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->caps.updateRequestableChannelClasses(caps);
+}
+
+void ProtocolInfo::setAllowedPresenceStatuses(const PresenceSpecList &statuses)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->statuses = statuses;
+}
+
+void ProtocolInfo::setAvatarRequirements(const AvatarSpec &avatarRequirements)
+{
+ if (!isValid()) {
+ mPriv = new Private;
+ }
+
+ mPriv->avatarRequirements = avatarRequirements;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/protocol-info.h b/qt4/TelepathyQt4/protocol-info.h
new file mode 100644
index 000000000..7eb652c39
--- /dev/null
+++ b/qt4/TelepathyQt4/protocol-info.h
@@ -0,0 +1,102 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_protocol_info_h_HEADER_GUARD_
+#define _TelepathyQt4_protocol_info_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/AvatarSpec>
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/PresenceSpec>
+#include <TelepathyQt4/ProtocolParameter>
+#include <TelepathyQt4/Types>
+
+#include <QSharedDataPointer>
+#include <QString>
+#include <QList>
+
+namespace Tp
+{
+
+class ConnectionCapabilities;
+
+class TELEPATHY_QT4_EXPORT ProtocolInfo
+{
+public:
+ ProtocolInfo();
+ ProtocolInfo(const ProtocolInfo &other);
+ ~ProtocolInfo();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ ProtocolInfo &operator=(const ProtocolInfo &other);
+
+ QString cmName() const;
+
+ QString name() const;
+
+ ProtocolParameterList parameters() const;
+ bool hasParameter(const QString &name) const;
+
+ bool canRegister() const;
+
+ ConnectionCapabilities capabilities() const;
+
+ QString vcardField() const;
+
+ QString englishName() const;
+
+ QString iconName() const;
+
+ PresenceSpecList allowedPresenceStatuses() const;
+
+ AvatarSpec avatarRequirements() const;
+
+private:
+ friend class ConnectionManager;
+
+ TELEPATHY_QT4_NO_EXPORT ProtocolInfo(const QString &cmName, const QString &name);
+
+ TELEPATHY_QT4_NO_EXPORT void addParameter(const ParamSpec &spec);
+ TELEPATHY_QT4_NO_EXPORT void setVCardField(const QString &vcardField);
+ TELEPATHY_QT4_NO_EXPORT void setEnglishName(const QString &englishName);
+ TELEPATHY_QT4_NO_EXPORT void setIconName(const QString &iconName);
+ TELEPATHY_QT4_NO_EXPORT void setRequestableChannelClasses(const RequestableChannelClassList &caps);
+ TELEPATHY_QT4_NO_EXPORT void setAllowedPresenceStatuses(const PresenceSpecList &statuses);
+ TELEPATHY_QT4_NO_EXPORT void setAvatarRequirements(const AvatarSpec &avatarRequirements);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+typedef QList<ProtocolInfo> ProtocolInfoList;
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ProtocolInfo);
+Q_DECLARE_METATYPE(Tp::ProtocolInfoList);
+
+#endif
diff --git a/qt4/TelepathyQt4/protocol-parameter.cpp b/qt4/TelepathyQt4/protocol-parameter.cpp
new file mode 100644
index 000000000..24557e8f4
--- /dev/null
+++ b/qt4/TelepathyQt4/protocol-parameter.cpp
@@ -0,0 +1,176 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ProtocolParameter>
+
+#include <TelepathyQt4/ManagerFile>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ProtocolParameter::Private : public QSharedData
+{
+ Private(const QString &name, const QDBusSignature &dbusSignature, QVariant::Type type,
+ const QVariant &defaultValue, ConnMgrParamFlag flags)
+ : name(name), dbusSignature(dbusSignature), type(type), defaultValue(defaultValue),
+ flags(flags) {}
+
+ QString name;
+ QDBusSignature dbusSignature;
+ QVariant::Type type;
+ QVariant defaultValue;
+ ConnMgrParamFlag flags;
+};
+
+/**
+ * \class ProtocolParameter
+ * \ingroup clientcm
+ * \headerfile TelepathyQt4/protocol-parameter.h <TelepathyQt4/ProtocolParameter>
+ *
+ * \brief The ProtocolParameter class represents a Telepathy protocol parameter.
+ */
+
+
+ProtocolParameter::ProtocolParameter()
+{
+}
+
+ProtocolParameter::ProtocolParameter(const QString &name,
+ const QDBusSignature &dbusSignature,
+ QVariant defaultValue,
+ ConnMgrParamFlag flags)
+ : mPriv(new Private(name, dbusSignature,
+ ManagerFile::variantTypeFromDBusSignature(dbusSignature.signature()), defaultValue,
+ flags))
+{
+}
+
+ProtocolParameter::ProtocolParameter(const ProtocolParameter &other)
+ : mPriv(other.mPriv)
+{
+}
+
+ProtocolParameter::~ProtocolParameter()
+{
+}
+
+ProtocolParameter &ProtocolParameter::operator=(const ProtocolParameter &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool ProtocolParameter::operator==(const ProtocolParameter &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return true;
+ }
+ return false;
+ }
+
+ return (mPriv->name == other.name());
+}
+
+bool ProtocolParameter::operator==(const QString &name) const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return (mPriv->name == name);
+}
+
+bool ProtocolParameter::operator<(const Tp::ProtocolParameter& other) const
+{
+ return mPriv->name < other.name();
+}
+
+QString ProtocolParameter::name() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+
+ return mPriv->name;
+}
+
+QDBusSignature ProtocolParameter::dbusSignature() const
+{
+ if (!isValid()) {
+ return QDBusSignature();
+ }
+
+ return mPriv->dbusSignature;
+}
+
+QVariant::Type ProtocolParameter::type() const
+{
+ if (!isValid()) {
+ return QVariant::Invalid;
+ }
+
+ return mPriv->type;
+}
+
+QVariant ProtocolParameter::defaultValue() const
+{
+ if (!isValid()) {
+ return QVariant();
+ }
+
+ return mPriv->defaultValue;
+}
+
+bool ProtocolParameter::isRequired() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return mPriv->flags & ConnMgrParamFlagRequired;
+}
+
+bool ProtocolParameter::isSecret() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return mPriv->flags & ConnMgrParamFlagSecret;
+}
+
+bool ProtocolParameter::isRequiredForRegistration() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return mPriv->flags & ConnMgrParamFlagRegister;
+}
+
+uint qHash(const ProtocolParameter& parameter)
+{
+ return qHash(parameter.name());
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/protocol-parameter.h b/qt4/TelepathyQt4/protocol-parameter.h
new file mode 100644
index 000000000..2750413b5
--- /dev/null
+++ b/qt4/TelepathyQt4/protocol-parameter.h
@@ -0,0 +1,87 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_protocol_parameter_h_HEADER_GUARD_
+#define _TelepathyQt4_protocol_parameter_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Global>
+
+#include <QDBusSignature>
+#include <QSharedDataPointer>
+#include <QString>
+#include <QVariant>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT ProtocolParameter
+{
+public:
+ ProtocolParameter();
+ ProtocolParameter(const ProtocolParameter &other);
+ ~ProtocolParameter();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ ProtocolParameter &operator=(const ProtocolParameter &other);
+ bool operator==(const ProtocolParameter &other) const;
+ bool operator==(const QString &name) const;
+ bool operator<(const ProtocolParameter &other) const;
+
+ QString name() const;
+ QDBusSignature dbusSignature() const;
+ QVariant::Type type() const;
+ QVariant defaultValue() const;
+
+ bool isRequired() const;
+ bool isSecret() const;
+ bool isRequiredForRegistration() const;
+
+private:
+ friend class ConnectionManager;
+ friend class ProtocolInfo;
+
+ TELEPATHY_QT4_NO_EXPORT ProtocolParameter(const QString &name,
+ const QDBusSignature &dbusSignature,
+ QVariant defaultValue,
+ ConnMgrParamFlag flags);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+typedef QList<ProtocolParameter> ProtocolParameterList;
+
+uint qHash(const ProtocolParameter &parameter);
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ProtocolParameter);
+Q_DECLARE_METATYPE(Tp::ProtocolParameterList);
+
+#endif
diff --git a/qt4/TelepathyQt4/readiness-helper.cpp b/qt4/TelepathyQt4/readiness-helper.cpp
new file mode 100644
index 000000000..d07b9dd61
--- /dev/null
+++ b/qt4/TelepathyQt4/readiness-helper.cpp
@@ -0,0 +1,684 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ReadinessHelper>
+
+#include "TelepathyQt4/_gen/readiness-helper.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/RefCounted>
+#include <TelepathyQt4/SharedPtr>
+
+#include <QDBusError>
+#include <QSharedData>
+#include <QTimer>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ReadinessHelper::Introspectable::Private : public QSharedData
+{
+ Private(const QSet<uint> &makesSenseForStatuses,
+ const Features &dependsOnFeatures,
+ const QStringList &dependsOnInterfaces,
+ IntrospectFunc introspectFunc,
+ void *introspectFuncData,
+ bool critical)
+ : makesSenseForStatuses(makesSenseForStatuses),
+ dependsOnFeatures(dependsOnFeatures),
+ dependsOnInterfaces(dependsOnInterfaces),
+ introspectFunc(introspectFunc),
+ introspectFuncData(introspectFuncData),
+ critical(critical) {}
+
+ QSet<uint> makesSenseForStatuses;
+ Features dependsOnFeatures;
+ QStringList dependsOnInterfaces;
+ IntrospectFunc introspectFunc;
+ void *introspectFuncData;
+ bool critical;
+};
+
+ReadinessHelper::Introspectable::Introspectable()
+ : mPriv(new Private(QSet<uint>(), Features(), QStringList(), 0, 0, false))
+{
+}
+
+ReadinessHelper::Introspectable::Introspectable(const QSet<uint> &makesSenseForStatuses,
+ const Features &dependsOnFeatures, const QStringList &dependsOnInterfaces,
+ IntrospectFunc introspectFunc, void *introspectFuncData, bool critical)
+ : mPriv(new Private(makesSenseForStatuses, dependsOnFeatures, dependsOnInterfaces,
+ introspectFunc, introspectFuncData, critical))
+{
+}
+
+ReadinessHelper::Introspectable::Introspectable(const Introspectable &other)
+ : mPriv(other.mPriv)
+{
+}
+
+ReadinessHelper::Introspectable::~Introspectable()
+{
+}
+
+ReadinessHelper::Introspectable &ReadinessHelper::Introspectable::operator=(
+ const Introspectable &other)
+{
+ mPriv = other.mPriv;
+ return *this;
+}
+
+struct TELEPATHY_QT4_NO_EXPORT ReadinessHelper::Private
+{
+ Private(ReadinessHelper *parent,
+ RefCounted *object,
+ uint currentStatus,
+ const Introspectables &introspectables);
+ Private(ReadinessHelper *parent,
+ DBusProxy *proxy,
+ uint currentStatus,
+ const Introspectables &introspectables);
+ ~Private();
+
+ void setCurrentStatus(uint newStatus);
+ void setIntrospectCompleted(const Feature &feature, bool success,
+ const QString &errorName = QString(),
+ const QString &errorMessage = QString());
+ void iterateIntrospection();
+ Features depsFor(const Feature &feature); // Recursive dependencies for a feature
+
+ void abortOperations(const QString &errorName, const QString &errorMessage);
+
+ ReadinessHelper *parent;
+ RefCounted *object;
+ DBusProxy *proxy;
+ uint currentStatus;
+ QStringList interfaces;
+ Introspectables introspectables;
+ QSet<uint> supportedStatuses;
+ Features supportedFeatures;
+ Features satisfiedFeatures;
+ Features requestedFeatures;
+ Features missingFeatures;
+ Features pendingFeatures;
+ Features inFlightFeatures;
+ QHash<Feature, QPair<QString, QString> > missingFeaturesErrors;
+ QList<PendingReady *> pendingOperations;
+
+ bool pendingStatusChange;
+ uint pendingStatus;
+};
+
+ReadinessHelper::Private::Private(
+ ReadinessHelper *parent,
+ RefCounted *object,
+ uint currentStatus,
+ const Introspectables &introspectables)
+ : parent(parent),
+ object(object),
+ proxy(0),
+ currentStatus(currentStatus),
+ introspectables(introspectables),
+ pendingStatusChange(false),
+ pendingStatus(-1)
+{
+ for (Introspectables::const_iterator i = introspectables.constBegin();
+ i != introspectables.constEnd(); ++i) {
+ Feature feature = i.key();
+ Introspectable introspectable = i.value();
+ Q_ASSERT(introspectable.mPriv->introspectFunc != 0);
+ supportedStatuses += introspectable.mPriv->makesSenseForStatuses;
+ supportedFeatures += feature;
+ }
+}
+
+ReadinessHelper::Private::Private(
+ ReadinessHelper *parent,
+ DBusProxy *proxy,
+ uint currentStatus,
+ const Introspectables &introspectables)
+ : parent(parent),
+ object(proxy),
+ proxy(proxy),
+ currentStatus(currentStatus),
+ introspectables(introspectables),
+ pendingStatusChange(false),
+ pendingStatus(-1)
+{
+ Q_ASSERT(proxy != 0);
+
+ for (Introspectables::const_iterator i = introspectables.constBegin();
+ i != introspectables.constEnd(); ++i) {
+ Feature feature = i.key();
+ Introspectable introspectable = i.value();
+ Q_ASSERT(introspectable.mPriv->introspectFunc != 0);
+ supportedStatuses += introspectable.mPriv->makesSenseForStatuses;
+ supportedFeatures += feature;
+ }
+}
+
+ReadinessHelper::Private::~Private()
+{
+ const static QString messageDestroyed(QLatin1String("Destroyed"));
+
+ abortOperations(TP_QT4_ERROR_CANCELLED, messageDestroyed);
+}
+
+void ReadinessHelper::Private::setCurrentStatus(uint newStatus)
+{
+ if (currentStatus == newStatus) {
+ return;
+ }
+
+ if (inFlightFeatures.isEmpty()) {
+ currentStatus = newStatus;
+ satisfiedFeatures.clear();
+ missingFeatures.clear();
+
+ // Make all features that were requested for the new status pending again
+ pendingFeatures = requestedFeatures;
+
+ // becomeReady ensures that the recursive dependencies of the requested features are already
+ // in the requested set, so we don't have to re-add them here
+
+ if (supportedStatuses.contains(currentStatus)) {
+ QTimer::singleShot(0, parent, SLOT(iterateIntrospection()));
+ } else {
+ emit parent->statusReady(currentStatus);
+ }
+ } else {
+ debug() << "status changed while introspection process was running";
+ pendingStatusChange = true;
+ pendingStatus = newStatus;
+ }
+}
+
+void ReadinessHelper::Private::setIntrospectCompleted(const Feature &feature,
+ bool success, const QString &errorName, const QString &errorMessage)
+{
+ debug() << "ReadinessHelper::setIntrospectCompleted: feature:" << feature <<
+ "- success:" << success;
+ if (pendingStatusChange) {
+ debug() << "ReadinessHelper::setIntrospectCompleted called while there is "
+ "a pending status change - ignoring";
+
+ inFlightFeatures.remove(feature);
+
+ // ignore all introspection completed as the state changed
+ if (!inFlightFeatures.isEmpty()) {
+ return;
+ }
+ pendingStatusChange = false;
+ setCurrentStatus(pendingStatus);
+ return;
+ }
+
+ Q_ASSERT(pendingFeatures.contains(feature));
+ Q_ASSERT(inFlightFeatures.contains(feature));
+
+ if (success) {
+ satisfiedFeatures.insert(feature);
+ }
+ else {
+ missingFeatures.insert(feature);
+ missingFeaturesErrors.insert(feature,
+ QPair<QString, QString>(errorName, errorMessage));
+ if (errorName.isEmpty()) {
+ warning() << "ReadinessHelper::setIntrospectCompleted: Feature" <<
+ feature << "introspection failed but no error message was given";
+ }
+ }
+
+ pendingFeatures.remove(feature);
+ inFlightFeatures.remove(feature);
+
+ QTimer::singleShot(0, parent, SLOT(iterateIntrospection()));
+}
+
+void ReadinessHelper::Private::iterateIntrospection()
+{
+ if (proxy && !proxy->isValid()) {
+ debug() << "ReadinessHelper: not iterating as the proxy is invalidated";
+ return;
+ }
+
+ // When there's a pending status change, we MUST NOT
+ // - finish PendingReadys (as they'd not be finished in the new status)
+ // - claim a status as being ready (because the new one isn't)
+ // and SHOULD NOT
+ // - fire new introspection jobs (as that would just delay the pending status change even more)
+ // and NEED NOT
+ // - flag features as missing (as the completed features will be cleared anyway when starting
+ // introspection for the new status)
+ //
+ // So we can safely skip the rest of this function here.
+ if (pendingStatusChange) {
+ debug() << "ReadinessHelper: not iterating as a status change is pending";
+ return;
+ }
+
+ // Flag the currently pending reverse dependencies of any previously discovered missing features
+ // as missing
+ foreach (const Feature &feature, pendingFeatures) {
+ if (!depsFor(feature).intersect(missingFeatures).isEmpty()) {
+ missingFeatures.insert(feature);
+ missingFeaturesErrors.insert(feature,
+ QPair<QString, QString>(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Feature depends on other features that are not available")));
+ }
+ }
+
+ const Features completedFeatures = satisfiedFeatures + missingFeatures;
+
+ // check if any pending operations for becomeReady should finish now
+ // based on their requested features having nothing more than what
+ // satisfiedFeatures + missingFeatures has
+ QString errorName;
+ QString errorMessage;
+ foreach (PendingReady *operation, pendingOperations) {
+ if ((operation->requestedFeatures() - completedFeatures).isEmpty()) {
+ if (parent->isReady(operation->requestedFeatures(), &errorName, &errorMessage)) {
+ operation->setFinished();
+ } else {
+ operation->setFinishedWithError(errorName, errorMessage);
+ }
+
+ // Remove the operation from tracking, so we don't double-finish it
+ //
+ // Qt foreach makes a copy of the container, which will be detached at this point, so
+ // this is perfectly safe
+ pendingOperations.removeOne(operation);
+ }
+ }
+
+ if ((requestedFeatures - completedFeatures).isEmpty()) {
+ // Otherwise, we'd emit statusReady with currentStatus although we are supposed to be
+ // introspecting the pendingStatus and only when that is complete, emit statusReady
+ Q_ASSERT(!pendingStatusChange);
+
+ // all requested features satisfied or missing
+ emit parent->statusReady(currentStatus);
+ return;
+ }
+
+ // update pendingFeatures with the difference of requested and
+ // satisfied + missing
+ pendingFeatures -= completedFeatures;
+
+ // find out which features don't have dependencies that are still pending
+ Features readyToIntrospect;
+ foreach (const Feature &feature, pendingFeatures) {
+ // missing doesn't have to be considered here anymore
+ if ((introspectables[feature].mPriv->dependsOnFeatures - satisfiedFeatures).isEmpty()) {
+ readyToIntrospect.insert(feature);
+ }
+ }
+
+ // now readyToIntrospect should contain all the features which have
+ // all their feature dependencies satisfied
+ foreach (const Feature &feature, readyToIntrospect) {
+ if (inFlightFeatures.contains(feature)) {
+ continue;
+ }
+
+ inFlightFeatures.insert(feature);
+
+ Introspectable introspectable = introspectables[feature];
+
+ if (!introspectable.mPriv->makesSenseForStatuses.contains(currentStatus)) {
+ // No-op satisfy features for which nothing has to be done in
+ // the current state
+ setIntrospectCompleted(feature, true);
+ return; // will be called with a single-shot soon again
+ }
+
+ foreach (const QString &interface, introspectable.mPriv->dependsOnInterfaces) {
+ if (!interfaces.contains(interface)) {
+ // If a feature is ready to introspect and depends on a interface
+ // that is not present the feature can't possibly be satisfied
+ debug() << "feature" << feature << "depends on interfaces" <<
+ introspectable.mPriv->dependsOnInterfaces << ", but interface" << interface <<
+ "is not present";
+ setIntrospectCompleted(feature, false,
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Feature depend on interfaces that are not available"));
+ return; // will be called with a single-shot soon again
+ }
+ }
+
+ // yes, with the dependency info, we can even parallelize
+ // introspection of several features at once, reducing total round trip
+ // time considerably with many independent features!
+ (*(introspectable.mPriv->introspectFunc))(introspectable.mPriv->introspectFuncData);
+ }
+}
+
+Features ReadinessHelper::Private::depsFor(const Feature &feature)
+{
+ Features deps;
+
+ foreach (Feature dep, introspectables[feature].mPriv->dependsOnFeatures) {
+ deps += dep;
+ deps += depsFor(dep);
+ }
+
+ return deps;
+}
+
+void ReadinessHelper::Private::abortOperations(const QString &errorName,
+ const QString &errorMessage)
+{
+ foreach (PendingReady *operation, pendingOperations) {
+ operation->setFinishedWithError(errorName, errorMessage);
+ }
+ pendingOperations.clear();
+}
+
+/**
+ * \class ReadinessHelper
+ * \ingroup utils
+ * \headerfile TelepathyQt4/readiness-helper.h <TelepathyQt4/ReadinessHelper>
+ *
+ * \brief The ReadinessHelper class is a helper class used by the introspection
+ * process.
+ */
+
+/**
+ * \class ReadinessHelper::Introspectable
+ * \ingroup utils
+ * \headerfile TelepathyQt4/readiness-helper.h <TelepathyQt4/ReadinessHelper>
+ *
+ * \brief The ReadinessHelper::Introspectable class represents a introspectable
+ * used by ReadinessHelper.
+ */
+
+ReadinessHelper::ReadinessHelper(RefCounted *object,
+ uint currentStatus,
+ const Introspectables &introspectables,
+ QObject *parent)
+ : QObject(parent),
+ mPriv(new Private(this, object, currentStatus, introspectables))
+{
+}
+
+ReadinessHelper::ReadinessHelper(DBusProxy *proxy,
+ uint currentStatus,
+ const Introspectables &introspectables,
+ QObject *parent)
+ : QObject(parent),
+ mPriv(new Private(this, proxy, currentStatus, introspectables))
+{
+}
+
+ReadinessHelper::~ReadinessHelper()
+{
+ delete mPriv;
+}
+
+void ReadinessHelper::addIntrospectables(const Introspectables &introspectables)
+{
+ // QMap::unite will create multiple items if the key is already in the map
+ // so let's make sure we don't duplicate keys
+ for (Introspectables::const_iterator i = introspectables.constBegin();
+ i != introspectables.constEnd(); ++i) {
+ Feature feature = i.key();
+ if (mPriv->introspectables.contains(feature)) {
+ warning() << "ReadinessHelper::addIntrospectables: trying to add an "
+ "introspectable for feature" << feature << "but introspectable "
+ "for this feature already exists";
+ } else {
+ Introspectable introspectable = i.value();
+ mPriv->introspectables.insert(feature, introspectable);
+ mPriv->supportedStatuses += introspectable.mPriv->makesSenseForStatuses;
+ mPriv->supportedFeatures += feature;
+ }
+ }
+
+ debug() << "ReadinessHelper: new supportedStatuses =" << mPriv->supportedStatuses;
+ debug() << "ReadinessHelper: new supportedFeatures =" << mPriv->supportedFeatures;
+}
+
+uint ReadinessHelper::currentStatus() const
+{
+ return mPriv->currentStatus;
+}
+
+void ReadinessHelper::setCurrentStatus(uint currentStatus)
+{
+ mPriv->setCurrentStatus(currentStatus);
+}
+
+/**
+ * Force the current internal status to \a currentStatus.
+ *
+ * Note that this method will not start a new introspection or restart the
+ * current one in case one is running.
+ *
+ * This is useful for example when the status is unknown initially but it will
+ * become known in the first introspection run and there is no need to re-run
+ * the introspection.
+ *
+ * \param currentStatus The status to set.
+ */
+void ReadinessHelper::forceCurrentStatus(uint currentStatus)
+{
+ mPriv->currentStatus = currentStatus;
+}
+
+QStringList ReadinessHelper::interfaces() const
+{
+ return mPriv->interfaces;
+}
+
+void ReadinessHelper::setInterfaces(const QStringList &interfaces)
+{
+ mPriv->interfaces = interfaces;
+}
+
+Features ReadinessHelper::requestedFeatures() const
+{
+ return mPriv->requestedFeatures;
+}
+
+Features ReadinessHelper::actualFeatures() const
+{
+ return mPriv->satisfiedFeatures;
+}
+
+Features ReadinessHelper::missingFeatures() const
+{
+ return mPriv->missingFeatures;
+}
+
+bool ReadinessHelper::isReady(const Feature &feature,
+ QString *errorName, QString *errorMessage) const
+{
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ if (errorName) {
+ *errorName = mPriv->proxy->invalidationReason();
+ }
+ if (errorMessage) {
+ *errorMessage = mPriv->proxy->invalidationMessage();
+ }
+ return false;
+ }
+
+ if (!mPriv->supportedFeatures.contains(feature)) {
+ if (errorName) {
+ *errorName = QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT);
+ }
+ if (errorMessage) {
+ *errorMessage = QLatin1String("Unsupported feature");
+ }
+ return false;
+ }
+
+ bool ret = true;
+
+ if (feature.isCritical()) {
+ if (!mPriv->satisfiedFeatures.contains(feature)) {
+ ret = false;
+ }
+ } else {
+ if (!mPriv->satisfiedFeatures.contains(feature) &&
+ !mPriv->missingFeatures.contains(feature)) {
+ ret = false;
+ }
+ }
+
+ if (!ret) {
+ QPair<QString, QString> error = mPriv->missingFeaturesErrors[feature];
+ if (errorName) {
+ *errorName = error.first;
+ }
+ if (errorMessage) {
+ *errorMessage = error.second;
+ }
+ }
+
+ return ret;
+}
+
+bool ReadinessHelper::isReady(const Features &features, QString *errorName, QString *errorMessage) const
+{
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ if (errorName) {
+ *errorName = mPriv->proxy->invalidationReason();
+ }
+ if (errorMessage) {
+ *errorMessage = mPriv->proxy->invalidationMessage();
+ }
+ return false;
+ }
+
+ Q_ASSERT(!features.isEmpty());
+
+ foreach (const Feature &feature, features) {
+ if (!isReady(feature, errorName, errorMessage)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+PendingReady *ReadinessHelper::becomeReady(const Features &requestedFeatures)
+{
+ Q_ASSERT(!requestedFeatures.isEmpty());
+
+ if (mPriv->proxy) {
+ connect(mPriv->proxy,
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ this,
+ SLOT(onProxyInvalidated(Tp::DBusProxy*,QString,QString)),
+ Qt::UniqueConnection);
+
+ if (!mPriv->proxy->isValid()) {
+ PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object),
+ requestedFeatures);
+ operation->setFinishedWithError(mPriv->proxy->invalidationReason(),
+ mPriv->proxy->invalidationMessage());
+ return operation;
+ }
+ }
+
+ Features supportedFeatures = mPriv->supportedFeatures;
+ if (supportedFeatures.intersect(requestedFeatures) != requestedFeatures) {
+ warning() << "ReadinessHelper::becomeReady called with invalid features: requestedFeatures =" <<
+ requestedFeatures << "- supportedFeatures =" << mPriv->supportedFeatures;
+ PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object),
+ requestedFeatures);
+ operation->setFinishedWithError(
+ QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Requested features contains unsupported feature"));
+ return operation;
+ }
+
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object),
+ requestedFeatures);
+ operation->setFinishedWithError(mPriv->proxy->invalidationReason(),
+ mPriv->proxy->invalidationMessage());
+ return operation;
+ }
+
+ PendingReady *operation;
+ foreach (operation, mPriv->pendingOperations) {
+ if (operation->requestedFeatures() == requestedFeatures) {
+ return operation;
+ }
+ }
+
+ // Insert the dependencies of the requested features too
+ Features requestedWithDeps = requestedFeatures;
+ foreach (const Feature &feature, requestedFeatures) {
+ requestedWithDeps.unite(mPriv->depsFor(feature));
+ }
+
+ mPriv->requestedFeatures += requestedWithDeps;
+ mPriv->pendingFeatures += requestedWithDeps; // will be updated in iterateIntrospection
+
+ operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object), requestedFeatures);
+ mPriv->pendingOperations.append(operation);
+ // Only we finish these PendingReadys, so we don't need destroyed or finished handling for them
+ // - we already know when that happens, as we caused it!
+
+ QTimer::singleShot(0, this, SLOT(iterateIntrospection()));
+
+ return operation;
+}
+
+void ReadinessHelper::setIntrospectCompleted(const Feature &feature, bool success,
+ const QString &errorName, const QString &errorMessage)
+{
+ if (mPriv->proxy && !mPriv->proxy->isValid()) {
+ // proxy became invalid, ignore here
+ return;
+ }
+ mPriv->setIntrospectCompleted(feature, success, errorName, errorMessage);
+}
+
+void ReadinessHelper::setIntrospectCompleted(const Feature &feature, bool success,
+ const QDBusError &error)
+{
+ setIntrospectCompleted(feature, success, error.name(), error.message());
+}
+
+void ReadinessHelper::iterateIntrospection()
+{
+ mPriv->iterateIntrospection();
+}
+
+void ReadinessHelper::onProxyInvalidated(DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ // clear satisfied and missing features as we have public methods to get them
+ mPriv->satisfiedFeatures.clear();
+ mPriv->missingFeatures.clear();
+
+ mPriv->abortOperations(errorName, errorMessage);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/readiness-helper.h b/qt4/TelepathyQt4/readiness-helper.h
new file mode 100644
index 000000000..003d51f7b
--- /dev/null
+++ b/qt4/TelepathyQt4/readiness-helper.h
@@ -0,0 +1,130 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_readiness_helper_h_HEADER_GUARD_
+#define _TelepathyQt4_readiness_helper_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Feature>
+
+#include <QMap>
+#include <QSet>
+#include <QSharedDataPointer>
+#include <QStringList>
+
+class QDBusError;
+
+namespace Tp
+{
+
+class DBusProxy;
+class PendingOperation;
+class PendingReady;
+class RefCounted;
+
+class TELEPATHY_QT4_EXPORT ReadinessHelper : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ReadinessHelper)
+
+public:
+ typedef void (*IntrospectFunc)(void *data);
+
+ struct Introspectable {
+ public:
+ Introspectable();
+ Introspectable(const QSet<uint> &makesSenseForStatuses,
+ const Features &dependsOnFeatures,
+ const QStringList &dependsOnInterfaces,
+ IntrospectFunc introspectFunc,
+ void *introspectFuncData,
+ bool critical = false);
+ Introspectable(const Introspectable &other);
+ ~Introspectable();
+
+ Introspectable &operator=(const Introspectable &other);
+
+ private:
+ friend class ReadinessHelper;
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+ typedef QMap<Feature, Introspectable> Introspectables;
+
+ ReadinessHelper(RefCounted *object,
+ uint currentStatus = 0,
+ const Introspectables &introspectables = Introspectables(),
+ QObject *parent = 0);
+ ReadinessHelper(DBusProxy *proxy,
+ uint currentStatus = 0,
+ const Introspectables &introspectables = Introspectables(),
+ QObject *parent = 0);
+ ~ReadinessHelper();
+
+ void addIntrospectables(const Introspectables &introspectables);
+
+ uint currentStatus() const;
+ void setCurrentStatus(uint currentStatus);
+ void forceCurrentStatus(uint currentStatus);
+
+ QStringList interfaces() const;
+ void setInterfaces(const QStringList &interfaces);
+
+ Features requestedFeatures() const;
+ Features actualFeatures() const;
+ Features missingFeatures() const;
+
+ bool isReady(const Feature &feature,
+ QString *errorName = 0, QString *errorMessage = 0) const;
+ bool isReady(const Features &features,
+ QString *errorName = 0, QString *errorMessage = 0) const;
+ PendingReady *becomeReady(const Features &requestedFeatures);
+
+ void setIntrospectCompleted(const Feature &feature, bool success,
+ const QString &errorName = QString(),
+ const QString &errorMessage = QString());
+ void setIntrospectCompleted(const Feature &feature, bool success,
+ const QDBusError &error);
+
+Q_SIGNALS:
+ void statusReady(uint status);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void iterateIntrospection();
+
+ TELEPATHY_QT4_NO_EXPORT void onProxyInvalidated(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/ready-object.cpp b/qt4/TelepathyQt4/ready-object.cpp
new file mode 100644
index 000000000..a4fa114ea
--- /dev/null
+++ b/qt4/TelepathyQt4/ready-object.cpp
@@ -0,0 +1,161 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ReadyObject>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReadinessHelper>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ReadyObject::Private
+{
+ Private(ReadyObject *parent, RefCounted *object, Feature featureCore);
+ Private(ReadyObject *parent, DBusProxy *proxy, Feature featureCore);
+ ~Private();
+
+ ReadyObject *parent;
+ const Features coreFeatures;
+ ReadinessHelper *readinessHelper;
+};
+
+ReadyObject::Private::Private(ReadyObject *parent, RefCounted *object,
+ Feature featureCore)
+ : parent(parent),
+ coreFeatures(Features() << featureCore),
+ readinessHelper(new ReadinessHelper(object))
+{
+}
+
+ReadyObject::Private::Private(ReadyObject *parent, DBusProxy *proxy,
+ Feature featureCore)
+ : parent(parent),
+ coreFeatures(Features() << featureCore),
+ readinessHelper(new ReadinessHelper(proxy))
+{
+}
+
+ReadyObject::Private::~Private()
+{
+ delete readinessHelper;
+}
+
+/**
+ * \class ReadyObject
+ * \ingroup clientreadiness
+ * \headerfile TelepathyQt4/ready-object.h> <TelepathyQt4/ReadyObject>
+ */
+
+/**
+ * Construct a new ReadyObject object.
+ *
+ * \param object The RefCounted the object refers to.
+ * \param featureCore The core feature of the object.
+ */
+ReadyObject::ReadyObject(RefCounted *object, const Feature &featureCore)
+ : mPriv(new Private(this, object, featureCore))
+{
+}
+
+/**
+ * Construct a new ReadyObject object.
+ *
+ * \param proxy The DBusProxy the object refers to.
+ * \param featureCore The core feature of the object.
+ */
+ReadyObject::ReadyObject(DBusProxy *proxy, const Feature &featureCore)
+ : mPriv(new Private(this, proxy, featureCore))
+{
+}
+
+/**
+ * Class destructor.
+ */
+ReadyObject::~ReadyObject()
+{
+ delete mPriv;
+}
+
+/**
+ * Return whether this object has finished its initial setup.
+ *
+ * This is mostly useful as a sanity check, in code that shouldn't be run
+ * until the object is ready. To wait for the object to be ready, call
+ * becomeReady() and connect to the finished signal on the result.
+ *
+ * \param features The features which should be tested
+ * \return \c true if the object has finished its initial setup for basic
+ * functionality plus the given features
+ */
+bool ReadyObject::isReady(const Features &features) const
+{
+ if (features.isEmpty()) {
+ return mPriv->readinessHelper->isReady(mPriv->coreFeatures);
+ }
+ return mPriv->readinessHelper->isReady(features);
+}
+
+/**
+ * Return a pending operation which will succeed when this object finishes
+ * its initial setup, or will fail if a fatal error occurs during this
+ * initial setup.
+ *
+ * If an empty set is used FeatureCore will be considered as the requested
+ * feature.
+ *
+ * \param requestedFeatures The features which should be enabled
+ * \return A PendingReady object which will emit finished
+ * when this object has finished or failed initial setup for basic
+ * functionality plus the given features
+ */
+PendingReady *ReadyObject::becomeReady(const Features &requestedFeatures)
+{
+ if (requestedFeatures.isEmpty()) {
+ return mPriv->readinessHelper->becomeReady(mPriv->coreFeatures);
+ }
+ return mPriv->readinessHelper->becomeReady(requestedFeatures);
+}
+
+Features ReadyObject::requestedFeatures() const
+{
+ return mPriv->readinessHelper->requestedFeatures();
+}
+
+Features ReadyObject::actualFeatures() const
+{
+ return mPriv->readinessHelper->actualFeatures();
+}
+
+Features ReadyObject::missingFeatures() const
+{
+ return mPriv->readinessHelper->missingFeatures();
+}
+
+ReadinessHelper *ReadyObject::readinessHelper() const
+{
+ return mPriv->readinessHelper;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/ready-object.h b/qt4/TelepathyQt4/ready-object.h
new file mode 100644
index 000000000..aa43a1ce2
--- /dev/null
+++ b/qt4/TelepathyQt4/ready-object.h
@@ -0,0 +1,69 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_ready_object_h_HEADER_GUARD_
+#define _TelepathyQt4_ready_object_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Feature>
+
+#include <QObject>
+
+namespace Tp
+{
+
+class DBusProxy;
+class PendingReady;
+class ReadinessHelper;
+class RefCounted;
+
+class TELEPATHY_QT4_EXPORT ReadyObject
+{
+ Q_DISABLE_COPY(ReadyObject)
+
+public:
+ ReadyObject(RefCounted *object, const Feature &featureCore);
+ ReadyObject(DBusProxy *proxy, const Feature &featureCore);
+ virtual ~ReadyObject();
+
+ virtual bool isReady(const Features &features = Features()) const;
+ virtual PendingReady *becomeReady(const Features &requestedFeatures = Features());
+
+ virtual Features requestedFeatures() const;
+ virtual Features actualFeatures() const;
+ virtual Features missingFeatures() const;
+
+protected:
+ ReadinessHelper *readinessHelper() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/referenced-handles.cpp b/qt4/TelepathyQt4/referenced-handles.cpp
new file mode 100644
index 000000000..5e7a0ba5b
--- /dev/null
+++ b/qt4/TelepathyQt4/referenced-handles.cpp
@@ -0,0 +1,333 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/ReferencedHandles>
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+
+#include <QPointer>
+#include <QSharedData>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT ReferencedHandles::Private : public QSharedData
+{
+ QWeakPointer<Connection> connection;
+ HandleType handleType;
+ UIntList handles;
+
+ Private()
+ {
+ handleType = HandleTypeNone;
+ }
+
+ Private(const ConnectionPtr &conn, HandleType handleType,
+ const UIntList &handles)
+ : connection(conn.data()), handleType(handleType), handles(handles)
+ {
+ Q_ASSERT(!conn.isNull());
+ Q_ASSERT(handleType != 0);
+
+ foreach (uint handle, handles) {
+ conn->refHandle(handleType, handle);
+ }
+ }
+
+ Private(const Private &a)
+ : QSharedData(a),
+ connection(a.connection),
+ handleType(a.handleType),
+ handles(a.handles)
+ {
+ if (!handles.isEmpty()) {
+ ConnectionPtr conn(connection);
+ if (!conn) {
+ debug() << " Destroyed after Connection, so the Connection "
+ "has already released the handles";
+ return;
+ }
+
+ for (const_iterator i = handles.constBegin(); i != handles.constEnd(); ++i) {
+ conn->refHandle(handleType, *i);
+ }
+ }
+ }
+
+ ~Private()
+ {
+ if (!handles.isEmpty()) {
+ ConnectionPtr conn(connection);
+ if (!conn) {
+ debug() << " Destroyed after Connection, so the Connection "
+ "has already released the handles";
+ return;
+ }
+
+ for (const_iterator i = handles.constBegin(); i != handles.constEnd(); ++i) {
+ conn->unrefHandle(handleType, *i);
+ }
+ }
+ }
+
+private:
+ void operator=(const Private&);
+};
+
+/**
+ * \class ReferencedHandles
+ * \ingroup clientconn
+ * \headerfile TelepathyQt4/referenced-handles.h <TelepathyQt4/ReferencedHandles>
+ *
+ * \brief Helper container for safe management of handle lifetimes. Every handle
+ * in a ReferencedHandles container is guaranteed to be valid (and stay valid,
+ * as long it's in at least one ReferencedHandles container).
+ *
+ * The class offers a QList-style API. However, from the mutable operations,
+ * only the operations for which the validity guarantees can be preserved are
+ * provided. This means no functions which can add an arbitrary handle to the
+ * container are included - the only way to add handles to the container is to
+ * reference them using Connection::referenceHandles() and appending the
+ * resulting ReferenceHandles instance.
+ *
+ * ReferencedHandles is a implicitly shared class.
+ */
+
+ReferencedHandles::ReferencedHandles()
+ : mPriv(new Private)
+{
+}
+
+ReferencedHandles::ReferencedHandles(const ReferencedHandles &other)
+ : mPriv(other.mPriv)
+{
+}
+
+ReferencedHandles::~ReferencedHandles()
+{
+}
+
+ConnectionPtr ReferencedHandles::connection() const
+{
+ return ConnectionPtr(mPriv->connection);
+}
+
+HandleType ReferencedHandles::handleType() const
+{
+ return mPriv->handleType;
+}
+
+uint ReferencedHandles::at(int i) const
+{
+ return mPriv->handles[i];
+}
+
+uint ReferencedHandles::value(int i, uint defaultValue) const
+{
+ return mPriv->handles.value(i, defaultValue);
+}
+
+ReferencedHandles::const_iterator ReferencedHandles::begin() const
+{
+ return mPriv->handles.begin();
+}
+
+ReferencedHandles::const_iterator ReferencedHandles::end() const
+{
+ return mPriv->handles.end();
+}
+
+bool ReferencedHandles::contains(uint handle) const
+{
+ return mPriv->handles.contains(handle);
+}
+
+int ReferencedHandles::count(uint handle) const
+{
+ return mPriv->handles.count(handle);
+}
+
+int ReferencedHandles::indexOf(uint handle, int from) const
+{
+ return mPriv->handles.indexOf(handle, from);
+}
+
+bool ReferencedHandles::isEmpty() const
+{
+ return mPriv->handles.isEmpty();
+}
+
+int ReferencedHandles::lastIndexOf(uint handle, int from) const
+{
+ return mPriv->handles.lastIndexOf(handle, from);
+}
+
+ReferencedHandles ReferencedHandles::mid(int pos, int length) const
+{
+ return ReferencedHandles(connection(), handleType(),
+ mPriv->handles.mid(pos, length));
+}
+
+int ReferencedHandles::size() const
+{
+ return mPriv->handles.size();
+}
+
+void ReferencedHandles::clear()
+{
+ if (!mPriv->handles.empty()) {
+ ConnectionPtr conn(mPriv->connection);
+ if (conn) {
+ foreach (uint handle, mPriv->handles) {
+ conn->unrefHandle(handleType(), handle);
+ }
+ } else {
+ warning() << "Connection already destroyed in "
+ "ReferencedHandles::clear() so can't unref!";
+ }
+ }
+
+ mPriv->handles.clear();
+}
+
+void ReferencedHandles::move(int from, int to)
+{
+ mPriv->handles.move(from, to);
+}
+
+int ReferencedHandles::removeAll(uint handle)
+{
+ int count = mPriv->handles.removeAll(handle);
+
+ if (count > 0) {
+ ConnectionPtr conn(mPriv->connection);
+ if (conn) {
+ for (int i = 0; i < count; ++i) {
+ conn->unrefHandle(handleType(), handle);
+ }
+ } else {
+ warning() << "Connection already destroyed in "
+ "ReferencedHandles::removeAll() with handle ==" <<
+ handle << "so can't unref!";
+ }
+ }
+
+ return count;
+}
+
+void ReferencedHandles::removeAt(int i)
+{
+ ConnectionPtr conn(mPriv->connection);
+ if (conn) {
+ conn->unrefHandle(handleType(), at(i));
+ } else {
+ warning() << "Connection already destroyed in "
+ "ReferencedHandles::removeAt() with i ==" <<
+ i << "so can't unref!";
+ }
+
+ mPriv->handles.removeAt(i);
+}
+
+bool ReferencedHandles::removeOne(uint handle)
+{
+ bool wasThere = mPriv->handles.removeOne(handle);
+
+ if (wasThere) {
+ ConnectionPtr conn(mPriv->connection);
+ if (conn) {
+ conn->unrefHandle(handleType(), handle);
+ } else {
+ warning() << "Connection already destroyed in "
+ "ReferencedHandles::removeOne() with handle ==" <<
+ handle << "so can't unref!";
+ }
+ }
+
+ return wasThere;
+}
+
+void ReferencedHandles::swap(int i, int j)
+{
+ mPriv->handles.swap(i, j);
+}
+
+uint ReferencedHandles::takeAt(int i)
+{
+ ConnectionPtr conn(mPriv->connection);
+ if (conn) {
+ conn->unrefHandle(handleType(), at(i));
+ } else {
+ warning() << "Connection already destroyed in "
+ "ReferencedHandles::takeAt() with i ==" <<
+ i << "so can't unref!";
+ }
+
+ return mPriv->handles.takeAt(i);
+}
+
+ReferencedHandles ReferencedHandles::operator+(const ReferencedHandles &another) const
+{
+ if (connection() != another.connection() ||
+ handleType() != another.handleType()) {
+ warning() << "Tried to concatenate ReferencedHandles instances "
+ "with different connection and/or handle type";
+ return *this;
+ }
+
+ return ReferencedHandles(connection(), handleType(),
+ mPriv->handles + another.mPriv->handles);
+}
+
+ReferencedHandles &ReferencedHandles::operator=(
+ const ReferencedHandles &another)
+{
+ mPriv = another.mPriv;
+ return *this;
+}
+
+bool ReferencedHandles::operator==(const ReferencedHandles &another) const
+{
+ return connection() == another.connection() &&
+ handleType() == another.handleType() &&
+ mPriv->handles == another.mPriv->handles;
+}
+
+bool ReferencedHandles::operator==(const UIntList &list) const
+{
+ return mPriv->handles == list;
+}
+
+UIntList ReferencedHandles::toList() const
+{
+ return mPriv->handles;
+}
+
+ReferencedHandles::ReferencedHandles(const ConnectionPtr &connection,
+ HandleType handleType, const UIntList &handles)
+ : mPriv(new Private(connection, handleType, handles))
+{
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/referenced-handles.h b/qt4/TelepathyQt4/referenced-handles.h
new file mode 100644
index 000000000..94237ea18
--- /dev/null
+++ b/qt4/TelepathyQt4/referenced-handles.h
@@ -0,0 +1,264 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_referenced_handles_h_HEADER_GUARD_
+#define _TelepathyQt4_referenced_handles_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#ifndef QT_NO_STL
+# include <list>
+#endif
+
+#include <QList>
+#include <QSet>
+#include <QSharedDataPointer>
+#include <QVector>
+
+namespace Tp
+{
+
+class Connection;
+
+class TELEPATHY_QT4_EXPORT ReferencedHandles
+{
+public:
+ typedef UIntList::const_iterator const_iterator;
+ typedef UIntList::ConstIterator ConstIterator;
+ typedef UIntList::const_pointer const_pointer;
+ typedef UIntList::const_reference const_reference;
+ typedef UIntList::difference_type difference_type;
+ typedef UIntList::pointer pointer;
+ typedef UIntList::reference reference;
+ typedef UIntList::size_type size_type;
+ typedef UIntList::value_type value_type;
+
+ ReferencedHandles();
+ ReferencedHandles(const ReferencedHandles &other);
+ ~ReferencedHandles();
+
+ ConnectionPtr connection() const;
+ HandleType handleType() const;
+
+ uint at(int i) const;
+
+ inline uint back() const
+ {
+ return last();
+ }
+
+ inline uint first() const
+ {
+ return at(0);
+ }
+
+ inline uint front() const
+ {
+ return first();
+ }
+
+ inline uint last() const
+ {
+ return at(size() - 1);
+ }
+
+ uint value(int i, uint defaultValue = 0) const;
+
+ const_iterator begin() const;
+
+ inline const_iterator constBegin() const
+ {
+ return begin();
+ }
+
+ inline const_iterator constEnd() const
+ {
+ return end();
+ }
+
+ const_iterator end() const;
+
+ bool contains(uint handle) const;
+
+ int count(uint handle) const;
+
+ inline int count() const
+ {
+ return size();
+ }
+
+ inline bool empty() const
+ {
+ return isEmpty();
+ }
+
+ inline bool endsWith(uint handle) const
+ {
+ return !isEmpty() && last() == handle;
+ }
+
+ int indexOf(uint handle, int from = 0) const;
+
+ bool isEmpty() const;
+
+ int lastIndexOf(uint handle, int from = -1) const;
+
+ inline int length() const
+ {
+ return count();
+ }
+
+ ReferencedHandles mid(int pos, int length = -1) const;
+
+ int size() const;
+
+ inline bool startsWith(uint handle) const
+ {
+ return !isEmpty() && first() == handle;
+ }
+
+ inline void append(const ReferencedHandles& another)
+ {
+ *this = *this + another;
+ }
+
+ void clear();
+ void move(int from, int to);
+
+ inline void pop_back()
+ {
+ return removeLast();
+ }
+
+ inline void pop_front()
+ {
+ return removeFirst();
+ }
+
+ int removeAll(uint handle);
+
+ void removeAt(int i);
+
+ inline void removeFirst()
+ {
+ return removeAt(0);
+ }
+
+ inline void removeLast()
+ {
+ return removeAt(size() - 1);
+ }
+
+ bool removeOne(uint handle);
+
+ void swap(int i, int j);
+
+ uint takeAt(int i);
+
+ inline uint takeFirst()
+ {
+ return takeAt(0);
+ }
+
+ inline uint takeLast()
+ {
+ return takeAt(size() - 1);
+ }
+
+ bool operator!=(const ReferencedHandles& another) const
+ {
+ return !(*this == another);
+ }
+
+ bool operator!=(const UIntList& another) const
+ {
+ return !(*this == another);
+ }
+
+ ReferencedHandles operator+(const ReferencedHandles& another) const;
+
+ inline ReferencedHandles& operator+=(const ReferencedHandles& another)
+ {
+ return *this = (*this + another);
+ }
+
+ ReferencedHandles& operator<<(const ReferencedHandles& another)
+ {
+ return *this += another;
+ }
+
+ ReferencedHandles& operator=(const ReferencedHandles& another);
+
+ bool operator==(const ReferencedHandles& another) const;
+ bool operator==(const UIntList& list) const;
+
+ inline uint operator[](int i) const
+ {
+ return at(i);
+ }
+
+ UIntList toList() const;
+
+ inline QSet<uint> toSet() const
+ {
+ return toList().toSet();
+ }
+
+#ifndef QT_NO_STL
+ inline std::list<uint> toStdList() const
+ {
+ return toList().toStdList();
+ }
+#endif
+
+ inline QVector<uint> toVector() const
+ {
+ return toList().toVector();
+ }
+
+private:
+ // For access to the "prime" constructor
+ friend class ContactManager;
+ friend class PendingContactAttributes;
+ friend class PendingContacts;
+ friend class PendingHandles;
+
+ TELEPATHY_QT4_NO_EXPORT ReferencedHandles(const ConnectionPtr &connection,
+ HandleType handleType, const UIntList& handles);
+
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+typedef QListIterator<uint> ReferencedHandlesIterator;
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::ReferencedHandles);
+
+#endif
diff --git a/qt4/TelepathyQt4/request-temporary-handler-internal.cpp b/qt4/TelepathyQt4/request-temporary-handler-internal.cpp
new file mode 100644
index 000000000..bd7ff6e7d
--- /dev/null
+++ b/qt4/TelepathyQt4/request-temporary-handler-internal.cpp
@@ -0,0 +1,135 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "TelepathyQt4/request-temporary-handler-internal.h"
+
+#include "TelepathyQt4/_gen/request-temporary-handler-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ChannelClassSpecList>
+
+namespace Tp
+{
+
+SharedPtr<RequestTemporaryHandler> RequestTemporaryHandler::create(const AccountPtr &account)
+{
+ return SharedPtr<RequestTemporaryHandler>(new RequestTemporaryHandler(account));
+}
+
+RequestTemporaryHandler::RequestTemporaryHandler(const AccountPtr &account)
+ : AbstractClient(),
+ QObject(),
+ AbstractClientHandler(ChannelClassSpecList(), AbstractClientHandler::Capabilities(), false),
+ mAccount(account),
+ mQueueChannelReceived(true),
+ dbusHandlerInvoked(false)
+{
+}
+
+RequestTemporaryHandler::~RequestTemporaryHandler()
+{
+}
+
+void RequestTemporaryHandler::handleChannels(
+ const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo)
+{
+ Q_ASSERT(dbusHandlerInvoked);
+
+ QString errorMessage;
+
+ ChannelPtr oldChannel = channel();
+ if (channels.size() != 1 || requestsSatisfied.size() != 1) {
+ errorMessage = QLatin1String("Only one channel and one channel request should be given "
+ "to HandleChannels");
+ } else if (account != mAccount) {
+ errorMessage = QLatin1String("Account received is not the same as the account which made "
+ "the request");
+ } else if (oldChannel && oldChannel != channels.first()) {
+ errorMessage = QLatin1String("Received a channel that is not the same as the first "
+ "one received");
+ }
+
+ if (!errorMessage.isEmpty()) {
+ warning() << "Handling channel failed with" << TP_QT4_ERROR_SERVICE_CONFUSED << ":" <<
+ errorMessage;
+
+ // Only emit error if we didn't receive any channel yet.
+ if (!oldChannel) {
+ emit error(TP_QT4_ERROR_SERVICE_CONFUSED, errorMessage);
+ }
+ context->setFinishedWithError(TP_QT4_ERROR_SERVICE_CONFUSED, errorMessage);
+ return;
+ }
+
+ ChannelRequestPtr channelRequest = requestsSatisfied.first();
+
+ if (!oldChannel) {
+ mChannel = QWeakPointer<Channel>(channels.first().data());
+ emit channelReceived(channel(), userActionTime, channelRequest->hints());
+ } else {
+ if (mQueueChannelReceived) {
+ mChannelReceivedQueue.enqueue(qMakePair(userActionTime, channelRequest->hints()));
+ } else {
+ emit channelReceived(oldChannel, userActionTime, channelRequest->hints());
+ }
+ }
+
+ context->setFinished();
+}
+
+void RequestTemporaryHandler::setQueueChannelReceived(bool queue)
+{
+ mQueueChannelReceived = queue;
+ if (!queue) {
+ processChannelReceivedQueue();
+ }
+}
+
+void RequestTemporaryHandler::setDBusHandlerInvoked()
+{
+ dbusHandlerInvoked = true;
+}
+
+void RequestTemporaryHandler::setDBusHandlerErrored(const QString &errorName, const QString &errorMessage)
+{
+ Q_ASSERT(dbusHandlerInvoked);
+ if (!channel()) {
+ emit error(errorName, errorMessage);
+ }
+}
+
+void RequestTemporaryHandler::processChannelReceivedQueue()
+{
+ while (!mChannelReceivedQueue.isEmpty()) {
+ QPair<QDateTime, ChannelRequestHints> info = mChannelReceivedQueue.dequeue();
+ emit channelReceived(channel(), info.first, info.second);
+ }
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/request-temporary-handler-internal.h b/qt4/TelepathyQt4/request-temporary-handler-internal.h
new file mode 100644
index 000000000..840767148
--- /dev/null
+++ b/qt4/TelepathyQt4/request-temporary-handler-internal.h
@@ -0,0 +1,91 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_request_temporary_handler_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_request_temporary_handler_internal_h_HEADER_GUARD_
+
+#include <QWeakPointer>
+
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Channel>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT RequestTemporaryHandler : public QObject, public AbstractClientHandler
+{
+ Q_OBJECT
+
+public:
+ static SharedPtr<RequestTemporaryHandler> create(const AccountPtr &account);
+
+ ~RequestTemporaryHandler();
+
+ AccountPtr account() const { return mAccount; }
+ ChannelPtr channel() const { return ChannelPtr(mChannel); }
+
+ /**
+ * Handlers we request ourselves never go through the approvers but this
+ * handler shouldn't get any channels we didn't request - hence let's make
+ * this always false to leave slightly less room for the CD to get confused and
+ * give some channel we didn't request to us, without even asking an approver
+ * first. Though if the CD isn't confused it shouldn't really matter - our filter
+ * is empty anyway.
+ */
+ bool bypassApproval() const { return false; }
+
+ void handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo);
+
+ void setQueueChannelReceived(bool queue);
+
+ void setDBusHandlerInvoked();
+ void setDBusHandlerErrored(const QString &errorName, const QString &errorMessage);
+
+ bool isDBusHandlerInvoked() const { return dbusHandlerInvoked; }
+
+Q_SIGNALS:
+ void error(const QString &errorName, const QString &errorMessage);
+ void channelReceived(const Tp::ChannelPtr &channel, const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &requestHints);
+
+private:
+ RequestTemporaryHandler(const AccountPtr &account);
+
+ void processChannelReceivedQueue();
+
+ AccountPtr mAccount;
+ QWeakPointer<Channel> mChannel;
+ bool mQueueChannelReceived;
+ QQueue<QPair<QDateTime, ChannelRequestHints> > mChannelReceivedQueue;
+ bool dbusHandlerInvoked;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/requestable-channel-class-spec.cpp b/qt4/TelepathyQt4/requestable-channel-class-spec.cpp
new file mode 100644
index 000000000..ecf7a2542
--- /dev/null
+++ b/qt4/TelepathyQt4/requestable-channel-class-spec.cpp
@@ -0,0 +1,494 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/RequestableChannelClassSpec>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT RequestableChannelClassSpec::Private : public QSharedData
+{
+ Private(const RequestableChannelClass &rcc)
+ : rcc(rcc) {}
+
+ RequestableChannelClass rcc;
+};
+
+/**
+ * \class RequestableChannelClassSpec
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/requestable-channel-class-spec.h <TelepathyQt4/RequestableChannelClassSpec>
+ *
+ * \brief The RequestableChannelClassSpec class represents a Telepathy
+ * requestable channel class.
+ */
+
+RequestableChannelClassSpec::RequestableChannelClassSpec(const RequestableChannelClass &rcc)
+ : mPriv(new Private(rcc))
+{
+}
+
+RequestableChannelClassSpec::RequestableChannelClassSpec()
+{
+}
+
+RequestableChannelClassSpec::RequestableChannelClassSpec(const RequestableChannelClassSpec &other)
+ : mPriv(other.mPriv)
+{
+}
+
+RequestableChannelClassSpec::~RequestableChannelClassSpec()
+{
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::textChat()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::textChatroom()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeRoom);
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaCall()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaAudioCall()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialAudio"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaVideoCall()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialVideo"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaVideoCallWithAudio()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialAudio"));
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialVideo"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::fileTransfer()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChat()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChatWithInvitees()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChatroom()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeRoom);
+
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChatroomWithInvitees()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeRoom);
+
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::conferenceStreamedMediaCall()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::contactSearch()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::contactSearchWithSpecificServer()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Server"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::contactSearchWithLimit()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Limit"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::contactSearchWithSpecificServerAndLimit()
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Server"));
+ rcc.allowedProperties.append(
+ TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Limit"));
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ return spec;
+}
+
+RequestableChannelClassSpec RequestableChannelClassSpec::streamTube(const QString &service)
+{
+ static RequestableChannelClassSpec spec;
+
+ if (!spec.isValid()) {
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ spec = RequestableChannelClassSpec(rcc);
+ }
+
+ if (service.isEmpty()) {
+ return spec;
+ }
+
+ RequestableChannelClass rcc = spec.bareClass();
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE + QLatin1String(".Service"),
+ service);
+ return RequestableChannelClassSpec(rcc);
+}
+
+RequestableChannelClassSpec &RequestableChannelClassSpec::operator=(const RequestableChannelClassSpec &other)
+{
+ this->mPriv = other.mPriv;
+ return *this;
+}
+
+bool RequestableChannelClassSpec::operator==(const RequestableChannelClassSpec &other) const
+{
+ if (!isValid() || !other.isValid()) {
+ if (!isValid() && !other.isValid()) {
+ return true;
+ }
+ return false;
+ }
+
+ return mPriv->rcc == other.mPriv->rcc;
+}
+
+bool RequestableChannelClassSpec::supports(const RequestableChannelClassSpec &other) const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ if (mPriv->rcc.fixedProperties == other.fixedProperties()) {
+ foreach (const QString &prop, other.allowedProperties()) {
+ if (!mPriv->rcc.allowedProperties.contains(prop)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+QString RequestableChannelClassSpec::channelType() const
+{
+ if (!isValid()) {
+ return QString();
+ }
+ return qdbus_cast<QString>(mPriv->rcc.fixedProperties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")));
+}
+
+bool RequestableChannelClassSpec::hasTargetHandleType() const
+{
+ if (!isValid()) {
+ return false;
+ }
+ return mPriv->rcc.fixedProperties.contains(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"));
+}
+
+HandleType RequestableChannelClassSpec::targetHandleType() const
+{
+ if (!hasTargetHandleType()) {
+ return (HandleType) -1;
+ }
+ return (HandleType) qdbus_cast<uint>(mPriv->rcc.fixedProperties.value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")));
+}
+
+bool RequestableChannelClassSpec::hasFixedProperty(const QString &name) const
+{
+ if (!isValid()) {
+ return false;
+ }
+ return mPriv->rcc.fixedProperties.contains(name);
+}
+
+QVariant RequestableChannelClassSpec::fixedProperty(const QString &name) const
+{
+ if (!isValid()) {
+ return QVariant();
+ }
+ return mPriv->rcc.fixedProperties.value(name);
+}
+
+QVariantMap RequestableChannelClassSpec::fixedProperties() const
+{
+ if (!isValid()) {
+ return QVariantMap();
+ }
+ return mPriv->rcc.fixedProperties;
+}
+
+bool RequestableChannelClassSpec::allowsProperty(const QString &name) const
+{
+ if (!isValid()) {
+ return false;
+ }
+ return mPriv->rcc.allowedProperties.contains(name);
+}
+
+QStringList RequestableChannelClassSpec::allowedProperties() const
+{
+ if (!isValid()) {
+ return QStringList();
+ }
+ return mPriv->rcc.allowedProperties;
+}
+
+RequestableChannelClass RequestableChannelClassSpec::bareClass() const
+{
+ return isValid() ? mPriv->rcc : RequestableChannelClass();
+}
+
+/**
+ * \class RequestableChannelClassSpecList
+ * \ingroup wrappers
+ * \headerfile TelepathyQt4/requestable-channel-class-spec.h <TelepathyQt4/RequestableChannelClassSpecList>
+ *
+ * \brief The RequestableChannelClassSpecList class represents a list of
+ * RequestableChannelClassSpec.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/requestable-channel-class-spec.h b/qt4/TelepathyQt4/requestable-channel-class-spec.h
new file mode 100644
index 000000000..0e274eee5
--- /dev/null
+++ b/qt4/TelepathyQt4/requestable-channel-class-spec.h
@@ -0,0 +1,134 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_requestable_channel_class_spec_h_HEADER_GUARD_
+#define _TelepathyQt4_requestable_channel_class_spec_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT RequestableChannelClassSpec
+{
+public:
+ RequestableChannelClassSpec();
+ RequestableChannelClassSpec(const RequestableChannelClass &rcc);
+ RequestableChannelClassSpec(const RequestableChannelClassSpec &other);
+ ~RequestableChannelClassSpec();
+
+ static RequestableChannelClassSpec textChat();
+ static RequestableChannelClassSpec textChatroom();
+
+ static RequestableChannelClassSpec streamedMediaCall();
+ static RequestableChannelClassSpec streamedMediaAudioCall();
+ static RequestableChannelClassSpec streamedMediaVideoCall();
+ static RequestableChannelClassSpec streamedMediaVideoCallWithAudio();
+
+ static RequestableChannelClassSpec fileTransfer();
+
+ static RequestableChannelClassSpec conferenceTextChat();
+ static RequestableChannelClassSpec conferenceTextChatWithInvitees();
+ static RequestableChannelClassSpec conferenceTextChatroom();
+ static RequestableChannelClassSpec conferenceTextChatroomWithInvitees();
+ static RequestableChannelClassSpec conferenceStreamedMediaCall();
+ static RequestableChannelClassSpec conferenceStreamedMediaCallWithInvitees();
+
+ static RequestableChannelClassSpec contactSearch();
+ static RequestableChannelClassSpec contactSearchWithSpecificServer();
+ static RequestableChannelClassSpec contactSearchWithLimit();
+ static RequestableChannelClassSpec contactSearchWithSpecificServerAndLimit();
+
+ static RequestableChannelClassSpec streamTube(const QString &service = QString());
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ RequestableChannelClassSpec &operator=(const RequestableChannelClassSpec &other);
+ bool operator==(const RequestableChannelClassSpec &other) const;
+
+ bool supports(const RequestableChannelClassSpec &spec) const;
+
+ QString channelType() const;
+
+ bool hasTargetHandleType() const;
+ HandleType targetHandleType() const;
+
+ bool hasFixedProperty(const QString &name) const;
+ QVariant fixedProperty(const QString &name) const;
+ QVariantMap fixedProperties() const;
+
+ bool allowsProperty(const QString &name) const;
+ QStringList allowedProperties() const;
+
+ RequestableChannelClass bareClass() const;
+
+private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT RequestableChannelClassSpecList :
+ public QList<RequestableChannelClassSpec>
+{
+public:
+ RequestableChannelClassSpecList() { }
+ RequestableChannelClassSpecList(const RequestableChannelClass &rcc)
+ {
+ append(RequestableChannelClassSpec(rcc));
+ }
+ RequestableChannelClassSpecList(const RequestableChannelClassList &rccs)
+ {
+ Q_FOREACH (const RequestableChannelClass &rcc, rccs) {
+ append(RequestableChannelClassSpec(rcc));
+ }
+ }
+ RequestableChannelClassSpecList(const RequestableChannelClassSpec &rccSpec)
+ {
+ append(rccSpec);
+ }
+ RequestableChannelClassSpecList(const QList<RequestableChannelClassSpec> &other)
+ : QList<RequestableChannelClassSpec>(other)
+ {
+ }
+
+ RequestableChannelClassList bareClasses() const
+ {
+ RequestableChannelClassList list;
+ Q_FOREACH (const RequestableChannelClassSpec &rccSpec, *this) {
+ list.append(rccSpec.bareClass());
+ }
+ return list;
+ }
+};
+
+} // Tp
+
+Q_DECLARE_METATYPE(Tp::RequestableChannelClassSpec);
+Q_DECLARE_METATYPE(Tp::RequestableChannelClassSpecList);
+
+#endif
diff --git a/qt4/TelepathyQt4/room-list-channel.cpp b/qt4/TelepathyQt4/room-list-channel.cpp
new file mode 100644
index 000000000..8f81a97a7
--- /dev/null
+++ b/qt4/TelepathyQt4/room-list-channel.cpp
@@ -0,0 +1,106 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/RoomListChannel>
+
+#include "TelepathyQt4/_gen/room-list-channel.moc.hpp"
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT RoomListChannel::Private
+{
+ inline Private();
+ inline ~Private();
+};
+
+RoomListChannel::Private::Private()
+{
+}
+
+RoomListChannel::Private::~Private()
+{
+}
+
+/**
+ * \class RoomListChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/room-list-channel.h <TelepathyQt4/RoomListChannel>
+ *
+ * \brief The RoomListChannel class represents a Telepathy Channel of type RoomList.
+ *
+ * Note that this subclass of Channel will eventually provide a high-level API for the
+ * RoomList interface. Until then, it's just a Channel.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Create a new RoomListChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A RoomListChannelPtr object pointing to the newly created
+ * RoomListChannel object.
+ */
+RoomListChannelPtr RoomListChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return RoomListChannelPtr(new RoomListChannel(connection, objectPath,
+ immutableProperties));
+}
+
+/**
+ * Construct a new RoomListChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * should depend on Channel::FeatureCore.
+ */
+RoomListChannel::RoomListChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : Channel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private())
+{
+}
+
+/**
+ * Class destructor.
+ */
+RoomListChannel::~RoomListChannel()
+{
+ delete mPriv;
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/room-list-channel.h b/qt4/TelepathyQt4/room-list-channel.h
new file mode 100644
index 000000000..2cd4eea5a
--- /dev/null
+++ b/qt4/TelepathyQt4/room-list-channel.h
@@ -0,0 +1,59 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_room_list_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_room_list_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT RoomListChannel : public Channel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(RoomListChannel)
+
+public:
+ static RoomListChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~RoomListChannel();
+
+protected:
+ RoomListChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = Channel::FeatureCore);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/shared-ptr.dox b/qt4/TelepathyQt4/shared-ptr.dox
new file mode 100644
index 000000000..e2712eb02
--- /dev/null
+++ b/qt4/TelepathyQt4/shared-ptr.dox
@@ -0,0 +1,111 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \page shared_ptr Shared Pointer Usage
+ *
+ * \section shared_ptr_overview Overview
+ *
+ * The Qt parent/child object model does not fit well with Telepathy-Qt4 object
+ * model, where in some places we either don't know the object parent or we
+ * can't use a parent, as the object can stay alive without it.
+ *
+ * To avoid memory leaks, caused by objects that got instantiated and don't have
+ * any parent, we decided to make some of our objects reference counted, by
+ * making them inherit SharedData.
+ *
+ * Making the object reference counted, does not guarantee that it will get
+ * deleted when nobody is referencing it.
+ *
+ * When instantiating new classes that inherits SharedData the reference count
+ * is 0, this is referred to as the floating state. Again this may lead to
+ * memory leaks, caused by objects in the floating state that never get deleted.
+ *
+ * So the solution is to put the object in a SharedPtr as soon as possible,
+ * letting the SharedPtr manage the object lifetime.
+ *
+ * The pattern used is that classes inherit SharedData and are used
+ * together with SharedPtr. When the reference count hits 0, the object
+ * is deleted.
+ *
+ * In order to assure that the object is put in a SharedPtr as soon as possible,
+ * our objects inheriting SharedData will have the constructor either private
+ * or protected, in case we want to support custom classes, and will have a
+ * public static create method that will return a SharedPtr pointing to the
+ * object instance.
+ *
+ * Note that when developing custom classes, this pattern should be followed,
+ * to avoid objects in floating state, avoiding memory leaks.
+ */
+
+/**
+ * \class Tp::RefCounted
+ * \ingroup utils
+ * \headerfile TelepathyQt4/shared-ptr.h <TelepathyQt4/RefCounted>
+ *
+ * \brief The RefCounted class is a base class for shared objects used by
+ * SharedPtr.
+ *
+ * See \ref shared_ptr
+ */
+
+/**
+ * \class Tp::SharedPtr
+ * \ingroup utils
+ * \headerfile TelepathyQt4/shared-ptr.h <TelepathyQt4/SharedPtr>
+ *
+ * \brief The SharedPtr class is a pointer to an explicitly shared object.
+ *
+ * See \ref shared_ptr
+ */
+
+/**
+ * \fn static SharedPtr<T> Tp::SharedPtr<T>::dynamicCast(const SharedPtr<X> &)
+ *
+ * Casts the pointer given by src to a pointer pointing to an object of type T. The cast will
+ * succeed if the C++ runtime type identification mechanism considers the type T to be the actual
+ * runtime type of the object pointed to by src or one of its (possibly indirect) parent classes.
+ * Otherwise, a null pointer is returned.
+ *
+ * Note that this also allows down-casting a baseclass pointer to a subclass pointer.
+ *
+ * This cast method should not be used for QObject-derived classes, as Qt provides a more portable
+ * and efficient type identification mechanism, which is used by qObjectCast().
+ *
+ * This cast method requires the C++ dynamic runtime type identification facility to be enabled
+ * (which might be disabled by eg. the -fno-rtti flag of the GNU G++ compiler).
+ */
+
+/**
+ * \fn static SharedPtr<T> Tp::SharedPtr<T>::qObjectCast(const SharedPtr<X> &)
+ *
+ * Casts the pointer given by src to a pointer pointing to an object of type T. The cast will
+ * succeed if the Qt runtime type identification mechanism considers the type T to be the actual
+ * runtime type of the object pointed to by src or one of its (possibly indirect) parent classes.
+ * Otherwise, a null pointer is returned.
+ *
+ * Note that this also allows down-casting a baseclass pointer to a subclass pointer.
+ *
+ * This cast method MUST not be used for classes not derived from QObject. However, dynamicCast()
+ * provides the same semantics for all classes, provided the C++ runtime type identification
+ * facility is enabled. This method, on the other hand, doesn't require the standard C++ facility
+ * and is probably also faster for the types it can be used with.
+ */
diff --git a/qt4/TelepathyQt4/shared-ptr.h b/qt4/TelepathyQt4/shared-ptr.h
new file mode 100644
index 000000000..11c76c131
--- /dev/null
+++ b/qt4/TelepathyQt4/shared-ptr.h
@@ -0,0 +1,152 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_shared_ptr_h_HEADER_GUARD_
+#define _TelepathyQt4_shared_ptr_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QHash>
+#include <QWeakPointer>
+
+namespace Tp
+{
+
+class RefCounted;
+template <class T> class SharedPtr;
+
+class TELEPATHY_QT4_EXPORT RefCounted
+{
+ Q_DISABLE_COPY(RefCounted)
+
+public:
+ inline RefCounted() : strongref(0) { }
+ inline virtual ~RefCounted() { }
+
+ inline void ref() const { strongref.ref(); }
+ inline bool deref() const { return strongref.deref(); }
+
+ mutable QAtomicInt strongref;
+};
+
+template <class T>
+class SharedPtr
+{
+ typedef bool (SharedPtr<T>::*UnspecifiedBoolType)() const;
+
+public:
+ inline SharedPtr() : d(0) { }
+ explicit inline SharedPtr(T *d) : d(d) { if (d) { d->ref(); } }
+ template <typename Subclass>
+ inline SharedPtr(const SharedPtr<Subclass> &o) : d(o.data()) { if (d) { d->ref(); } }
+ inline SharedPtr(const SharedPtr<T> &o) : d(o.d) { if (d) { d->ref(); } }
+ explicit inline SharedPtr(const QWeakPointer<T> &o)
+ {
+ if (o.data() && o.data()->strongref > 0) {
+ d = static_cast<T*>(o.data());
+ d->ref();
+ } else {
+ d = 0;
+ }
+ }
+ inline ~SharedPtr()
+ {
+ if (d && !d->deref()) {
+ T *saved = d;
+ d = 0;
+ delete saved;
+ }
+ }
+
+ inline void reset()
+ {
+ SharedPtr<T>().swap(*this);
+ }
+
+ inline T *data() const { return d; }
+ inline const T *constData() const { return d; }
+ inline T *operator->() { return d; }
+ inline T *operator->() const { return d; }
+
+ inline bool isNull() const { return !d; }
+ inline bool operator!() const { return isNull(); }
+ operator UnspecifiedBoolType() const { return !isNull() ? &SharedPtr<T>::operator! : 0; }
+
+ inline bool operator==(const SharedPtr<T> &o) const { return d == o.d; }
+ inline bool operator!=(const SharedPtr<T> &o) const { return d != o.d; }
+ inline bool operator==(const T *ptr) const { return d == ptr; }
+ inline bool operator!=(const T *ptr) const { return d != ptr; }
+
+ inline SharedPtr<T> &operator=(const SharedPtr<T> &o)
+ {
+ SharedPtr<T>(o).swap(*this);
+ return *this;
+ }
+
+ inline void swap(SharedPtr<T> &o)
+ {
+ T *tmp = d;
+ d = o.d;
+ o.d = tmp;
+ }
+
+ template <class X>
+ static inline SharedPtr<T> staticCast(const SharedPtr<X> &src)
+ {
+ return SharedPtr<T>(static_cast<T*>(src.data()));
+ }
+
+ template <class X>
+ static inline SharedPtr<T> dynamicCast(const SharedPtr<X> &src)
+ {
+ return SharedPtr<T>(dynamic_cast<T*>(src.data()));
+ }
+
+ template <class X>
+ static inline SharedPtr<T> constCast(const SharedPtr<X> &src)
+ {
+ return SharedPtr<T>(const_cast<T*>(src.data()));
+ }
+
+ template <class X>
+ static inline SharedPtr<T> qObjectCast(const SharedPtr<X> &src)
+ {
+ return SharedPtr<T>(qobject_cast<T*>(src.data()));
+ }
+
+private:
+ T *d;
+};
+
+template<typename T>
+inline uint qHash(const SharedPtr<T> &ptr)
+{
+ return QT_PREPEND_NAMESPACE(qHash<T>(ptr.data()));
+}
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/simple-call-observer.cpp b/qt4/TelepathyQt4/simple-call-observer.cpp
new file mode 100644
index 000000000..67b1c9f37
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-call-observer.cpp
@@ -0,0 +1,298 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/SimpleCallObserver>
+
+#include "TelepathyQt4/_gen/simple-call-observer.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingSuccess>
+#include <TelepathyQt4/SimpleObserver>
+#include <TelepathyQt4/StreamedMediaChannel>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT SimpleCallObserver::Private
+{
+ Private(SimpleCallObserver *parent, const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization,
+ CallDirection direction);
+
+ SimpleCallObserver *parent;
+ AccountPtr account;
+ QString contactIdentifier;
+ CallDirection direction;
+ SimpleObserverPtr observer;
+};
+
+SimpleCallObserver::Private::Private(SimpleCallObserver *parent,
+ const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization,
+ CallDirection direction)
+ : parent(parent),
+ account(account),
+ contactIdentifier(contactIdentifier),
+ direction(direction)
+{
+ debug() << "Creating a new SimpleCallObserver";
+ ChannelClassSpec channelFilter = ChannelClassSpec::streamedMediaCall();
+ if (direction == CallDirectionIncoming) {
+ channelFilter.setRequested(false);
+ } else if (direction == CallDirectionOutgoing) {
+ channelFilter.setRequested(true);
+ }
+
+ observer = SimpleObserver::create(account, ChannelClassSpecList() << channelFilter,
+ contactIdentifier, requiresNormalization, QList<ChannelClassFeatures>());
+
+ parent->connect(observer.data(),
+ SIGNAL(newChannels(QList<Tp::ChannelPtr>)),
+ SLOT(onNewChannels(QList<Tp::ChannelPtr>)));
+ parent->connect(observer.data(),
+ SIGNAL(channelInvalidated(Tp::ChannelPtr,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::ChannelPtr,QString,QString)));
+}
+
+/**
+ * \class SimpleCallObserver
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-call-observer.h <TelepathyQt4/SimpleCallObserver>
+ *
+ * \brief The SimpleCallObserver class provides an easy way to track calls
+ * in an account and can be optionally filtered by a contact and/or
+ * call direction.
+ */
+
+/**
+ * Create a new SimpleCallObserver object.
+ *
+ * Events will be signalled for all calls in \a account that respect \a direction.
+ *
+ * \param account The account used to listen to events.
+ * \param direction The direction of the calls used to filter events.
+ * \return An SimpleCallObserverPtr object pointing to the newly created
+ * SimpleCallObserver object.
+ */
+SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account,
+ CallDirection direction)
+{
+ return create(account, QString(), false, direction);
+}
+
+/**
+ * Create a new SimpleCallObserver object.
+ *
+ * Events will be signalled for all calls in \a account established with \a contact and
+ * that respect \a direction.
+ *
+ * \param account The account used to listen to events.
+ * \param contact The contact used to filter events.
+ * \param direction The direction of the calls used to filter events.
+ * \return An SimpleCallObserverPtr object pointing to the newly created
+ * SimpleCallObserver object.
+ */
+SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account,
+ const ContactPtr &contact,
+ CallDirection direction)
+{
+ if (contact) {
+ return create(account, contact->id(), false, direction);
+ }
+ return create(account, QString(), false, direction);
+}
+
+/**
+ * Create a new SimpleCallObserver object.
+ *
+ * Events will be signalled for all calls in \a account established with a contact identified by \a
+ * contactIdentifier and that respect \a direction.
+ *
+ * \param account The account used to listen to events.
+ * \param contactIdentifier The identifier of the contact used to filter events.
+ * \param direction The direction of the calls used to filter events.
+ * \return An SimpleCallObserverPtr object pointing to the newly created
+ * SimpleCallObserver object.
+ */
+SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account,
+ const QString &contactIdentifier,
+ CallDirection direction)
+{
+ return create(account, contactIdentifier, true, direction);
+}
+
+SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization,
+ CallDirection direction)
+{
+ return SimpleCallObserverPtr(
+ new SimpleCallObserver(account, contactIdentifier,
+ requiresNormalization, direction));
+}
+
+/**
+ * Construct a new SimpleCallObserver object.
+ *
+ * \param account The account used to listen to events.
+ * \param contactIdentifier The identifier of the contact used to filter events.
+ * \param requiresNormalization Whether \a contactIdentifier needs to be
+ * normalized.
+ * \param direction The direction of the calls used to filter events.
+ * \return An SimpleCallObserverPtr object pointing to the newly created
+ * SimpleCallObserver object.
+ */
+SimpleCallObserver::SimpleCallObserver(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization,
+ CallDirection direction)
+ : mPriv(new Private(this, account, contactIdentifier, requiresNormalization, direction))
+{
+}
+
+/**
+ * Class destructor.
+ */
+SimpleCallObserver::~SimpleCallObserver()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account used to listen to events.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr SimpleCallObserver::account() const
+{
+ return mPriv->account;
+}
+
+/**
+ * Return the identifier of the contact used to filter events, or an empty string if none was
+ * provided at construction.
+ *
+ * \return The identifier of the contact.
+ */
+QString SimpleCallObserver::contactIdentifier() const
+{
+ return mPriv->contactIdentifier;
+}
+
+/**
+ * Return the direction of the calls used to filter events.
+ *
+ * \return The direction of the calls as SimpleCallObserver::CallDirection.
+ */
+SimpleCallObserver::CallDirection SimpleCallObserver::direction() const
+{
+ return mPriv->direction;
+}
+
+/**
+ * Return the list of streamed media calls currently being observed.
+ *
+ * \return A list of pointers to StreamedMediaChannel objects.
+ */
+QList<StreamedMediaChannelPtr> SimpleCallObserver::streamedMediaCalls() const
+{
+ QList<StreamedMediaChannelPtr> ret;
+ foreach (const ChannelPtr &channel, mPriv->observer->channels()) {
+ StreamedMediaChannelPtr smChannel = StreamedMediaChannelPtr::qObjectCast(channel);
+ if (smChannel) {
+ ret << smChannel;
+ }
+ }
+ return ret;
+}
+
+void SimpleCallObserver::onNewChannels(const QList<ChannelPtr> &channels)
+{
+ foreach (const ChannelPtr &channel, channels) {
+ StreamedMediaChannelPtr smChannel = StreamedMediaChannelPtr::qObjectCast(channel);
+ if (!smChannel) {
+ if (channel->channelType() != TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) {
+ warning() << "Channel received to observe is not of type StreamedMedia, service "
+ "confused. Ignoring channel";
+ } else {
+ warning() << "Channel received to observe is not a subclass of "
+ "StreamedMediaChannel. ChannelFactory set on this observer's account must "
+ "construct StreamedMediaChannel subclasses for channels of type StreamedMedia. "
+ "Ignoring channel";
+ }
+ continue;
+ }
+
+ emit streamedMediaCallStarted(StreamedMediaChannelPtr::qObjectCast(channel));
+ }
+}
+
+void SimpleCallObserver::onChannelInvalidated(const ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage)
+{
+ StreamedMediaChannelPtr smChannel = StreamedMediaChannelPtr::qObjectCast(channel);
+ if (!smChannel) {
+ if (channel->channelType() != TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) {
+ warning() << "Channel received by this observer is not of type StreamedMedia, service "
+ "confused. Ignoring channel";
+ } else {
+ warning() << "Channel received by this observer is not a subclass of "
+ "StreamedMediaChannel. ChannelFactory set on this observer's account must "
+ "construct StreamedMediaChannel subclasses for channels of type StreamedMedia. "
+ "Ignoring channel";
+ }
+ return;
+ }
+ emit streamedMediaCallEnded(smChannel, errorName, errorMessage);
+}
+
+/**
+ * \fn void SimpleCallObserver::streamedMediaCallStarted(const Tp::StreamedMediaChannelPtr &channel)
+ *
+ * Emitted whenever a streamed media call that matches this observer's criteria is
+ * started.
+ *
+ * \param channel The channel representing the streamed media call that started.
+ */
+
+/**
+ * \fn void SimpleCallObserver::streamedMediaCallEnded(const Tp::StreamedMediaChannelPtr &channel,
+ * const QString &errorName, const QString &errorMessage)
+ *
+ * Emitted whenever a streamed media call that matches this observer's criteria has
+ * ended.
+ *
+ * \param channel The channel representing the streamed media call that ended.
+ * \param errorName A D-Bus error name (a string in a subset
+ * of ASCII, prefixed with a reversed domain name).
+ * \param errorMessage A debugging message associated with the error.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/simple-call-observer.h b/qt4/TelepathyQt4/simple-call-observer.h
new file mode 100644
index 000000000..fb52161f9
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-call-observer.h
@@ -0,0 +1,95 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_simple_call_observer_h_HEADER_GUARD_
+#define _TelepathyQt4_simple_call_observer_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#include <QObject>
+
+namespace Tp
+{
+
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT SimpleCallObserver : public QObject,
+ public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(SimpleCallObserver)
+ Q_FLAGS(CallDirection CallDirections)
+
+public:
+ enum CallDirection {
+ CallDirectionIncoming = 0x01,
+ CallDirectionOutgoing = 0x02,
+ CallDirectionBoth = CallDirectionIncoming | CallDirectionOutgoing
+ };
+ Q_DECLARE_FLAGS(CallDirections, CallDirection)
+
+ static SimpleCallObserverPtr create(const AccountPtr &account,
+ CallDirection direction = CallDirectionBoth);
+ static SimpleCallObserverPtr create(const AccountPtr &account,
+ const ContactPtr &contact,
+ CallDirection direction = CallDirectionBoth);
+ static SimpleCallObserverPtr create(const AccountPtr &account,
+ const QString &contactIdentifier,
+ CallDirection direction = CallDirectionBoth);
+
+ virtual ~SimpleCallObserver();
+
+ AccountPtr account() const;
+ QString contactIdentifier() const;
+ CallDirection direction() const;
+
+ QList<StreamedMediaChannelPtr> streamedMediaCalls() const;
+
+Q_SIGNALS:
+ void streamedMediaCallStarted(const Tp::StreamedMediaChannelPtr &channel);
+ void streamedMediaCallEnded(const Tp::StreamedMediaChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onNewChannels(const QList<Tp::ChannelPtr> &channels);
+ TELEPATHY_QT4_NO_EXPORT void onChannelInvalidated(const Tp::ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+
+private:
+ TELEPATHY_QT4_NO_EXPORT static SimpleCallObserverPtr create(
+ const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization,
+ CallDirection direction);
+
+ TELEPATHY_QT4_NO_EXPORT SimpleCallObserver(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization,
+ CallDirection direction);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/simple-observer-internal.h b/qt4/TelepathyQt4/simple-observer-internal.h
new file mode 100644
index 000000000..47a0e17f8
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-observer-internal.h
@@ -0,0 +1,261 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelClassFeatures>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT SimpleObserver::Private
+{
+ Private(SimpleObserver *parent,
+ const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ bool requiresNormalization,
+ const QList<ChannelClassFeatures> &extraChannelFeatures);
+
+ bool filterChannel(const AccountPtr &channelAccount, const ChannelPtr &channel);
+ void insertChannels(const AccountPtr &channelsAccount, const QList<ChannelPtr> &channels);
+ void removeChannel(const AccountPtr &channelAccount, const ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+
+ void processChannelsQueue();
+ void processNewChannelsQueue();
+ void processChannelsInvalidationQueue();
+
+ class FakeAccountFactory;
+ class Observer;
+ class ChannelWrapper;
+ struct NewChannelsInfo;
+ struct ChannelInvalidationInfo;
+
+ SimpleObserver *parent;
+ AccountPtr account;
+ ChannelClassSpecList channelFilter;
+ QString contactIdentifier;
+ QString normalizedContactIdentifier;
+ QList<ChannelClassFeatures> extraChannelFeatures;
+ ClientRegistrarPtr cr;
+ SharedPtr<Observer> observer;
+ QSet<ChannelPtr> channels;
+ QQueue<void (SimpleObserver::Private::*)()> channelsQueue;
+ QQueue<ChannelInvalidationInfo> channelsInvalidationQueue;
+ QQueue<NewChannelsInfo> newChannelsQueue;
+ static QHash<QPair<QString, QSet<ChannelClassSpec> >, QWeakPointer<Observer> > observers;
+ static uint numObservers;
+};
+
+class TELEPATHY_QT4_NO_EXPORT SimpleObserver::Private::FakeAccountFactory :
+ public AccountFactory
+{
+ Q_OBJECT
+
+public:
+ static SharedPtr<FakeAccountFactory> create(const QDBusConnection &bus)
+ {
+ return SharedPtr<FakeAccountFactory>(new FakeAccountFactory(bus));
+ }
+
+ ~FakeAccountFactory() { }
+
+private:
+ friend class Observer;
+
+ FakeAccountFactory(const QDBusConnection &bus)
+ : AccountFactory(bus, Features())
+ {
+ }
+
+ AccountPtr construct(const QString &busName, const QString &objectPath,
+ const ConnectionFactoryConstPtr &connFactory,
+ const ChannelFactoryConstPtr &chanFactory,
+ const ContactFactoryConstPtr &contactFactory) const
+ {
+ if (mAccounts.contains(objectPath)) {
+ return mAccounts.value(objectPath);
+ }
+ return AccountFactory::construct(busName, objectPath, connFactory,
+ chanFactory, contactFactory);
+ }
+
+ QHash<QString, AccountPtr> accounts() const { return mAccounts; }
+ void registerAccount(const AccountPtr &account)
+ {
+ mAccounts.insert(account->objectPath(), account);
+ }
+
+ QHash<QString, AccountPtr> mAccounts;
+};
+
+class TELEPATHY_QT4_NO_EXPORT SimpleObserver::Private::Observer : public QObject,
+ public AbstractClientObserver
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Observer)
+
+public:
+ struct ContextInfo
+ {
+ ContextInfo() {}
+ ContextInfo(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const QList<ChannelPtr> &channels)
+ : context(context),
+ account(account),
+ channels(channels)
+ {
+ }
+
+ MethodInvocationContextPtr<> context;
+ AccountPtr account;
+ QList<ChannelPtr> channels;
+ };
+
+ Observer(const QWeakPointer<ClientRegistrar> &cr,
+ const SharedPtr<FakeAccountFactory> &fakeAccountFactory,
+ const ChannelClassSpecList &channelFilter,
+ const QString &observerName);
+ ~Observer();
+
+ QWeakPointer<ClientRegistrar> clientRegistrar() const { return mCr; }
+ SharedPtr<FakeAccountFactory> fakeAccountFactory() const { return mFakeAccountFactory; }
+
+ QString observerName() const { return mObserverName; }
+
+ QSet<ChannelClassFeatures> extraChannelFeatures() const { return mExtraChannelFeatures; }
+ void registerExtraChannelFeatures(const QList<ChannelClassFeatures> &features)
+ {
+ mExtraChannelFeatures.unite(features.toSet());
+ }
+
+ QSet<AccountPtr> accounts() const { return mAccounts; }
+ void registerAccount(const AccountPtr &account)
+ {
+ mAccounts.insert(account);
+ mFakeAccountFactory->registerAccount(account);
+ }
+
+ QHash<ChannelPtr, ChannelWrapper*> channels() const { return mChannels; }
+
+ void observeChannels(
+ const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const ChannelDispatchOperationPtr &dispatchOperation,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const ObserverInfo &observerInfo);
+
+Q_SIGNALS:
+ void newChannels(const Tp::AccountPtr &channelsAccount, const QList<Tp::ChannelPtr> &channels);
+ void channelInvalidated(const Tp::AccountPtr &channelAccount, const Tp::ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+
+private Q_SLOTS:
+ void onChannelInvalidated(const Tp::AccountPtr &channelAccount, const Tp::ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+ void onChannelsReady(Tp::PendingOperation *op);
+
+private:
+ Features featuresFor(const ChannelClassSpec &channelClass) const;
+
+ QWeakPointer<ClientRegistrar> mCr;
+ SharedPtr<FakeAccountFactory> mFakeAccountFactory;
+ QString mObserverName;
+ QSet<ChannelClassFeatures> mExtraChannelFeatures;
+ QSet<AccountPtr> mAccounts;
+ QHash<ChannelPtr, ChannelWrapper*> mChannels;
+ QHash<ChannelPtr, ChannelWrapper*> mIncompleteChannels;
+ QHash<PendingOperation*, ContextInfo*> mObserveChannelsInfo;
+};
+
+class TELEPATHY_QT4_NO_EXPORT SimpleObserver::Private::ChannelWrapper :
+ public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(ChannelWrapper)
+
+public:
+ ChannelWrapper(const AccountPtr &channelAccount, const ChannelPtr &channel,
+ const Features &extraChannelFeatures, QObject *parent);
+ ~ChannelWrapper() { }
+
+ AccountPtr channelAccount() const { return mChannelAccount; }
+ ChannelPtr channel() const { return mChannel; }
+ Features extraChannelFeatures() const { return mExtraChannelFeatures; }
+
+ PendingOperation *becomeReady();
+
+Q_SIGNALS:
+ void channelInvalidated(const Tp::AccountPtr &channelAccount, const Tp::ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+
+private Q_SLOTS:
+ void onChannelInvalidated(Tp::DBusProxy *proxy, const QString &errorName,
+ const QString &errorMessage);
+
+private:
+ AccountPtr mChannelAccount;
+ ChannelPtr mChannel;
+ Features mExtraChannelFeatures;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT SimpleObserver::Private::NewChannelsInfo
+{
+ NewChannelsInfo();
+ NewChannelsInfo(const AccountPtr &channelsAccount, const QList<ChannelPtr> &channels)
+ : channelsAccount(channelsAccount),
+ channels(channels)
+ {
+ }
+
+ AccountPtr channelsAccount;
+ QList<ChannelPtr> channels;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT SimpleObserver::Private::ChannelInvalidationInfo
+{
+ ChannelInvalidationInfo();
+ ChannelInvalidationInfo(const AccountPtr &channelAccount, const ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage)
+ : channelAccount(channelAccount),
+ channel(channel),
+ errorName(errorName),
+ errorMessage(errorMessage)
+ {
+ }
+
+ AccountPtr channelAccount;
+ ChannelPtr channel;
+ QString errorName;
+ QString errorMessage;
+};
+
+} // Tp
diff --git a/qt4/TelepathyQt4/simple-observer.cpp b/qt4/TelepathyQt4/simple-observer.cpp
new file mode 100644
index 000000000..40dddbc72
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-observer.cpp
@@ -0,0 +1,643 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/SimpleObserver>
+#include "TelepathyQt4/simple-observer-internal.h"
+
+#include "TelepathyQt4/_gen/simple-observer.moc.hpp"
+#include "TelepathyQt4/_gen/simple-observer-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingSuccess>
+
+namespace Tp
+{
+
+QHash<QPair<QString, QSet<ChannelClassSpec> >, QWeakPointer<SimpleObserver::Private::Observer> > SimpleObserver::Private::observers;
+uint SimpleObserver::Private::numObservers = 0;
+
+SimpleObserver::Private::Private(SimpleObserver *parent,
+ const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ bool requiresNormalization,
+ const QList<ChannelClassFeatures> &extraChannelFeatures)
+ : parent(parent),
+ account(account),
+ channelFilter(channelFilter),
+ contactIdentifier(contactIdentifier),
+ extraChannelFeatures(extraChannelFeatures)
+{
+ QSet<ChannelClassSpec> normalizedChannelFilter = channelFilter.toSet();
+ QPair<QString, QSet<ChannelClassSpec> > observerUniqueId(
+ account->dbusConnection().baseService(), normalizedChannelFilter);
+ observer = SharedPtr<Observer>(observers.value(observerUniqueId));
+ if (!observer) {
+ SharedPtr<FakeAccountFactory> fakeAccountFactory = FakeAccountFactory::create(
+ account->dbusConnection());
+
+ cr = ClientRegistrar::create(
+ AccountFactoryPtr::qObjectCast(fakeAccountFactory),
+ account->connectionFactory(),
+ account->channelFactory(),
+ account->contactFactory());
+
+ QString observerName = QString(QLatin1String("TpQt4SO_%1_%2"))
+ .arg(account->dbusConnection().baseService()
+ .replace(QLatin1String(":"), QLatin1String("_"))
+ .replace(QLatin1String("."), QLatin1String("_")))
+ .arg(numObservers++);
+ observer = SharedPtr<Observer>(new Observer(cr.data(), fakeAccountFactory,
+ normalizedChannelFilter.toList(), observerName));
+ if (!cr->registerClient(observer, observerName, false)) {
+ warning() << "Unable to register observer" << observerName;
+ observer.reset();
+ cr.reset();
+ return;
+ }
+
+ debug() << "Observer" << observerName << "registered";
+ observers.insert(observerUniqueId, observer.data());
+ } else {
+ debug() << "Observer" << observer->observerName() <<
+ "already registered and matches filter, using it";
+ cr = ClientRegistrarPtr(observer->clientRegistrar());
+ }
+
+ observer->registerExtraChannelFeatures(extraChannelFeatures);
+ observer->registerAccount(account);
+
+ if (contactIdentifier.isEmpty() || !requiresNormalization) {
+ normalizedContactIdentifier = contactIdentifier;
+ } else {
+ parent->connect(account.data(),
+ SIGNAL(connectionChanged(Tp::ConnectionPtr)),
+ SLOT(onAccountConnectionChanged(Tp::ConnectionPtr)));
+ }
+
+ parent->connect(observer.data(),
+ SIGNAL(newChannels(Tp::AccountPtr,QList<Tp::ChannelPtr>)),
+ SLOT(onNewChannels(Tp::AccountPtr,QList<Tp::ChannelPtr>)));
+ parent->connect(observer.data(),
+ SIGNAL(channelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString)));
+}
+
+bool SimpleObserver::Private::filterChannel(const AccountPtr &channelAccount,
+ const ChannelPtr &channel)
+{
+ if (channelAccount != account) {
+ return false;
+ }
+
+ if (contactIdentifier.isEmpty()) {
+ return true;
+ }
+
+ QString targetId = channel->immutableProperties().value(
+ TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetID")).toString();
+ if (targetId != normalizedContactIdentifier) {
+ // we didn't filter per contact, let's filter here
+ return false;
+ }
+ return true;
+}
+
+void SimpleObserver::Private::insertChannels(const AccountPtr &channelsAccount,
+ const QList<ChannelPtr> &newChannels)
+{
+ QSet<ChannelPtr> match;
+ foreach (const ChannelPtr &channel, newChannels) {
+ if (!channels.contains(channel) && filterChannel(channelsAccount, channel)) {
+ match.insert(channel);
+ }
+ }
+
+ if (match.isEmpty()) {
+ return;
+ }
+
+ channels.unite(match);
+ emit parent->newChannels(match.toList());
+}
+
+void SimpleObserver::Private::removeChannel(const AccountPtr &channelAccount,
+ const ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage)
+{
+ if (!channels.contains(channel) || !filterChannel(channelAccount, channel)) {
+ return;
+ }
+
+ channels.remove(channel);
+ emit parent->channelInvalidated(channel, errorName, errorMessage);
+}
+
+void SimpleObserver::Private::processChannelsQueue()
+{
+ if (channelsQueue.isEmpty()) {
+ return;
+ }
+
+ while (!channelsQueue.isEmpty()) {
+ (this->*(channelsQueue.dequeue()))();
+ }
+}
+
+void SimpleObserver::Private::processNewChannelsQueue()
+{
+ NewChannelsInfo info = newChannelsQueue.dequeue();
+ insertChannels(info.channelsAccount, info.channels);
+}
+
+void SimpleObserver::Private::processChannelsInvalidationQueue()
+{
+ ChannelInvalidationInfo info = channelsInvalidationQueue.dequeue();
+ removeChannel(info.channelAccount, info.channel, info.errorName, info.errorMessage);
+}
+
+SimpleObserver::Private::Observer::Observer(const QWeakPointer<ClientRegistrar> &cr,
+ const SharedPtr<FakeAccountFactory> &fakeAccountFactory,
+ const ChannelClassSpecList &channelFilter,
+ const QString &observerName)
+ : AbstractClient(),
+ QObject(),
+ AbstractClientObserver(channelFilter, true),
+ mCr(cr),
+ mFakeAccountFactory(fakeAccountFactory),
+ mObserverName(observerName)
+{
+}
+
+SimpleObserver::Private::Observer::~Observer()
+{
+ // no need to delete channel wrappers here as they have 'this' as parent
+
+ // no need to delete context infos here as this observer will never be deleted before all
+ // PendingComposites finish (hold reference to this), which will properly delete them
+
+ // no need to unregister this observer here as the client registrar destructor will
+ // unregister it
+}
+
+void SimpleObserver::Private::Observer::observeChannels(
+ const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const ChannelDispatchOperationPtr &dispatchOperation,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const ObserverInfo &observerInfo)
+{
+ if (!mAccounts.contains(account)) {
+ context->setFinished();
+ return;
+ }
+
+ QList<PendingOperation*> readyOps;
+ QList<ChannelPtr> newChannels;
+
+ foreach (const ChannelPtr &channel, channels) {
+ if (mIncompleteChannels.contains(channel) ||
+ mChannels.contains(channel)) {
+ // we are already observing this channel
+ continue;
+ }
+
+ // this shouldn't happen, but in any case
+ if (!channel->isValid()) {
+ warning() << "Channel received to observe is invalid. "
+ "Ignoring channel";
+ continue;
+ }
+
+ SimpleObserver::Private::ChannelWrapper *wrapper =
+ new SimpleObserver::Private::ChannelWrapper(account, channel,
+ featuresFor(ChannelClassSpec(channel->immutableProperties())), this);
+ mIncompleteChannels.insert(channel, wrapper);
+ connect(wrapper,
+ SIGNAL(channelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString)));
+
+ newChannels.append(channel);
+ readyOps.append(wrapper->becomeReady());
+ }
+
+ if (readyOps.isEmpty()) {
+ context->setFinished();
+ return;
+ }
+
+ PendingComposite *pc = new PendingComposite(readyOps,
+ false /* failOnFirstError */, SharedPtr<Observer>(this));
+ mObserveChannelsInfo.insert(pc, new ContextInfo(context, account, newChannels));
+ connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onChannelsReady(Tp::PendingOperation*)));
+}
+
+void SimpleObserver::Private::Observer::onChannelInvalidated(const AccountPtr &channelAccount,
+ const ChannelPtr &channel, const QString &errorName, const QString &errorMessage)
+{
+ if (mIncompleteChannels.contains(channel)) {
+ // we are still handling the channel, wait for onChannelsReady that will properly remove
+ // it from mChannels
+ return;
+ }
+ emit channelInvalidated(channelAccount, channel, errorName, errorMessage);
+ Q_ASSERT(mChannels.contains(channel));
+ delete mChannels.take(channel);
+}
+
+void SimpleObserver::Private::Observer::onChannelsReady(PendingOperation *op)
+{
+ ContextInfo *info = mObserveChannelsInfo.value(op);
+
+ foreach (const ChannelPtr &channel, info->channels) {
+ Q_ASSERT(mIncompleteChannels.contains(channel));
+ ChannelWrapper *wrapper = mIncompleteChannels.take(channel);
+ mChannels.insert(channel, wrapper);
+ }
+ emit newChannels(info->account, info->channels);
+
+ foreach (const ChannelPtr &channel, info->channels) {
+ ChannelWrapper *wrapper = mChannels.value(channel);
+ if (!channel->isValid()) {
+ mChannels.remove(channel);
+ emit channelInvalidated(info->account, channel, channel->invalidationReason(),
+ channel->invalidationMessage());
+ delete wrapper;
+ }
+ }
+
+ mObserveChannelsInfo.remove(op);
+ info->context->setFinished();
+ delete info;
+}
+
+Features SimpleObserver::Private::Observer::featuresFor(
+ const ChannelClassSpec &channelClass) const
+{
+ Features features;
+
+ foreach (const ChannelClassFeatures &spec, mExtraChannelFeatures) {
+ if (spec.first.isSubsetOf(channelClass)) {
+ features.unite(spec.second);
+ }
+ }
+
+ return features;
+}
+
+SimpleObserver::Private::ChannelWrapper::ChannelWrapper(const AccountPtr &channelAccount,
+ const ChannelPtr &channel, const Features &extraChannelFeatures, QObject *parent)
+ : QObject(parent),
+ mChannelAccount(channelAccount),
+ mChannel(channel),
+ mExtraChannelFeatures(extraChannelFeatures)
+{
+ connect(channel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::DBusProxy*,QString,QString)));
+}
+
+PendingOperation *SimpleObserver::Private::ChannelWrapper::becomeReady()
+{
+ PendingOperation *op;
+
+ if (!mChannel->isReady(mExtraChannelFeatures)) {
+ // The channel factory passed to the Account used by SimpleObserver does
+ // not contain the extra features, request them
+ op = mChannel->becomeReady(mExtraChannelFeatures);
+ } else {
+ op = new PendingSuccess(mChannel);
+ }
+
+ return op;
+}
+
+void SimpleObserver::Private::ChannelWrapper::onChannelInvalidated(DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ Q_ASSERT(proxy == mChannel.data());
+ emit channelInvalidated(mChannelAccount, mChannel, errorName, errorMessage);
+}
+
+/**
+ * \class SimpleObserver
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-observer.h <TelepathyQt4/SimpleObserver>
+ *
+ * \brief The SimpleObserver class provides an easy way to track channels
+ * in an account and can be optionally filtered by a contact.
+ */
+
+/**
+ * Create a new SimpleObserver object.
+ *
+ * Events will be signalled for all channels in \a account that match
+ * \a channelFilter for all contacts.
+ *
+ * \param channelFilter A specification of the channels in which this observer
+ * is interested.
+ * \param account The account used to listen to events.
+ * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in
+ * newChannels() will have the extra features that match their
+ * immutable properties enabled.
+ * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object.
+ */
+SimpleObserverPtr SimpleObserver::create(
+ const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QList<ChannelClassFeatures> &extraChannelFeatures)
+{
+ return create(account, channelFilter, extraChannelFeatures);
+}
+
+/**
+ * Create a new SimpleObserver object.
+ *
+ * Events will be signalled for all channels in \a account established with
+ * \a contact, if not null, and that match \a channelFilter.
+ *
+ * \param channelFilter A specification of the channels in which this observer
+ * is interested.
+ * \param account The account used to listen to events.
+ * \param contact The contact used to filter events.
+ * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in
+ * newChannels() will have the extra features that match their
+ * immutable properties enabled.
+ * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object.
+ */
+SimpleObserverPtr SimpleObserver::create(
+ const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const ContactPtr &contact,
+ const QList<ChannelClassFeatures> &extraChannelFeatures)
+{
+ if (contact) {
+ return create(account, channelFilter, contact->id(), false, extraChannelFeatures);
+ }
+ return create(account, channelFilter, QString(), false, extraChannelFeatures);
+}
+
+/**
+ * Create a new SimpleObserver object.
+ *
+ * Events will be signalled for all channels in \a account established
+ * with a contact identified by \a contactIdentifier, if non-empty, and that match
+ * \a channelFilter.
+ *
+ * \param channelFilter A specification of the channels in which this observer
+ * is interested.
+ * \param account The account used to listen to events.
+ * \param contactIdentifier The identifier of the contact used to filter events.
+ * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in
+ * newChannels() will have the extra features that match their
+ * immutable properties enabled.
+ * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object.
+ */
+SimpleObserverPtr SimpleObserver::create(
+ const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ const QList<ChannelClassFeatures> &extraChannelFeatures)
+{
+ return create(account, channelFilter, contactIdentifier, true, extraChannelFeatures);
+}
+
+SimpleObserverPtr SimpleObserver::create(
+ const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ bool requiresNormalization,
+ const QList<ChannelClassFeatures> &extraChannelFeatures)
+{
+ return SimpleObserverPtr(new SimpleObserver(account, channelFilter, contactIdentifier,
+ requiresNormalization, extraChannelFeatures));
+}
+
+/**
+ * Construct a new SimpleObserver object.
+ *
+ * \param cr The ClientRegistrar used to register this observer.
+ * \param channelFilter The channel filter used by this observer.
+ * \param account The account used to listen to events.
+ * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in
+ * newChannels() will have the extra features that match their
+ * immutable properties enabled.
+ * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object.
+ */
+SimpleObserver::SimpleObserver(const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier, bool requiresNormalization,
+ const QList<ChannelClassFeatures> &extraChannelFeatures)
+ : mPriv(new Private(this, account, channelFilter, contactIdentifier,
+ requiresNormalization, extraChannelFeatures))
+{
+ if (mPriv->observer) {
+ // populate our channels list with current observer channels
+ QHash<AccountPtr, QList<ChannelPtr> > channels;
+ foreach (const Private::ChannelWrapper *wrapper, mPriv->observer->channels()) {
+ channels[wrapper->channelAccount()].append(wrapper->channel());
+ }
+
+ QHash<AccountPtr, QList<ChannelPtr> >::const_iterator it = channels.constBegin();
+ QHash<AccountPtr, QList<ChannelPtr> >::const_iterator end = channels.constEnd();
+ for (; it != end; ++it) {
+ onNewChannels(it.key(), it.value());
+ }
+
+ if (requiresNormalization) {
+ debug() << "Contact id requires normalization. "
+ "Queueing events until it is normalized";
+ onAccountConnectionChanged(account->connection());
+ }
+ }
+}
+
+/**
+ * Class destructor.
+ */
+SimpleObserver::~SimpleObserver()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account used to listen to events.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr SimpleObserver::account() const
+{
+ return mPriv->account;
+}
+
+/**
+ * Return a specification of the channels that this observer is interested.
+ *
+ * \return The specification of the channels as a list of ChannelClassSpec objects.
+ */
+ChannelClassSpecList SimpleObserver::channelFilter() const
+{
+ return mPriv->channelFilter;
+}
+
+/**
+ * Return the extra channel features to be enabled based on the channels immutable properties.
+ *
+ * \return The features as a list of ChannelClassFeatures objects.
+ */
+QList<ChannelClassFeatures> SimpleObserver::extraChannelFeatures() const
+{
+ return mPriv->extraChannelFeatures;
+}
+
+/**
+ * Return the channels being observed.
+ *
+ * \return A list of pointers to Channel objects.
+ */
+QList<ChannelPtr> SimpleObserver::channels() const
+{
+ return mPriv->channels.toList();
+}
+
+/**
+ * Return the identifier of the contact used to filter events, or an empty string if none was
+ * provided at construction.
+ *
+ * \return The identifier of the contact.
+ */
+QString SimpleObserver::contactIdentifier() const
+{
+ return mPriv->contactIdentifier;
+}
+
+void SimpleObserver::onAccountConnectionChanged(const Tp::ConnectionPtr &connection)
+{
+ if (connection) {
+ connect(connection->becomeReady(Connection::FeatureConnected),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAccountConnectionConnected()));
+ }
+}
+
+void SimpleObserver::onAccountConnectionConnected()
+{
+ ConnectionPtr conn = mPriv->account->connection();
+
+ // check here again as the account connection may have changed and the op failed
+ if (!conn || conn->status() != ConnectionStatusConnected) {
+ return;
+ }
+
+ debug() << "Normalizing contact id" << mPriv->contactIdentifier;
+ ContactManagerPtr contactManager = conn->contactManager();
+ connect(contactManager->contactsForIdentifiers(QStringList() << mPriv->contactIdentifier),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactConstructed(Tp::PendingOperation*)));
+}
+
+void SimpleObserver::onContactConstructed(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ // what should we do here? retry? wait for a new connection?
+ warning() << "Normalizing contact id failed with" <<
+ op->errorName() << " : " << op->errorMessage();
+ return;
+ }
+
+ PendingContacts *pc = qobject_cast<PendingContacts*>(op);
+ Q_ASSERT((pc->contacts().size() + pc->invalidIdentifiers().size()) == 1);
+ if (!pc->invalidIdentifiers().isEmpty()) {
+ warning() << "Normalizing contact id failed with invalid id" <<
+ mPriv->contactIdentifier;
+ return;
+ }
+
+ ContactPtr contact = pc->contacts().first();
+ debug() << "Contact id" << mPriv->contactIdentifier <<
+ "normalized to" << contact->id();
+ mPriv->normalizedContactIdentifier = contact->id();
+ mPriv->processChannelsQueue();
+
+ // disconnect all account signals we are handling
+ disconnect(mPriv->account.data(), 0, this, 0);
+}
+
+void SimpleObserver::onNewChannels(const AccountPtr &channelsAccount,
+ const QList<ChannelPtr> &channels)
+{
+ if (!mPriv->contactIdentifier.isEmpty() && mPriv->normalizedContactIdentifier.isEmpty()) {
+ mPriv->newChannelsQueue.append(Private::NewChannelsInfo(channelsAccount, channels));
+ mPriv->channelsQueue.append(&SimpleObserver::Private::processNewChannelsQueue);
+ return;
+ }
+
+ mPriv->insertChannels(channelsAccount, channels);
+}
+
+void SimpleObserver::onChannelInvalidated(const AccountPtr &channelAccount,
+ const ChannelPtr &channel, const QString &errorName, const QString &errorMessage)
+{
+ if (!mPriv->contactIdentifier.isEmpty() && mPriv->normalizedContactIdentifier.isEmpty()) {
+ mPriv->channelsInvalidationQueue.append(Private::ChannelInvalidationInfo(channelAccount,
+ channel, errorName, errorMessage));
+ mPriv->channelsQueue.append(&SimpleObserver::Private::processChannelsInvalidationQueue);
+ return;
+ }
+
+ mPriv->removeChannel(channelAccount, channel, errorName, errorMessage);
+}
+
+/**
+ * \fn void SimpleObserver::newChannels(const QList<Tp::ChannelPtr> &channels)
+ *
+ * Emitted whenever new channels that match this observer's criteria are created.
+ *
+ * \param channels The new channels.
+ */
+
+/**
+ * \fn void SimpleObserver::channelInvalidated(const Tp::ChannelPtr &channel,
+ * const QString &errorName, const QString &errorMessage)
+ *
+ * Emitted whenever a channel that is being observed is invalidated.
+ *
+ * \param channel The channel that was invalidated.
+ * \param errorName A D-Bus error name (a string in a subset
+ * of ASCII, prefixed with a reversed domain name).
+ * \param errorMessage A debugging message associated with the error.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/simple-observer.h b/qt4/TelepathyQt4/simple-observer.h
new file mode 100644
index 000000000..7f0c7c42a
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-observer.h
@@ -0,0 +1,105 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_simple_observer_h_HEADER_GUARD_
+#define _TelepathyQt4_simple_observer_h_HEADER_GUARD_
+
+#include <TelepathyQt4/AbstractClientObserver>
+#include <TelepathyQt4/ChannelClassFeatures>
+#include <TelepathyQt4/Types>
+
+#include <QObject>
+
+namespace Tp
+{
+
+class PendingOperation;
+
+class TELEPATHY_QT4_EXPORT SimpleObserver : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(SimpleObserver)
+
+public:
+ static SimpleObserverPtr create(const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QList<ChannelClassFeatures> &extraChannelFeatures =
+ QList<ChannelClassFeatures>());
+ static SimpleObserverPtr create(const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const ContactPtr &contact,
+ const QList<ChannelClassFeatures> &extraChannelFeatures =
+ QList<ChannelClassFeatures>());
+ static SimpleObserverPtr create(const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ const QList<ChannelClassFeatures> &extraChannelFeatures =
+ QList<ChannelClassFeatures>());
+
+ virtual ~SimpleObserver();
+
+ AccountPtr account() const;
+ ChannelClassSpecList channelFilter() const;
+ QString contactIdentifier() const;
+ QList<ChannelClassFeatures> extraChannelFeatures() const;
+
+ QList<ChannelPtr> channels() const;
+
+Q_SIGNALS:
+ void newChannels(const QList<Tp::ChannelPtr> &channels);
+ void channelInvalidated(const Tp::ChannelPtr &channel, const QString &errorName,
+ const QString &errorMessage);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onAccountConnectionChanged(const Tp::ConnectionPtr &connection);
+ TELEPATHY_QT4_NO_EXPORT void onAccountConnectionConnected();
+ TELEPATHY_QT4_NO_EXPORT void onContactConstructed(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onNewChannels(const Tp::AccountPtr &channelsAccount,
+ const QList<Tp::ChannelPtr> &channels);
+ TELEPATHY_QT4_NO_EXPORT void onChannelInvalidated(const Tp::AccountPtr &channelAccount,
+ const Tp::ChannelPtr &channel, const QString &errorName, const QString &errorMessage);
+
+private:
+ friend class SimpleCallObserver;
+ friend class SimpleTextObserver;
+
+ TELEPATHY_QT4_NO_EXPORT static SimpleObserverPtr create(const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ bool requiresNormalization,
+ const QList<ChannelClassFeatures> &extraChannelFeatures);
+
+ TELEPATHY_QT4_NO_EXPORT SimpleObserver(const AccountPtr &account,
+ const ChannelClassSpecList &channelFilter,
+ const QString &contactIdentifier,
+ bool requiresNormalization,
+ const QList<ChannelClassFeatures> &extraChannelFeatures);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/simple-pending-operations.h b/qt4/TelepathyQt4/simple-pending-operations.h
new file mode 100644
index 000000000..096200550
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-pending-operations.h
@@ -0,0 +1,110 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_pending_operations_h_HEADER_GUARD_
+#define _TelepathyQt4_pending_operations_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <QObject>
+
+#include <TelepathyQt4/PendingOperation>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT PendingSuccess : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingSuccess)
+
+public:
+ PendingSuccess(const SharedPtr<RefCounted> &object)
+ : PendingOperation(object)
+ {
+ setFinished();
+ }
+};
+
+class TELEPATHY_QT4_EXPORT PendingFailure : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingFailure)
+
+public:
+ PendingFailure(const QString &name, const QString &message,
+ const SharedPtr<RefCounted> &object)
+ : PendingOperation(object)
+ {
+ setFinishedWithError(name, message);
+ }
+
+ PendingFailure(const QDBusError &error,
+ const SharedPtr<RefCounted> &object)
+ : PendingOperation(object)
+ {
+ setFinishedWithError(error);
+ }
+};
+
+class TELEPATHY_QT4_EXPORT PendingVoid : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingVoid)
+
+public:
+ PendingVoid(QDBusPendingCall call, const SharedPtr<RefCounted> &object);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT PendingComposite : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingComposite)
+
+public:
+ PendingComposite(const QList<PendingOperation*> &operations, const SharedPtr<RefCounted> &object);
+ PendingComposite(const QList<PendingOperation*> &operations, bool failOnFirstError,
+ const SharedPtr<RefCounted> &object);
+ ~PendingComposite();
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onOperationFinished(Tp::PendingOperation *);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/simple-stream-tube-handler.cpp b/qt4/TelepathyQt4/simple-stream-tube-handler.cpp
new file mode 100644
index 000000000..057800749
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-stream-tube-handler.cpp
@@ -0,0 +1,254 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "TelepathyQt4/simple-stream-tube-handler.h"
+
+#include "TelepathyQt4/_gen/simple-stream-tube-handler.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/StreamTubeChannel>
+
+namespace Tp
+{
+
+namespace
+{
+ ChannelClassSpecList buildFilter(const QStringList &p2pServices,
+ const QStringList &roomServices, bool requested)
+ {
+ ChannelClassSpecList filter;
+
+ // Convert to QSet to weed out duplicates
+ foreach (const QString &service, p2pServices.toSet())
+ {
+ filter.append(requested ?
+ ChannelClassSpec::outgoingStreamTube(service) :
+ ChannelClassSpec::incomingStreamTube(service));
+ }
+
+ // Convert to QSet to weed out duplicates
+ foreach (const QString &service, roomServices.toSet())
+ {
+ filter.append(requested ?
+ ChannelClassSpec::outgoingRoomStreamTube(service) :
+ ChannelClassSpec::incomingRoomStreamTube(service));
+ }
+
+ return filter;
+ }
+}
+
+SharedPtr<SimpleStreamTubeHandler> SimpleStreamTubeHandler::create(
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ bool requested,
+ bool monitorConnections,
+ bool bypassApproval)
+{
+ return SharedPtr<SimpleStreamTubeHandler>(
+ new SimpleStreamTubeHandler(
+ p2pServices,
+ roomServices,
+ requested,
+ monitorConnections,
+ bypassApproval));
+}
+
+SimpleStreamTubeHandler::SimpleStreamTubeHandler(
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ bool requested,
+ bool monitorConnections,
+ bool bypassApproval)
+ : AbstractClient(),
+ AbstractClientHandler(buildFilter(p2pServices, roomServices, requested)),
+ mMonitorConnections(monitorConnections),
+ mBypassApproval(bypassApproval)
+{
+}
+
+SimpleStreamTubeHandler::~SimpleStreamTubeHandler()
+{
+ if (!mTubes.empty()) {
+ debug() << "~SSTubeHandler(): Closing" << mTubes.size() << "leftover tubes";
+
+ foreach (const StreamTubeChannelPtr &tube, mTubes.keys()) {
+ tube->requestClose();
+ }
+ }
+}
+
+void SimpleStreamTubeHandler::handleChannels(
+ const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo)
+{
+ debug() << "SimpleStreamTubeHandler::handleChannels() invoked for " <<
+ channels.size() << "channels on account" << account->objectPath();
+
+ SharedPtr<InvocationData> invocation(new InvocationData());
+ QList<PendingOperation *> readyOps;
+
+ foreach (const ChannelPtr &chan, channels) {
+ StreamTubeChannelPtr tube = StreamTubeChannelPtr::qObjectCast(chan);
+
+ if (!tube) {
+ // TODO: if Channel ever starts utilizing its immutable props for the immutable
+ // accessors, use Channel::channelType() here
+ const QString channelType =
+ chan->immutableProperties()[QLatin1String(
+ TELEPATHY_INTERFACE_CHANNEL ".ChannelType")].toString();
+
+ if (channelType != TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE) {
+ debug() << "We got a non-StreamTube channel" << chan->objectPath() <<
+ "of type" << channelType << ", ignoring";
+ } else {
+ warning() << "The channel factory used for a simple StreamTube handler must" <<
+ "construct StreamTubeChannel subclasses for stream tubes";
+ }
+ continue;
+ }
+
+ Features features = StreamTubeChannel::FeatureStreamTube;
+ if (mMonitorConnections) {
+ features.insert(StreamTubeChannel::FeatureConnectionMonitoring);
+ }
+ readyOps.append(tube->becomeReady(features));
+
+ invocation->tubes.append(tube);
+ }
+
+ invocation->ctx = context;
+ invocation->acc = account;
+ invocation->time = userActionTime;
+
+ if (!requestsSatisfied.isEmpty()) {
+ invocation->hints = requestsSatisfied.first()->hints();
+ }
+
+ mInvocations.append(invocation);
+
+ if (invocation->tubes.isEmpty()) {
+ warning() << "SSTH::HandleChannels got no suitable channels, admitting we're Confused";
+ invocation->readyOp = 0;
+ invocation->error = TP_QT4_ERROR_CONFUSED;
+ invocation->message = QLatin1String("Got no suitable channels");
+ onReadyOpFinished(0);
+ } else {
+ invocation->readyOp = new PendingComposite(readyOps, SharedPtr<SimpleStreamTubeHandler>(this));
+ connect(invocation->readyOp,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onReadyOpFinished(Tp::PendingOperation*)));
+ }
+}
+
+void SimpleStreamTubeHandler::onReadyOpFinished(Tp::PendingOperation *op)
+{
+ Q_ASSERT(!mInvocations.isEmpty());
+ Q_ASSERT(!op || op->isFinished());
+
+ for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin();
+ op != 0 && i != mInvocations.end(); ++i) {
+ if ((*i)->readyOp != op) {
+ continue;
+ }
+
+ (*i)->readyOp = 0;
+
+ if (op->isError()) {
+ warning() << "Preparing proxies for SSTubeHandler failed with" << op->errorName()
+ << op->errorMessage();
+ (*i)->error = op->errorName();
+ (*i)->message = op->errorMessage();
+ }
+
+ break;
+ }
+
+ while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) {
+ SharedPtr<InvocationData> invocation = mInvocations.takeFirst();
+
+ if (!invocation->error.isEmpty()) {
+ // We guarantee that the proxies were ready - so we can't invoke the client if they
+ // weren't made ready successfully. Fix the introspection code if this happens :)
+ invocation->ctx->setFinishedWithError(invocation->error, invocation->message);
+ continue;
+ }
+
+ debug() << "Emitting SSTubeHandler::invokedForTube for" << invocation->tubes.size()
+ << "tubes";
+
+ foreach (const StreamTubeChannelPtr &tube, invocation->tubes) {
+ if (!tube->isValid()) {
+ debug() << "Skipping already invalidated tube" << tube->objectPath();
+ continue;
+ }
+
+ if (!mTubes.contains(tube)) {
+ connect(tube.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ mTubes.insert(tube, invocation->acc);
+ }
+
+ emit invokedForTube(
+ invocation->acc,
+ tube,
+ invocation->time,
+ invocation->hints);
+ }
+
+ invocation->ctx->setFinished();
+ }
+}
+
+void SimpleStreamTubeHandler::onTubeInvalidated(DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ StreamTubeChannelPtr tube(qobject_cast<StreamTubeChannel *>(proxy));
+
+ Q_ASSERT(!tube.isNull());
+ Q_ASSERT(mTubes.contains(tube));
+
+ debug() << "Tube" << tube->objectPath() << "invalidated - " << errorName << ':' << errorMessage;
+
+ AccountPtr acc = mTubes.value(tube);
+ mTubes.remove(tube);
+
+ emit tubeInvalidated(
+ acc,
+ tube,
+ errorName,
+ errorMessage);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/simple-stream-tube-handler.h b/qt4/TelepathyQt4/simple-stream-tube-handler.h
new file mode 100644
index 000000000..313afc5fe
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-stream-tube-handler.h
@@ -0,0 +1,120 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_simple_stream_tube_handler_h_HEADER_GUARD_
+#define _TelepathyQt4_simple_stream_tube_handler_h_HEADER_GUARD_
+
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/ChannelRequestHints>
+#include <TelepathyQt4/RefCounted>
+#include <TelepathyQt4/Types>
+
+#include <QDateTime>
+#include <QLinkedList>
+#include <QHash>
+#include <QQueue>
+#include <QSet>
+
+namespace Tp
+{
+
+class PendingOperation;
+
+class TELEPATHY_QT4_NO_EXPORT SimpleStreamTubeHandler : public QObject,
+ public AbstractClientHandler
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(SimpleStreamTubeHandler)
+
+public:
+ static SharedPtr<SimpleStreamTubeHandler> create(
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ bool requested,
+ bool monitorConnections,
+ bool bypassApproval = false);
+ ~SimpleStreamTubeHandler();
+
+ bool monitorsConnections() const
+ {
+ return mMonitorConnections;
+ }
+
+ bool bypassApproval() const
+ {
+ return mBypassApproval;
+ }
+
+ void handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo);
+
+Q_SIGNALS:
+ void invokedForTube(
+ const Tp::AccountPtr &account,
+ const Tp::StreamTubeChannelPtr &tube,
+ const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &requestHints);
+ void tubeInvalidated(
+ const Tp::AccountPtr &account,
+ const Tp::StreamTubeChannelPtr &tube,
+ const QString &errorName,
+ const QString &errorMessage);
+
+private Q_SLOTS:
+ void onReadyOpFinished(Tp::PendingOperation *);
+ void onTubeInvalidated(Tp::DBusProxy *, const QString &, const QString &);
+
+private:
+ SimpleStreamTubeHandler(
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ bool requested,
+ bool monitorConnections,
+ bool bypassApproval);
+
+ bool mMonitorConnections;
+
+ struct InvocationData : RefCounted {
+ InvocationData() : readyOp(0) {}
+
+ PendingOperation *readyOp;
+ QString error, message;
+
+ MethodInvocationContextPtr<> ctx;
+ AccountPtr acc;
+ QList<StreamTubeChannelPtr> tubes;
+ QDateTime time;
+ ChannelRequestHints hints;
+ };
+ QLinkedList<SharedPtr<InvocationData> > mInvocations;
+ QHash<StreamTubeChannelPtr, AccountPtr> mTubes;
+ bool mBypassApproval;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/simple-text-observer-internal.h b/qt4/TelepathyQt4/simple-text-observer-internal.h
new file mode 100644
index 000000000..9dd6e786e
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-text-observer-internal.h
@@ -0,0 +1,70 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/SimpleObserver>
+#include <TelepathyQt4/TextChannel>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT SimpleTextObserver::Private
+{
+ Private(SimpleTextObserver *parent, const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization);
+ ~Private();
+
+ class TextChannelWrapper;
+
+ SimpleTextObserver *parent;
+ AccountPtr account;
+ QString contactIdentifier;
+ SimpleObserverPtr observer;
+ QHash<ChannelPtr, TextChannelWrapper*> channels;
+};
+
+class TELEPATHY_QT4_NO_EXPORT SimpleTextObserver::Private::TextChannelWrapper :
+ public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(TextChannelWrapper)
+
+public:
+ TextChannelWrapper(const Tp::TextChannelPtr &channel);
+ ~TextChannelWrapper() { }
+
+Q_SIGNALS:
+ void channelMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken, const Tp::TextChannelPtr &channel);
+ void channelMessageReceived(const Tp::ReceivedMessage &message, const Tp::TextChannelPtr &channel);
+
+private Q_SLOTS:
+ void onChannelMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken);
+ void onChannelMessageReceived(const Tp::ReceivedMessage &message);
+
+private:
+ TextChannelPtr mChannel;
+};
+
+} // Tp
diff --git a/qt4/TelepathyQt4/simple-text-observer.cpp b/qt4/TelepathyQt4/simple-text-observer.cpp
new file mode 100644
index 000000000..ce63695b3
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-text-observer.cpp
@@ -0,0 +1,298 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/SimpleTextObserver>
+#include "TelepathyQt4/simple-text-observer-internal.h"
+
+#include "TelepathyQt4/_gen/simple-text-observer.moc.hpp"
+#include "TelepathyQt4/_gen/simple-text-observer-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingSuccess>
+
+namespace Tp
+{
+
+SimpleTextObserver::Private::Private(SimpleTextObserver *parent,
+ const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization)
+ : parent(parent),
+ account(account),
+ contactIdentifier(contactIdentifier)
+{
+ debug() << "Creating a new SimpleTextObserver";
+ ChannelClassSpec channelFilter = ChannelClassSpec::textChat();
+ observer = SimpleObserver::create(account, ChannelClassSpecList() << channelFilter,
+ contactIdentifier, requiresNormalization,
+ QList<ChannelClassFeatures>() << ChannelClassFeatures(channelFilter,
+ TextChannel::FeatureMessageQueue | TextChannel::FeatureMessageSentSignal));
+
+ parent->connect(observer.data(),
+ SIGNAL(newChannels(QList<Tp::ChannelPtr>)),
+ SLOT(onNewChannels(QList<Tp::ChannelPtr>)));
+ parent->connect(observer.data(),
+ SIGNAL(channelInvalidated(Tp::ChannelPtr,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::ChannelPtr)));
+}
+
+SimpleTextObserver::Private::~Private()
+{
+ foreach (TextChannelWrapper *wrapper, channels) {
+ delete wrapper;
+ }
+}
+
+SimpleTextObserver::Private::TextChannelWrapper::TextChannelWrapper(const TextChannelPtr &channel)
+ : mChannel(channel)
+{
+ connect(mChannel.data(),
+ SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)),
+ SLOT(onChannelMessageSent(Tp::Message,Tp::MessageSendingFlags,QString)));
+ connect(mChannel.data(),
+ SIGNAL(messageReceived(Tp::ReceivedMessage)),
+ SLOT(onChannelMessageReceived(Tp::ReceivedMessage)));
+}
+
+void SimpleTextObserver::Private::TextChannelWrapper::onChannelMessageSent(
+ const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken)
+{
+ emit channelMessageSent(message, flags, sentMessageToken, mChannel);
+}
+
+void SimpleTextObserver::Private::TextChannelWrapper::onChannelMessageReceived(
+ const Tp::ReceivedMessage &message)
+{
+ emit channelMessageReceived(message, mChannel);
+}
+
+/**
+ * \class SimpleTextObserver
+ * \ingroup utils
+ * \headerfile TelepathyQt4/simple-text-observer.h <TelepathyQt4/SimpleTextObserver>
+ *
+ * \brief The SimpleTextObserver class provides an easy way to track sent/received text messages
+ * in an account and can be optionally filtered by a contact.
+ */
+
+/**
+ * Create a new SimpleTextObserver object.
+ *
+ * Events will be signalled for all messages sent/received by all contacts in \a account.
+ *
+ * \param account The account used to listen to events.
+ * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object.
+ */
+SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account)
+{
+ return create(account, QString(), false);
+}
+
+/**
+ * Create a new SimpleTextObserver object.
+ *
+ * If \a contact is not null, events will be signalled for all messages sent/received by \a
+ * contact, otherwise this method works the same as create(const Tp::AccountPtr &).
+ *
+ * \param account The account used to listen to events.
+ * \param contact The contact used to filter events.
+ * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object.
+ */
+SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account,
+ const ContactPtr &contact)
+{
+ if (contact) {
+ return create(account, contact->id(), false);
+ }
+ return create(account, QString(), false);
+}
+
+/**
+ * Create a new SimpleTextObserver object.
+ *
+ * If \a contactIdentifier is non-empty, events will be signalled for all messages sent/received
+ * by a contact identified by \a contactIdentifier, otherwise this method works the same as
+ * create(const Tp::AccountPtr &).
+ *
+ * \param account The account used to listen to events.
+ * \param contactIdentifier The identifier of the contact used to filter events.
+ * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object.
+ */
+SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account,
+ const QString &contactIdentifier)
+{
+ return create(account, contactIdentifier, true);
+}
+
+SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization)
+{
+ return SimpleTextObserverPtr(
+ new SimpleTextObserver(account, contactIdentifier, requiresNormalization));
+}
+
+/**
+ * Construct a new SimpleTextObserver object.
+ *
+ * \param account The account used to listen to events.
+ * \param contactIdentifier The identifier of the contact used to filter events.
+ * \param requiresNormalization Whether \a contactIdentifier needs to be normalized.
+ * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object.
+ */
+SimpleTextObserver::SimpleTextObserver(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization)
+ : mPriv(new Private(this, account, contactIdentifier, requiresNormalization))
+{
+ if (!mPriv->observer->channels().isEmpty()) {
+ onNewChannels(mPriv->observer->channels());
+ }
+}
+
+/**
+ * Class destructor.
+ */
+SimpleTextObserver::~SimpleTextObserver()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the account used to listen to events.
+ *
+ * \return A pointer to the Account object.
+ */
+AccountPtr SimpleTextObserver::account() const
+{
+ return mPriv->account;
+}
+
+/**
+ * Return the identifier of the contact used to filter events, or an empty string if none was
+ * provided at construction.
+ *
+ * \return The identifier of the contact.
+ */
+QString SimpleTextObserver::contactIdentifier() const
+{
+ return mPriv->contactIdentifier;
+}
+
+/**
+ * Return the list of text chats currently being observed.
+ *
+ * \return A list of pointers to TextChannel objects.
+ */
+QList<TextChannelPtr> SimpleTextObserver::textChats() const
+{
+ QList<TextChannelPtr> ret;
+ foreach (const ChannelPtr &channel, mPriv->observer->channels()) {
+ TextChannelPtr textChannel = TextChannelPtr::qObjectCast(channel);
+ if (textChannel) {
+ ret << textChannel;
+ }
+ }
+ return ret;
+}
+
+void SimpleTextObserver::onNewChannels(const QList<ChannelPtr> &channels)
+{
+ foreach (const ChannelPtr &channel, channels) {
+ TextChannelPtr textChannel = TextChannelPtr::qObjectCast(channel);
+ if (!textChannel) {
+ if (channel->channelType() != TP_QT4_IFACE_CHANNEL_TYPE_TEXT) {
+ warning() << "Channel received to observe is not of type Text, service confused. "
+ "Ignoring channel";
+ } else {
+ warning() << "Channel received to observe is not a subclass of TextChannel. "
+ "ChannelFactory set on this observer's account must construct TextChannel "
+ "subclasses for channels of type Text. Ignoring channel";
+ }
+ continue;
+ }
+
+ if (mPriv->channels.contains(channel)) {
+ // we are already observing this channel
+ continue;
+ }
+
+ Private::TextChannelWrapper *wrapper = new Private::TextChannelWrapper(textChannel);
+ mPriv->channels.insert(channel, wrapper);
+ connect(wrapper,
+ SIGNAL(channelMessageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr)),
+ SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr)));
+ connect(wrapper,
+ SIGNAL(channelMessageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)),
+ SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)));
+
+ foreach (const ReceivedMessage &message, textChannel->messageQueue()) {
+ emit messageReceived(message, textChannel);
+ }
+ }
+}
+
+void SimpleTextObserver::onChannelInvalidated(const ChannelPtr &channel)
+{
+ // it may happen that the channel received in onNewChannels is not a text channel somehow, thus
+ // the channel won't be added to mPriv->channels
+ if (mPriv->channels.contains(channel)) {
+ delete mPriv->channels.take(channel);
+ }
+}
+
+/**
+ * \fn void SimpleTextObserver::messageSent(const Tp::Message &message,
+ * Tp::MessageSendingFlags flags, const QString &sentMessageToken,
+ * const Tp::TextChannelPtr &channel);
+ *
+ * Emitted whenever a text message on account() is sent.
+ * If contactIdentifier() is non-empty, only messages sent to the contact identified by it will
+ * be signalled.
+ *
+ * \param message The message sent.
+ * \param flags The message flags,
+ * \param sentMessageToken The message token.
+ * \param channel The channel which received the message.
+ */
+
+/**
+ * \fn void SimpleTextObserver::messageReceived(const Tp::ReceivedMessage &message,
+ * const Tp::TextChannelPtr &channel);
+ *
+ * Emitted whenever a text message on account() is received.
+ * If contactIdentifier() is non-empty, only messages received by the contact identified by it will
+ * be signalled.
+ *
+ * \param message The message received.
+ * \param channel The channel which received the message.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/simple-text-observer.h b/qt4/TelepathyQt4/simple-text-observer.h
new file mode 100644
index 000000000..e03665dce
--- /dev/null
+++ b/qt4/TelepathyQt4/simple-text-observer.h
@@ -0,0 +1,80 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_simple_text_observer_h_HEADER_GUARD_
+#define _TelepathyQt4_simple_text_observer_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#include <QObject>
+
+namespace Tp
+{
+
+class Message;
+class PendingOperation;
+class ReceivedMessage;
+
+class TELEPATHY_QT4_EXPORT SimpleTextObserver : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(SimpleTextObserver)
+
+public:
+ static SimpleTextObserverPtr create(const AccountPtr &account);
+ static SimpleTextObserverPtr create(const AccountPtr &account,
+ const ContactPtr &contact);
+ static SimpleTextObserverPtr create(const AccountPtr &account,
+ const QString &contactIdentifier);
+
+ virtual ~SimpleTextObserver();
+
+ AccountPtr account() const;
+ QString contactIdentifier() const;
+
+ QList<TextChannelPtr> textChats() const;
+
+Q_SIGNALS:
+ void messageSent(const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken, const Tp::TextChannelPtr &channel);
+ void messageReceived(const Tp::ReceivedMessage &message, const Tp::TextChannelPtr &channel);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onNewChannels(const QList<Tp::ChannelPtr> &channels);
+ TELEPATHY_QT4_NO_EXPORT void onChannelInvalidated(const Tp::ChannelPtr &channel);
+
+private:
+ TELEPATHY_QT4_NO_EXPORT static SimpleTextObserverPtr create(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization);
+
+ TELEPATHY_QT4_NO_EXPORT SimpleTextObserver(const AccountPtr &account,
+ const QString &contactIdentifier, bool requiresNormalization);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/stable-interfaces.xml b/qt4/TelepathyQt4/stable-interfaces.xml
new file mode 100644
index 000000000..934f65d9c
--- /dev/null
+++ b/qt4/TelepathyQt4/stable-interfaces.xml
@@ -0,0 +1,26 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Telepathy D-Bus Interface Specification, TelepathyQt4 copy</tp:title>
+<tp:version>0.17.7</tp:version>
+
+<xi:include href="connection-manager.xml"/>
+<xi:include href="connection.xml"/>
+<xi:include href="channel.xml"/>
+<xi:include href="channel-dispatcher.xml"/>
+<xi:include href="channel-dispatch-operation.xml"/>
+<xi:include href="channel-request.xml"/>
+<xi:include href="media-session-handler.xml"/>
+<xi:include href="media-stream-handler.xml"/>
+<xi:include href="dbus.xml"/>
+<xi:include href="properties.xml"/>
+<xi:include href="account-manager.xml"/>
+<xi:include href="account.xml"/>
+<xi:include href="client.xml"/>
+<xi:include href="tls-certificate.xml"/>
+
+<xi:include href="../spec/generic-types.xml"/>
+<xi:include href="../spec/errors.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/stream-tube-channel.cpp b/qt4/TelepathyQt4/stream-tube-channel.cpp
new file mode 100644
index 000000000..546551698
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-channel.cpp
@@ -0,0 +1,740 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/StreamTubeChannel>
+
+#include "TelepathyQt4/_gen/stream-tube-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingVariantMap>
+
+#include <QHostAddress>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT StreamTubeChannel::Private
+{
+ Private(StreamTubeChannel *parent);
+
+ static void introspectStreamTube(Private *self);
+ static void introspectConnectionMonitoring(Private *self);
+
+ void extractStreamTubeProperties(const QVariantMap &props);
+
+ // Public object
+ StreamTubeChannel *parent;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ SupportedSocketMap socketTypes;
+ QString serviceName;
+
+ QSet<uint> connections;
+ QPair<QHostAddress, quint16> ipAddress;
+ QString unixAddress;
+ SocketAddressType addressType;
+ SocketAccessControl accessControl;
+ bool droppingConnections;
+};
+
+StreamTubeChannel::Private::Private(StreamTubeChannel *parent)
+ : parent(parent),
+ readinessHelper(parent->readinessHelper()),
+ addressType(SocketAddressTypeUnix),
+ accessControl(SocketAccessControlLocalhost),
+ droppingConnections(false)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableStreamTube(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << TubeChannel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &StreamTubeChannel::Private::introspectStreamTube,
+ this);
+ introspectables[StreamTubeChannel::FeatureCore] = introspectableStreamTube;
+
+ ReadinessHelper::Introspectable introspectableConnectionMonitoring(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << StreamTubeChannel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc)
+ &StreamTubeChannel::Private::introspectConnectionMonitoring,
+ this);
+ introspectables[StreamTubeChannel::FeatureConnectionMonitoring] =
+ introspectableConnectionMonitoring;
+
+ parent->connect(
+ parent,
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(dropConnections()));
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+void StreamTubeChannel::Private::introspectStreamTube(
+ StreamTubeChannel::Private *self)
+{
+ StreamTubeChannel *parent = self->parent;
+
+ debug() << "Introspecting stream tube properties";
+ Client::ChannelTypeStreamTubeInterface *streamTubeInterface =
+ parent->interface<Client::ChannelTypeStreamTubeInterface>();
+
+ PendingVariantMap *pvm = streamTubeInterface->requestAllProperties();
+ parent->connect(pvm,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(gotStreamTubeProperties(Tp::PendingOperation *)));
+}
+
+void StreamTubeChannel::Private::introspectConnectionMonitoring(
+ StreamTubeChannel::Private *self)
+{
+ StreamTubeChannel *parent = self->parent;
+
+ Client::ChannelTypeStreamTubeInterface *streamTubeInterface =
+ parent->interface<Client::ChannelTypeStreamTubeInterface>();
+
+ parent->connect(streamTubeInterface,
+ SIGNAL(ConnectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString)));
+
+ if (parent->isRequested()) {
+ parent->connect(streamTubeInterface,
+ SIGNAL(NewRemoteConnection(uint,QDBusVariant,uint)),
+ SLOT(onNewRemoteConnection(uint,QDBusVariant,uint)));
+ } else {
+ parent->connect(streamTubeInterface,
+ SIGNAL(NewLocalConnection(uint)),
+ SLOT(onNewLocalConnection(uint)));
+ }
+
+ self->readinessHelper->setIntrospectCompleted(
+ StreamTubeChannel::FeatureConnectionMonitoring, true);
+}
+
+void StreamTubeChannel::Private::extractStreamTubeProperties(const QVariantMap &props)
+{
+ serviceName = qdbus_cast<QString>(props[QLatin1String("Service")]);
+ socketTypes = qdbus_cast<SupportedSocketMap>(props[QLatin1String("SupportedSocketTypes")]);
+}
+
+/**
+ * \class StreamTubeChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/stream-tube-channel.h <TelepathyQt4/StreamTubeChannel>
+ *
+ * \brief The StreamTubeChannel class represents a Telepathy channel of type StreamTube.
+ *
+ * It provides a transport for reliable and ordered data transfer, similar to SOCK_STREAM sockets.
+ *
+ * StreamTubeChannel is an intermediate base class; OutgoingStreamTubeChannel and
+ * IncomingStreamTubeChannel are the specialized classes used for locally and remotely initiated
+ * tubes respectively.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * StreamTubeChannel object usable.
+ *
+ * Note that this feature must be enabled in order to use most
+ * StreamTubeChannel methods.
+ * See specific methods documentation for more details.
+ */
+const Feature StreamTubeChannel::FeatureCore =
+ Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0);
+
+/**
+ * \deprecated Use StreamTubeChannel::FeatureCore instead.
+ */
+const Feature StreamTubeChannel::FeatureStreamTube = StreamTubeChannel::FeatureCore;
+
+/**
+ * Feature used in order to monitor connections to this stream tube.
+ *
+ * See connection monitoring specific methods' documentation for more details.
+ *
+ * \sa newConnection(), connectionClosed()
+ */
+const Feature StreamTubeChannel::FeatureConnectionMonitoring =
+ Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 1);
+
+/**
+ * Create a new StreamTubeChannel channel.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A StreamTubeChannelPtr object pointing to the newly created
+ * StreamTubeChannel object.
+ */
+StreamTubeChannelPtr StreamTubeChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return StreamTubeChannelPtr(new StreamTubeChannel(connection, objectPath,
+ immutableProperties, StreamTubeChannel::FeatureCore));
+}
+
+/**
+ * Construct a new StreamTubeChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on StreamTubeChannel::FeatureCore.
+ */
+StreamTubeChannel::StreamTubeChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : TubeChannel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+StreamTubeChannel::~StreamTubeChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the service name which will be used over this stream tube. This should be a
+ * well-known TCP service name, for instance "rsync" or "daap".
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return The service name.
+ */
+QString StreamTubeChannel::service() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::service() used with "
+ "FeatureCore not ready";
+ return QString();
+ }
+
+ return mPriv->serviceName;
+}
+
+/**
+ * Return whether this stream tube is capable to accept or offer an IPv4 socket accepting all
+ * incoming connections coming from localhost.
+ *
+ * Note that the \telepathy_spec implies that any connection manager, if capable of providing
+ * stream tubes, must at least support IPv4 sockets with localhost access control.
+ * For this reason, this method should always return \c true.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept or offer an IPv4 socket
+ * accepting all incoming connections coming from localhost, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(),
+ * OutgoingStreamTubeChannel::offerTcpSocket(),
+ * supportsIPv4SocketsWithSpecifiedAddress()
+ */
+bool StreamTubeChannel::supportsIPv4SocketsOnLocalhost() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsIPv4SocketsOnLocalhost() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes.value(SocketAddressTypeIPv4).contains(SocketAccessControlLocalhost);
+}
+
+/**
+ * Return whether this stream tube is capable to accept an IPv4 socket accepting all
+ * incoming connections coming from a specific address for incoming tubes or whether
+ * this stream tube is capable of mapping connections to the socket's source address for outgoing
+ * tubes.
+ *
+ * For incoming tubes, when this capability is available, the stream tube can be accepted specifying
+ * an IPv4 address. Every connection coming from any other address than the specified one will be
+ * rejected.
+ *
+ * For outgoing tubes, when this capability is available, one can keep track of incoming connections
+ * by enabling StreamTubeChannel::FeatureConnectionMonitoring (possibly before
+ * opening the stream tube itself), and checking OutgoingStreamTubeChannel::contactsForConnections()
+ * or OutgoingStreamTubeChannel::connectionsForSourceAddresses().
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsTcpSocket() or
+ * OutgoingStreamTubeChannel::offerTcpSocket() with a specified address to prevent failures,
+ * as the spec implies this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept an IPv4 socket accepting all
+ * incoming connections coming from a specific address for incoming tubes or
+ * the stream tube is capable of mapping connections to the socket's source address for
+ * outgoing tubes, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(),
+ * OutgoingStreamTubeChannel::offerTcpSocket(),
+ * OutgoingStreamTubeChannel::connectionsForSourceAddresses(),
+ * OutgoingStreamTubeChannel::contactsForConnections(),
+ * supportsIPv4SocketsOnLocalhost()
+ */
+bool StreamTubeChannel::supportsIPv4SocketsWithSpecifiedAddress() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsIPv4SocketsWithSpecifiedAddress() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes.value(SocketAddressTypeIPv4).contains(SocketAccessControlPort);
+}
+
+/**
+ * Return whether this stream tube is capable to accept or offer an IPv6 socket accepting all
+ * incoming connections coming from localhost.
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsTcpSocket() or
+ * OutgoingStreamTubeChannel::offerTcpSocket() with a specified address to prevent failures,
+ * as the spec implies this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept or offer an IPv6 socket
+ * accepting all incoming connections coming from localhost, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(),
+ * OutgoingStreamTubeChannel::offerTcpSocket(),
+ * supportsIPv6SocketsWithSpecifiedAddress()
+ */
+bool StreamTubeChannel::supportsIPv6SocketsOnLocalhost() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsIPv6SocketsOnLocalhost() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes.value(SocketAddressTypeIPv6).contains(SocketAccessControlLocalhost);
+}
+
+/**
+ * Return whether this stream tube is capable to accept an IPv6 socket accepting all
+ * incoming connections coming from a specific address for incoming tubes or whether
+ * this stream tube is capable of mapping connections to the socket's source address for outgoing
+ * tubes.
+ *
+ * For incoming tubes, when this capability is available, the stream tube can be accepted specifying
+ * an IPv6 address. Every connection coming from any other address than the specified one will be
+ * rejected.
+ *
+ * For outgoing tubes, when this capability is available, one can keep track of incoming connections
+ * by enabling StreamTubeChannel::FeatureConnectionMonitoring (possibly before
+ * opening the stream tube itself), and checking OutgoingStreamTubeChannel::contactsForConnections()
+ * or OutgoingStreamTubeChannel::connectionsForSourceAddresses().
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsTcpSocket() or
+ * OutgoingStreamTubeChannel::offerTcpSocket() with a specified address to prevent failures,
+ * as the spec implies this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept an IPv6 socket accepting all
+ * incoming connections coming from a specific address for incoming tubes or
+ * the stream tube is capable of mapping connections to the socket's source address for
+ * outgoing tubes, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(),
+ * OutgoingStreamTubeChannel::offerTcpSocket(),
+ * OutgoingStreamTubeChannel::connectionsForSourceAddresses(),
+ * OutgoingStreamTubeChannel::contactsForConnections(),
+ * supportsIPv6SocketsOnLocalhost()
+ */
+bool StreamTubeChannel::supportsIPv6SocketsWithSpecifiedAddress() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsIPv6SocketsWithSpecifiedAddress() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes.value(SocketAddressTypeIPv6).contains(SocketAccessControlPort);
+}
+
+/**
+ * Return whether this stream tube is capable to accept or offer an Unix socket accepting all
+ * incoming connections coming from localhost.
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or
+ * OutgoingStreamTubeChannel::offerUnixSocket() without credentials enabled, as the spec implies
+ * this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept or offer an Unix socket
+ * accepting all incoming connections coming from localhost, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(),
+ * OutgoingStreamTubeChannel::offerUnixSocket(),
+ * supportsUnixSocketsWithCredentials()
+ * supportsAbstractUnixSocketsOnLocalhost(),
+ * supportsAbstractUnixSocketsWithCredentials(),
+ */
+bool StreamTubeChannel::supportsUnixSocketsOnLocalhost() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsUnixSocketsOnLocalhost() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes.value(SocketAddressTypeUnix).contains(SocketAccessControlLocalhost);
+}
+
+/**
+ * Return whether this stream tube is capable to accept or offer an Unix socket which will require
+ * credentials upon connection.
+ *
+ * When this capability is available and enabled, the connecting process must send a byte when
+ * it first connects, which is not considered to be part of the data stream.
+ * If the operating system uses sendmsg() with SCM_CREDS or SCM_CREDENTIALS to pass
+ * credentials over sockets, the connecting process must do so if possible;
+ * if not, it must still send the byte.
+ *
+ * The listening process will disconnect the connection unless it can determine
+ * by OS-specific means that the connecting process has the same user ID as the listening process.
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or
+ * OutgoingStreamTubeChannel::offerUnixSocket() with credentials enabled, as the spec implies
+ * this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept or offer an Unix socket
+ * which will require credentials upon connection, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(),
+ * OutgoingStreamTubeChannel::offerUnixSocket(),
+ * supportsUnixSocketsOnLocalhost(),
+ * supportsAbstractUnixSocketsOnLocalhost(),
+ * supportsAbstractUnixSocketsWithCredentials(),
+ */
+bool StreamTubeChannel::supportsUnixSocketsWithCredentials() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsUnixSocketsWithCredentials() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes[SocketAddressTypeUnix].contains(SocketAccessControlCredentials);
+}
+
+/**
+ * Return whether this stream tube is capable to accept or offer an abstract Unix socket accepting
+ * all incoming connections coming from localhost.
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or
+ * OutgoingStreamTubeChannel::offerUnixSocket() without credentials enabled, as the spec implies
+ * this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept or offer an abstract Unix socket
+ * accepting all incoming connections coming from localhost, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(),
+ * OutgoingStreamTubeChannel::offerUnixSocket(),
+ * supportsUnixSocketsOnLocalhost(),
+ * supportsUnixSocketsWithCredentials(),
+ * supportsAbstractUnixSocketsWithCredentials()
+ */
+bool StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes[SocketAddressTypeAbstractUnix].contains(SocketAccessControlLocalhost);
+}
+
+/**
+ * Return whether this stream tube is capable to accept or offer an abstract Unix socket which will
+ * require credentials upon connection.
+ *
+ * When this capability is available and enabled, the connecting process must send a byte when
+ * it first connects, which is not considered to be part of the data stream.
+ * If the operating system uses sendmsg() with SCM_CREDS or SCM_CREDENTIALS to pass
+ * credentials over sockets, the connecting process must do so if possible;
+ * if not, it must still send the byte.
+ *
+ * The listening process will disconnect the connection unless it can determine
+ * by OS-specific means that the connecting process has the same user ID as the listening process.
+ *
+ * Note that it is strongly advised to call this method before attempting to call
+ * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or
+ * OutgoingStreamTubeChannel::offerUnixSocket() with credentials enabled, as the spec implies
+ * this feature is not compulsory for connection managers.
+ *
+ * This method requires StreamTubeChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the stream tube is capable to accept or offer an abstract Unix socket
+ * which will require credentials upon connection, \c false otherwise.
+ * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(),
+ * OutgoingStreamTubeChannel::offerUnixSocket(),
+ * supportsUnixSocketsOnLocalhost(),
+ * supportsUnixSocketsWithCredentials(),
+ * supportsAbstractUnixSocketsOnLocalhost()
+ */
+bool StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() used with "
+ "FeatureCore not ready";
+ return false;
+ }
+
+ return mPriv->socketTypes[SocketAddressTypeAbstractUnix].contains(SocketAccessControlCredentials);
+}
+
+/**
+ * Return all the known active connections since StreamTubeChannel::FeatureConnectionMonitoring has
+ * been enabled.
+ *
+ * For this method to return all known connections, you need to make
+ * StreamTubeChannel::FeatureConnectionMonitoring ready before accepting or offering the stream
+ * tube.
+ *
+ * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready.
+ *
+ * \return The list of active connection ids.
+ * \sa newConnection(), connectionClosed()
+ */
+UIntList StreamTubeChannel::connections() const
+{
+ if (!isReady(FeatureConnectionMonitoring)) {
+ warning() << "StreamTubeChannel::connections() used with "
+ "FeatureConnectionMonitoring not ready";
+ return UIntList();
+ }
+
+ return mPriv->connections.toList();
+}
+
+/**
+ * Return the type of the tube's local endpoint socket.
+ *
+ * Note that this function will return a valid value only after state() has gone #TubeStateOpen.
+ *
+ * \return The socket type as #SocketAddressType.
+ * \sa localAddress(), ipAddress()
+ */
+SocketAddressType StreamTubeChannel::addressType() const
+{
+ return mPriv->addressType;
+}
+
+/**
+ * Return the access control used by this stream tube.
+ *
+ * Note that this function will only return a valid value after state() has gone #TubeStateOpen.
+ *
+ * \return The access control as #SocketAccessControl.
+ * \sa addressType()
+ */
+SocketAccessControl StreamTubeChannel::accessControl() const
+{
+ return mPriv->accessControl;
+}
+
+/**
+ * Return the IP address/port combination used by this stream tube.
+ *
+ * This method will return a meaningful value only if the local endpoint socket for the tube is a
+ * TCP socket, i.e. addressType() is #SocketAddressTypeIPv4 or #SocketAddressTypeIPv6.
+ *
+ * Note that this function will return a valid value only after state() has gone #TubeStateOpen.
+ *
+ * \return Pair of IP address as QHostAddress and port if using a TCP socket,
+ * or an undefined value otherwise.
+ * \sa localAddress()
+ */
+QPair<QHostAddress, quint16> StreamTubeChannel::ipAddress() const
+{
+ if (state() != TubeChannelStateOpen) {
+ warning() << "Tube not open, returning invalid IP address";
+ return qMakePair<QHostAddress, quint16>(QHostAddress::Null, 0);
+ }
+
+ return mPriv->ipAddress;
+}
+
+/**
+ * Return the local address used by this stream tube.
+ *
+ * This method will return a meaningful value only if the local endpoint socket for the tube is an
+ * UNIX socket, i.e. addressType() is #SocketAddressTypeUnix or #SocketAddressTypeAbstractUnix.
+ *
+ * Note that this function will return a valid value only after state() has gone #TubeStateOpen.
+ *
+ * \return Unix socket address if using an Unix socket,
+ * or an undefined value otherwise.
+ * \sa ipAddress()
+ */
+QString StreamTubeChannel::localAddress() const
+{
+ if (state() != TubeChannelStateOpen) {
+ warning() << "Tube not open, returning invalid local socket address";
+ return QString();
+ }
+
+ return mPriv->unixAddress;
+}
+
+/**
+ * \deprecated This method never did anything useful when called from outside,
+ * and now does nothing at all. It will be removed in the next API/ABI break.
+ */
+void StreamTubeChannel::setBaseTubeType(uint type)
+{
+ Q_UNUSED(type);
+}
+
+void StreamTubeChannel::setConnections(UIntList connections)
+{
+ // This is rather sub-optimal: we'll do a O(n) replace of the old connections list every time a
+ // connection is added, so O(n^2) in total for adding n connections
+ mPriv->connections = QSet<uint>::fromList(connections);
+}
+
+void StreamTubeChannel::addConnection(uint connection)
+{
+ if (!mPriv->connections.contains(connection)) {
+ mPriv->connections.insert(connection);
+ emit newConnection(connection);
+ } else {
+ warning() << "Tried to add connection" << connection << "on StreamTube" << objectPath()
+ << "but it already was there";
+ }
+}
+
+void StreamTubeChannel::removeConnection(uint connection, const QString &error,
+ const QString &message)
+{
+ if (mPriv->connections.contains(connection)) {
+ mPriv->connections.remove(connection);
+ emit connectionClosed(connection, error, message);
+ } else {
+ warning() << "Tried to remove connection" << connection << "from StreamTube" << objectPath()
+ << "but it wasn't there";
+ }
+}
+
+void StreamTubeChannel::setAddressType(SocketAddressType type)
+{
+ mPriv->addressType = type;
+}
+
+void StreamTubeChannel::setAccessControl(SocketAccessControl accessControl)
+{
+ mPriv->accessControl = accessControl;
+}
+
+void StreamTubeChannel::setIpAddress(const QPair<QHostAddress, quint16> &address)
+{
+ mPriv->ipAddress = address;
+}
+
+void StreamTubeChannel::setLocalAddress(const QString &address)
+{
+ mPriv->unixAddress = address;
+}
+
+bool StreamTubeChannel::isDroppingConnections() const
+{
+ return mPriv->droppingConnections;
+}
+
+void StreamTubeChannel::gotStreamTubeProperties(PendingOperation *op)
+{
+ if (!op->isError()) {
+ PendingVariantMap *pvm = qobject_cast<PendingVariantMap *>(op);
+
+ mPriv->extractStreamTubeProperties(pvm->result());
+
+ debug() << "Got reply to Properties::GetAll(StreamTubeChannel)";
+ mPriv->readinessHelper->setIntrospectCompleted(StreamTubeChannel::FeatureCore, true);
+ }
+ else {
+ warning().nospace() << "Properties::GetAll(StreamTubeChannel) failed "
+ "with " << op->errorName() << ": " << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(StreamTubeChannel::FeatureCore, false,
+ op->errorName(), op->errorMessage());
+ }
+}
+
+void StreamTubeChannel::onConnectionClosed(uint connId, const QString &error,
+ const QString &message)
+{
+ removeConnection(connId, error, message);
+}
+
+void StreamTubeChannel::dropConnections()
+{
+ if (!mPriv->connections.isEmpty()) {
+ debug() << "StreamTubeChannel invalidated with" << mPriv->connections.size()
+ << "connections remaining, synthesizing close events";
+ mPriv->droppingConnections = true;
+ foreach (uint connId, mPriv->connections) {
+ removeConnection(connId, TP_QT4_ERROR_ORPHANED,
+ QLatin1String("parent tube invalidated, streams closing"));
+ }
+ mPriv->droppingConnections = false;
+ }
+}
+
+/**
+ * \fn void StreamTubeChannel::connectionClosed(uint connectionId,
+ * const QString &errorName, const QString &errorMessage)
+ *
+ * Emitted when a connection on this stream tube has been closed.
+ *
+ * \param connectionId The unique ID associated with the connection that was closed.
+ * \param errorName The name of a D-Bus error describing the error that occurred.
+ * \param errorMessage A debugging message associated with the error.
+ * \sa newConnection(), connections()
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/stream-tube-channel.h b/qt4/TelepathyQt4/stream-tube-channel.h
new file mode 100644
index 000000000..65d20b460
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-channel.h
@@ -0,0 +1,108 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_stream_tube_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_stream_tube_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/TubeChannel>
+
+class QHostAddress;
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT StreamTubeChannel : public TubeChannel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StreamTubeChannel)
+
+public:
+ static const Feature FeatureCore;
+ // FIXME (API/ABI break) Remove FeatureStreamTube in favour of FeatureCore
+ static const Feature FeatureStreamTube;
+ static const Feature FeatureConnectionMonitoring;
+
+ static StreamTubeChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~StreamTubeChannel();
+
+ QString service() const;
+
+ bool supportsIPv4SocketsOnLocalhost() const;
+ bool supportsIPv4SocketsWithSpecifiedAddress() const;
+
+ bool supportsIPv6SocketsOnLocalhost() const;
+ bool supportsIPv6SocketsWithSpecifiedAddress() const;
+
+ bool supportsUnixSocketsOnLocalhost() const;
+ bool supportsUnixSocketsWithCredentials() const;
+
+ bool supportsAbstractUnixSocketsOnLocalhost() const;
+ bool supportsAbstractUnixSocketsWithCredentials() const;
+
+ // API/ABI break TODO: return QSet<uint> instead
+ UIntList connections() const;
+
+ SocketAddressType addressType() const;
+
+ QPair< QHostAddress, quint16 > ipAddress() const;
+ QString localAddress() const;
+
+Q_SIGNALS:
+ void newConnection(uint connectionId);
+ void connectionClosed(uint connectionId, const QString &errorName,
+ const QString &errorMessage);
+
+protected:
+ StreamTubeChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = StreamTubeChannel::FeatureCore);
+
+ TELEPATHY_QT4_DEPRECATED void setBaseTubeType(uint type);
+ TELEPATHY_QT4_DEPRECATED void setConnections(UIntList connections); // -> {add,remove}Connection
+ void addConnection(uint connection);
+ void removeConnection(uint connection, const QString &error, const QString &message);
+ void setAddressType(SocketAddressType type);
+ SocketAccessControl accessControl() const;
+ void setAccessControl(SocketAccessControl accessControl);
+ void setIpAddress(const QPair<QHostAddress, quint16> &address);
+ void setLocalAddress(const QString &address);
+ bool isDroppingConnections() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotStreamTubeProperties(Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionClosed(uint, const QString &, const QString &);
+ TELEPATHY_QT4_NO_EXPORT void dropConnections();
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+#endif
diff --git a/qt4/TelepathyQt4/stream-tube-client-internal.h b/qt4/TelepathyQt4/stream-tube-client-internal.h
new file mode 100644
index 000000000..cd474eddc
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-client-internal.h
@@ -0,0 +1,61 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/StreamTubeClient>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT StreamTubeClient::TubeWrapper :
+ public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(TubeWrapper)
+
+public:
+ TubeWrapper(const AccountPtr &acc, const IncomingStreamTubeChannelPtr &tube,
+ const QHostAddress &sourceAddress, quint16 sourcePort, StreamTubeClient *parent);
+ TubeWrapper(const AccountPtr &acc, const IncomingStreamTubeChannelPtr &tube,
+ bool requireCredentials, StreamTubeClient *parent);
+ ~TubeWrapper() { }
+
+ AccountPtr mAcc;
+ IncomingStreamTubeChannelPtr mTube;
+ QHostAddress mSourceAddress;
+ quint16 mSourcePort;
+
+Q_SIGNALS:
+ void acceptFinished(TubeWrapper *wrapper, Tp::PendingStreamTubeConnection *conn);
+ void newConnection(TubeWrapper *wrapper, uint conn);
+ void connectionClosed(TubeWrapper *wrapper, uint conn, const QString &error,
+ const QString &message);
+
+private Q_SLOTS:
+ void onTubeAccepted(Tp::PendingOperation *);
+ void onNewConnection(uint);
+ void onConnectionClosed(uint, const QString &, const QString &);
+};
+
+} // Tp
diff --git a/qt4/TelepathyQt4/stream-tube-client.cpp b/qt4/TelepathyQt4/stream-tube-client.cpp
new file mode 100644
index 000000000..4830608a8
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-client.cpp
@@ -0,0 +1,1048 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/StreamTubeClient>
+
+#include "TelepathyQt4/stream-tube-client-internal.h"
+#include "TelepathyQt4/_gen/stream-tube-client.moc.hpp"
+#include "TelepathyQt4/_gen/stream-tube-client-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/simple-stream-tube-handler.h"
+
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/PendingStreamTubeConnection>
+#include <TelepathyQt4/StreamTubeChannel>
+
+#include <QAbstractSocket>
+#include <QHash>
+
+namespace Tp
+{
+
+/**
+ * \class StreamTubeClient::TcpSourceAddressGenerator
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-client.h <TelepathyQt4/StreamTubeClient>
+ *
+ * \brief The StreamTubeClient::TcpSourceAddressGenerator abstract interface allows using socket
+ * source address/port based access control for connecting to tubes accepted as TCP sockets.
+ *
+ * By default, every application on the local computer is allowed to connect to the socket created
+ * by the protocol backend as the local endpoint of the tube. This is not always desirable, as that
+ * includes even other users.
+ *
+ * Note that since every TCP connection must have an unique source address, only one simultaneous
+ * connection can be made through each tube for which this type of access control has been used.
+ */
+
+/**
+ * \fn QPair<QHostAddress, quint16> StreamTubeClient::TcpSourceAddressGenerator::nextSourceAddress(const AccountPtr &, const
+ * IncomingStreamTubeChannelPtr &)
+ *
+ * Return the source address from which connections will be allowed to the given \a tube once it has
+ * been accepted.
+ *
+ * Returning the pair (QHostAddress::Any, 0) makes the protocol backend allow connections from any
+ * address on the local computer. This can be used on a tube-by-tube basis if for some tubes its
+ * known that multiple connections need to be made, so a single source address doesn't suffice.
+ *
+ * The \a account and \a tube parameters can be inspected to make the decision; typically by looking
+ * at the tube's service type, parameters and/or initiator contact.
+ *
+ * The general pattern for implementing this method is:
+ * <ol>
+ * <li>Determine whether \a tube needs to allow multiple connections, and if so, skip source address
+ * access control completely</li>
+ * <li>Otherwise, create a socket and bind it to a free address</li>
+ * <li>Return this socket's address</li>
+ * <li>Keep the socket bound so that no other process can (accidentally or maliciously) take the
+ * address until it's used to connect to the tube when StreamTubeClient::tubeAcceptedAsTcp() is
+ * emitted for the tube</li>
+ * </ol>
+ *
+ * \param account The account from which the tube originates.
+ * \param tube The tube channel which is going to be accepted by the StreamTubeClient.
+ *
+ * \return A pair containing the host address and port allowed to connect.
+ */
+
+/**
+ * \fn StreamTubeClient::TcpSourceAddressGenerator::~TcpSourceAddressGenerator
+ *
+ * Class destructor. Protected, because StreamTubeClient never deletes a TcpSourceAddressGenerator passed
+ * to it.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT StreamTubeClient::Tube::Private : public QSharedData
+{
+ // empty placeholder for now
+};
+
+/**
+ * \class StreamTubeClient::Tube
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-client.h <TelepathyQt4/StreamTubeClient>
+ *
+ * \brief The StreamTubeClient::Tube class represents a tube being handled by the client.
+ */
+
+/**
+ * Constructs a new invalid Tube instance.
+ */
+StreamTubeClient::Tube::Tube()
+{
+ // invalid instance
+}
+
+/**
+ * Constructs a Tube instance for the given tube \a channel from the given \a account.
+ *
+ * \param account A pointer to the account the online connection of which the tube originates from.
+ * \param channel A pointer to the tube channel object.
+ */
+StreamTubeClient::Tube::Tube(
+ const AccountPtr &account,
+ const IncomingStreamTubeChannelPtr &channel)
+ : QPair<AccountPtr, IncomingStreamTubeChannelPtr>(account, channel), mPriv(new Private)
+{
+}
+
+/**
+ * Copy constructor.
+ */
+StreamTubeClient::Tube::Tube(
+ const Tube &other)
+ : QPair<AccountPtr, IncomingStreamTubeChannelPtr>(other.account(), other.channel()),
+ mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+StreamTubeClient::Tube::~Tube()
+{
+ // mPriv deleted automatically
+}
+
+/**
+ * Assignment operator.
+ */
+StreamTubeClient::Tube &StreamTubeClient::Tube::operator=(
+ const Tube &other)
+{
+ if (&other == this) {
+ return *this;
+ }
+
+ first = other.account();
+ second = other.channel();
+ mPriv = other.mPriv;
+
+ return *this;
+}
+
+/**
+ * \fn bool StreamTubeClient::Tube::isValid() const
+ *
+ * Return whether or not the tube is valid or is just the null object created using the default
+ * constructor.
+ *
+ * \return \c true if valid, \c false otherwise.
+ */
+
+/**
+ * \fn AccountPtr StreamTubeClient::Tube::account() const
+ *
+ * Return the account from which the tube originates.
+ *
+ * \return A pointer to the account object.
+ */
+
+/**
+ * \fn IncomingStreamTubeChannelPtr StreamTubeClient::Tube::channel() const
+ *
+ * Return the actual tube channel.
+ *
+ * \return A pointer to the channel.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT StreamTubeClient::Private
+{
+ Private(const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &maybeClientName,
+ bool monitorConnections,
+ bool bypassApproval)
+ : registrar(registrar),
+ handler(SimpleStreamTubeHandler::create(
+ p2pServices, roomServices, false, monitorConnections, bypassApproval)),
+ clientName(maybeClientName),
+ isRegistered(false),
+ acceptsAsTcp(false), acceptsAsUnix(false),
+ tcpGenerator(0), requireCredentials(false)
+ {
+ if (clientName.isEmpty()) {
+ clientName = QString::fromLatin1("TpQt4STubeClient_%1_%2")
+ .arg(registrar->dbusConnection().baseService()
+ .replace(QLatin1Char(':'), QLatin1Char('_'))
+ .replace(QLatin1Char('.'), QLatin1Char('_')))
+ .arg((intptr_t) this, 0, 16);
+ }
+ }
+
+ void ensureRegistered()
+ {
+ if (isRegistered) {
+ return;
+ }
+
+ debug() << "Register StreamTubeClient with name " << clientName;
+
+ if (registrar->registerClient(handler, clientName)) {
+ isRegistered = true;
+ } else {
+ warning() << "StreamTubeClient" << clientName
+ << "registration failed";
+ }
+ }
+
+ ClientRegistrarPtr registrar;
+ SharedPtr<SimpleStreamTubeHandler> handler;
+ QString clientName;
+ bool isRegistered;
+
+ bool acceptsAsTcp, acceptsAsUnix;
+ TcpSourceAddressGenerator *tcpGenerator;
+ bool requireCredentials;
+
+ QHash<StreamTubeChannelPtr, TubeWrapper *> tubes;
+};
+
+StreamTubeClient::TubeWrapper::TubeWrapper(
+ const AccountPtr &acc,
+ const IncomingStreamTubeChannelPtr &tube,
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ StreamTubeClient *parent)
+ : QObject(parent), mAcc(acc), mTube(tube), mSourceAddress(sourceAddress), mSourcePort(sourcePort)
+{
+ if (sourcePort != 0) {
+ if ((sourceAddress.protocol() == QAbstractSocket::IPv4Protocol &&
+ !tube->supportsIPv4SocketsWithSpecifiedAddress()) ||
+ (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol &&
+ !tube->supportsIPv6SocketsWithSpecifiedAddress())) {
+ debug() << "StreamTubeClient falling back to Localhost AC for tube" <<
+ tube->objectPath();
+ mSourceAddress = sourceAddress.protocol() == QAbstractSocket::IPv4Protocol ?
+ QHostAddress::Any : QHostAddress::AnyIPv6;
+ mSourcePort = 0;
+ }
+ }
+
+ connect(tube->acceptTubeAsTcpSocket(mSourceAddress, mSourcePort),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onTubeAccepted(Tp::PendingOperation*)));
+ connect(tube.data(),
+ SIGNAL(newConnection(uint)),
+ SLOT(onNewConnection(uint)));
+ connect(tube.data(),
+ SIGNAL(connectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString)));
+}
+
+StreamTubeClient::TubeWrapper::TubeWrapper(
+ const AccountPtr &acc,
+ const IncomingStreamTubeChannelPtr &tube,
+ bool requireCredentials,
+ StreamTubeClient *parent)
+ : QObject(parent), mAcc(acc), mTube(tube), mSourcePort(0)
+{
+ if (requireCredentials && !tube->supportsUnixSocketsWithCredentials()) {
+ debug() << "StreamTubeClient falling back to Localhost AC for tube" << tube->objectPath();
+ requireCredentials = false;
+ }
+
+ connect(tube->acceptTubeAsUnixSocket(requireCredentials),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onTubeAccepted(Tp::PendingOperation*)));
+ connect(tube.data(),
+ SIGNAL(newConnection(uint)),
+ SLOT(onNewConnection(uint)));
+ connect(tube.data(),
+ SIGNAL(connectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString)));
+}
+
+void StreamTubeClient::TubeWrapper::onTubeAccepted(Tp::PendingOperation *op)
+{
+ emit acceptFinished(this, qobject_cast<Tp::PendingStreamTubeConnection *>(op));
+}
+
+void StreamTubeClient::TubeWrapper::onNewConnection(uint conn)
+{
+ emit newConnection(this, conn);
+}
+
+void StreamTubeClient::TubeWrapper::onConnectionClosed(uint conn, const QString &error,
+ const QString &message)
+{
+ emit connectionClosed(this, conn, error, message);
+}
+
+/**
+ * \class StreamTubeClient
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-client.h <TelepathyQt4/StreamTubeClient>
+ *
+ * \brief The StreamTubeClient class is a Handler implementation for incoming %Stream %Tube channels,
+ * allowing an application to easily get notified about services they can connect to offered to them
+ * over Telepathy Tubes without worrying about the channel dispatching details.
+ *
+ * Telepathy Tubes is a technology for connecting arbitrary applications together through the IM
+ * network (and sometimes with direct peer-to-peer connections), such that issues like firewall/NAT
+ * traversal are automatically handled. Stream Tubes in particular offer properties similar to
+ * SOCK_STREAM sockets. The StreamTubeClient class negotiates tubes offered to us so that an
+ * application can connect such bytestream sockets of theirs to them. The StreamTubeServer class is
+ * the counterpart, offering services from a bytestream socket server to tubes requested to be
+ * initiated.
+ *
+ * Both peer-to-peer (\c TargetHandleType == \ref HandleTypeContact) and group (\c TargetHandleType
+ * == \ref HandleTypeRoom) channels are supported, and it's possible to specify the tube services to
+ * handle for each separately. There must be at least one service in total declared, as it never
+ * makes sense to handle stream tubes without considering the protocol of the service offered
+ * through them.
+ *
+ * %Connection monitoring allows fine-grained error reporting for connections made through tubes,
+ * and observing connections being made and broken even if the application code running
+ * StreamTubeClient can't easily get this information from the code actually connecting through it.
+ * Such a setting might occur e.g. when a wrapper application is developed to connect some existing
+ * "black box" networked application through TCP tubes by launching it with the appropriate command
+ * line arguments or alike for accepted tubes.
+ *
+ * Enabling connection monitoring adds a small overhead and latency to handling each incoming tube
+ * and signaling each new incoming connection over them, though, so use it only when needed.
+ *
+ * A service activated Handler can be implemented using StreamTubeClient by passing a predefined \a
+ * clientName manually to the chosen create() method, and installing Telepathy \c .client and D-Bus
+ * \c .service files declaring the implemented tube services as channel classes and a path to the
+ * executable. If this is not needed, the \a clientName can be omitted, in which case a random
+ * unique client name is generated and used instead. However, then the tube client application must
+ * already be running for remote contacts to be able to offer services to us over tubes.
+ *
+ * Whether the Handler application implemented using StreamTubeClient is service activatable or not,
+ * incoming channels will typically first be given to an Approver, if there is one for tube services
+ * corresponding to the tube in question. Only if the Approver decides that the tube communication
+ * should be allowed (usually by asking the user), or if there is no matching Approver at all, is
+ * the channel given to the actual Handler tube client. This can be overridden by setting \a
+ * bypassApproval to \c true, which skips approval for the given services completely and directs
+ * them straight to the Handler.
+ *
+ * StreamTubeClient shares Account, Connection and Channel proxies and Contact objects with the
+ * rest of the application as long as a reference to the AccountManager, ClientRegistrar, or the
+ * factories used elsewhere is passed to the create() method. A stand-alone tube client Handler can
+ * get away without passing these however, or just passing select factories to make the desired
+ * features prepared and subclasses employed for these objects for their own convenience.
+ *
+ * Whichever method is used, the ChannelFactory (perhaps indirectly) given must construct
+ * IncomingStreamTubeChannel instances or subclasses thereof for all channel classes corresponding
+ * to the tube services the client should be able to connect to. This is the default; overriding it
+ * without obeying these constraints using ChannelFactory::setSubclassForIncomingStreamTubes() or
+ * the related methods for room tubes prevents StreamTubeClient from operating correctly.
+ *
+ * \todo Coin up a small Python script or alike to easily generate the .client and .service files.
+ * (fd.o #41614)
+ */
+
+/**
+ * Create a new StreamTubeClient, which will register itself on the session bus using an internal
+ * ClientRegistrar and use the given factories.
+ *
+ * \param p2pServices Names of the tube services to accept on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to accept on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming
+ * channels if there is one.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ */
+StreamTubeClientPtr StreamTubeClient::create(
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ bool bypassApproval,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return create(
+ QDBusConnection::sessionBus(),
+ accountFactory,
+ connectionFactory,
+ channelFactory,
+ contactFactory,
+ p2pServices,
+ roomServices,
+ clientName,
+ monitorConnections,
+ bypassApproval);
+}
+
+/**
+ * Create a new StreamTubeClient, which will register itself on the given \a bus using an internal
+ * ClientRegistrar and use the given factories.
+ *
+ * The factories must all be created for the given \a bus.
+ *
+ * \param bus Connection to the bus to register on.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming
+ * channels if there is one.
+ */
+StreamTubeClientPtr StreamTubeClient::create(
+ const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ bool bypassApproval)
+{
+ return create(
+ ClientRegistrar::create(
+ bus,
+ accountFactory,
+ connectionFactory,
+ channelFactory,
+ contactFactory),
+ p2pServices,
+ roomServices,
+ clientName,
+ monitorConnections,
+ bypassApproval);
+}
+
+/**
+ * Create a new StreamTubeClient, which will register itself on the bus of and share objects with
+ * the given \a accountManager, creating an internal ClientRegistrar.
+ *
+ * \param accountManager A pointer to the account manager to link up with.
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming
+ * channels if there is one.
+ */
+StreamTubeClientPtr StreamTubeClient::create(
+ const AccountManagerPtr &accountManager,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ bool bypassApproval)
+{
+ return create(
+ accountManager->dbusConnection(),
+ accountManager->accountFactory(),
+ accountManager->connectionFactory(),
+ accountManager->channelFactory(),
+ accountManager->contactFactory(),
+ p2pServices,
+ roomServices,
+ clientName,
+ monitorConnections,
+ bypassApproval);
+}
+
+/**
+ * Create a new StreamTubeClient, which will register itself on the bus of and using the given
+ * client \a registrar, and share objects with it.
+ *
+ * \param registrar The client registrar to use.
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming
+ * channels if there is one.
+ */
+StreamTubeClientPtr StreamTubeClient::create(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ bool bypassApproval)
+{
+ if (p2pServices.isEmpty() && roomServices.isEmpty()) {
+ warning() << "Tried to create a StreamTubeClient with no services, returning NULL";
+ return StreamTubeClientPtr();
+ }
+
+ return StreamTubeClientPtr(
+ new StreamTubeClient(registrar, p2pServices, roomServices, clientName,
+ monitorConnections, bypassApproval));
+}
+
+StreamTubeClient::StreamTubeClient(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ bool bypassApproval)
+ : mPriv(new Private(registrar, p2pServices, roomServices, clientName, monitorConnections, bypassApproval))
+{
+ connect(mPriv->handler.data(),
+ SIGNAL(invokedForTube(
+ Tp::AccountPtr,
+ Tp::StreamTubeChannelPtr,
+ QDateTime,
+ Tp::ChannelRequestHints)),
+ SLOT(onInvokedForTube(
+ Tp::AccountPtr,
+ Tp::StreamTubeChannelPtr,
+ QDateTime,
+ Tp::ChannelRequestHints)));
+}
+
+/**
+ * Class destructor.
+ */
+StreamTubeClient::~StreamTubeClient()
+{
+ if (isRegistered()) {
+ mPriv->registrar->unregisterClient(mPriv->handler);
+ }
+
+ delete mPriv;
+}
+
+/**
+ * Return the client registrar used by the client to register itself as a Telepathy channel Handler
+ * %Client.
+ *
+ * This is the registrar originally passed to
+ * create(const ClientRegistrarPtr &, const QStringList &, const QStringList &, const QString &, bool, bool)
+ * if that was used, and an internally constructed one otherwise. In any case, it can be used to
+ * e.g. register further clients, just like any other ClientRegistrar.
+ *
+ * \return A pointer to the registrar.
+ */
+ClientRegistrarPtr StreamTubeClient::registrar() const
+{
+ return mPriv->registrar;
+}
+
+/**
+ * Return the Telepathy %Client name of the client.
+ *
+ * \return The name, without the \c org.freedesktop.Telepathy.Client. prefix of the full D-Bus service name.
+ */
+QString StreamTubeClient::clientName() const
+{
+ return mPriv->clientName;
+}
+
+/**
+ * Return whether the client has been successfully registered or not.
+ *
+ * Registration is attempted, at the latest, when the client is first set to accept incoming tubes,
+ * either as TCP sockets (setToAcceptAsTcp()) or Unix ones (setToAcceptAsUnix()). It can fail e.g.
+ * because the connection to the bus has failed, or a predefined \a clientName has been passed to
+ * create(), and a %Client with the same name is already registered. Typically, failure registering
+ * would be a fatal error for a stand-alone tube handler, but only a warning event for an
+ * application serving other purposes. In any case, a high-quality user of the API will check the
+ * return value of this accessor after choosing the desired address family.
+ *
+ * \return \c true if the client has been successfully registered, \c false if not.
+ */
+bool StreamTubeClient::isRegistered() const
+{
+ return mPriv->isRegistered;
+}
+
+/**
+ * Return whether connection monitoring is enabled on this client.
+ *
+ * For technical reasons, connection monitoring can't be enabled when the client is already running,
+ * so there is no corresponding setter method. It has to be enabled by passing \c true as the \a
+ * monitorConnections parameter to the create() method.
+ *
+ * If connection monitoring isn't enabled, newConnection() and connectionClosed() won't be
+ * emitted and connections() won't be populated.
+ *
+ * \return \c true if monitoring is enabled, \c false if not.
+ */
+bool StreamTubeClient::monitorsConnections() const
+{
+ return mPriv->handler->monitorsConnections();
+}
+
+/**
+ * Return whether the client is currently set to accept incoming tubes as TCP sockets.
+ *
+ * \return \c true if the client will accept tubes as TCP sockets, \c false if it will accept them
+ * as Unix ones or hasn't been set to accept at all yet.
+ */
+bool StreamTubeClient::acceptsAsTcp() const
+{
+ return mPriv->acceptsAsTcp;
+}
+
+/**
+ * Return the TCP source address generator, if any, set by setToAcceptAsTcp() previously.
+ *
+ * \return A pointer to the generator instance.
+ */
+StreamTubeClient::TcpSourceAddressGenerator *StreamTubeClient::tcpGenerator() const
+{
+ if (!acceptsAsTcp()) {
+ warning() << "StreamTubeClient::tcpGenerator() used, but not accepting as TCP, returning 0";
+ return 0;
+ }
+
+ return mPriv->tcpGenerator;
+}
+
+/**
+ * Return whether the client is currently set to accept incoming tubes as Unix sockets.
+ *
+ * \return \c true if the client will accept tubes as Unix sockets, \c false if it will accept them
+ * as TCP ones or hasn't been set to accept at all yet.
+ */
+bool StreamTubeClient::acceptsAsUnix() const
+{
+ return mPriv->acceptsAsUnix;
+}
+
+/**
+ * Set the client to accept tubes received to handle in the future in a fashion which will yield a
+ * TCP socket as the local endpoint to connect to.
+ *
+ * A source address generator can optionally be set. If non-null, it will be invoked for each new
+ * tube received to handle and an attempt is made to restrict connections to the tube's local socket
+ * endpoint to those from that source address.
+ *
+ * However, if the protocol backend doesn't actually support source address based access control,
+ * tubeAcceptedAsTcp() will be emitted with QHostAddress::Any as the allowed source address to
+ * signal that it doesn't matter where we connect from, but more importantly, that anybody else on
+ * the same host could have, and can, connect to the tube. The tube can be closed at this point if
+ * this would be unacceptable security-wise. To totally prevent the tube from being accepted in the
+ * first place, one can close it already when tubeOffered() is emitted for it - support for the
+ * needed security mechanism can be queried using its supportsIPv4SocketsWithSpecifiedAddress()
+ * accessor.
+ *
+ * The handler is registered on the bus at the latest when this method or setToAcceptAsUnix() is
+ * called for the first time, so one should check the return value of isRegistered() at that point
+ * to verify that was successful.
+ *
+ * \param generator A pointer to the source address generator to use, or 0 to allow all
+ * connections from the local host.
+ *
+ * \todo Make it possible to set the tube client to auto-close tubes if the desired access control
+ * level is not achieved, as an alternative to the current best-effort behavior.
+ */
+void StreamTubeClient::setToAcceptAsTcp(TcpSourceAddressGenerator *generator)
+{
+ mPriv->tcpGenerator = generator;
+ mPriv->acceptsAsTcp = true;
+ mPriv->acceptsAsUnix = false;
+
+ mPriv->ensureRegistered();
+}
+
+/**
+ * Set the client to accept tubes received to handle in the future in a fashion which will yield a
+ * Unix socket as the local endpoint to connect to.
+ *
+ * If that doesn't cause problems for the payload protocol, it's possible to increase security by
+ * restricting the processes allowed to connect to the local endpoint socket to those from the same
+ * user ID as the protocol backend is running as by setting \a requireCredentials to \c true. This
+ * requires transmitting a single byte, signaled as the \a credentialByte parameter to the
+ * tubeAcceptedAsUnix() signal, in a \c SCM_CREDS or SCM_CREDENTIALS message, whichever is supported
+ * by the platform, as the first thing after having connected to the socket. Even if the platform
+ * doesn't implement either concept, the byte must still be sent.
+ *
+ * However, not all protocol backends support the credential passing based access control on all the
+ * platforms they can run on. If a tube is offered through such a backend, tubeAcceptedAsUnix() will
+ * be emitted with \a requiresCredentials set to \c false, to signal that a credential byte should
+ * NOT be sent for that tube, and that any local process can or could have connected to the tube
+ * already. The tube can be closed at this point if this would be unacceptable security-wise. To
+ * totally prevent the tube from being accepted in the first place, one can close it already when
+ * tubeOffered() is emitted for it - support for the needed security mechanism can be queried using
+ * its supportsIPv4SocketsWithSpecifiedAddress()
+ * accessor.
+ *
+ * The handler is registered on the bus at the latest when this method or setToAcceptAsTcp() is
+ * called for the first time, so one should check the return value of isRegistered() at that point
+ * to verify that was successful.
+ *
+ * \param requireCredentials \c true to try and restrict connecting by UID, \c false to allow all
+ * connections.
+ *
+ * \todo Make it possible to set the tube client to auto-close tubes if the desired access control
+ * level is not achieved, as an alternative to the current best-effort behavior.
+ */
+void StreamTubeClient::setToAcceptAsUnix(bool requireCredentials)
+{
+ mPriv->tcpGenerator = 0;
+ mPriv->acceptsAsTcp = false;
+ mPriv->acceptsAsUnix = true;
+ mPriv->requireCredentials = requireCredentials;
+
+ mPriv->ensureRegistered();
+}
+
+/**
+ * Return the tubes currently handled by the client.
+ *
+ * \return A list of Tube structures containing pointers to the account and tube channel for each
+ * tube.
+ */
+QList<StreamTubeClient::Tube> StreamTubeClient::tubes() const
+{
+ QList<Tube> tubes;
+
+ foreach (TubeWrapper *wrapper, mPriv->tubes.values()) {
+ tubes.push_back(Tube(wrapper->mAcc, wrapper->mTube));
+ }
+
+ return tubes;
+}
+
+/**
+ * Return the ongoing connections established through tubes signaled by this client.
+ *
+ * The returned mapping has for each Tube a structure containing pointers to the account and tube
+ * channel objects as keys, with the integer identifiers for the current connections on them as the
+ * values. The IDs are unique amongst the connections active on a single tube at any given time, but
+ * not globally.
+ *
+ * This is effectively a state recovery accessor corresponding to the change notification signals
+ * newConnection() and connectionClosed().
+ *
+ * The mapping is only populated if connection monitoring was requested when creating the client (so
+ * monitorsConnections() returns \c true).
+ *
+ * \return The connections in a mapping with Tube structures containing pointers to the account and
+ * channel objects for each tube as keys, and the sets of numerical IDs as values.
+ */
+QHash<StreamTubeClient::Tube, QSet<uint> > StreamTubeClient::connections() const
+{
+ QHash<Tube, QSet<uint> > conns;
+ if (!monitorsConnections()) {
+ warning() << "StreamTubeClient::connections() used, but connection monitoring is disabled";
+ return conns;
+ }
+
+ foreach (const Tube &tube, tubes()) {
+ if (!tube.channel()->isValid()) {
+ // The tube has been invalidated, so skip it to avoid warnings
+ // We're going to get rid of the wrapper in the next mainloop iteration when we get the
+ // invalidation signal
+ continue;
+ }
+
+ QSet<uint> tubeConns = QSet<uint>::fromList(tube.channel()->connections());
+ if (!tubeConns.empty()) {
+ conns.insert(tube, tubeConns);
+ }
+ }
+
+ return conns;
+}
+
+void StreamTubeClient::onInvokedForTube(
+ const AccountPtr &acc,
+ const StreamTubeChannelPtr &tube,
+ const QDateTime &time,
+ const ChannelRequestHints &hints)
+{
+ Q_ASSERT(isRegistered()); // our SSTH shouldn't be receiving any channels unless it's registered
+ Q_ASSERT(!tube->isRequested());
+ Q_ASSERT(tube->isValid()); // SSTH won't emit invalid tubes
+
+ if (mPriv->tubes.contains(tube)) {
+ debug() << "Ignoring StreamTubeClient reinvocation for tube" << tube->objectPath();
+ return;
+ }
+
+ IncomingStreamTubeChannelPtr incoming = IncomingStreamTubeChannelPtr::qObjectCast(tube);
+
+ if (!incoming) {
+ warning() << "The ChannelFactory used by StreamTubeClient must construct" <<
+ "IncomingStreamTubeChannel subclasses for Requested=false StreamTubes";
+ tube->requestClose();
+ return;
+ }
+
+ TubeWrapper *wrapper = 0;
+
+ if (mPriv->acceptsAsTcp) {
+ QPair<QHostAddress, quint16> srcAddr =
+ qMakePair(QHostAddress(QHostAddress::Any), quint16(0));
+
+ if (mPriv->tcpGenerator) {
+ srcAddr = mPriv->tcpGenerator->nextSourceAddress(acc, incoming);
+ }
+
+ wrapper = new TubeWrapper(acc, incoming, srcAddr.first, srcAddr.second, this);
+ } else {
+ Q_ASSERT(mPriv->acceptsAsUnix); // we should only be registered when we're set to accept as either TCP or Unix
+ wrapper = new TubeWrapper(acc, incoming, mPriv->requireCredentials, this);
+ }
+
+ connect(wrapper,
+ SIGNAL(acceptFinished(TubeWrapper*,Tp::PendingStreamTubeConnection*)),
+ SLOT(onAcceptFinished(TubeWrapper*,Tp::PendingStreamTubeConnection*)));
+ connect(tube.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ if (monitorsConnections()) {
+ connect(wrapper,
+ SIGNAL(newConnection(TubeWrapper*,uint)),
+ SLOT(onNewConnection(TubeWrapper*,uint)));
+ connect(wrapper,
+ SIGNAL(connectionClosed(TubeWrapper*,uint,QString,QString)),
+ SLOT(onConnectionClosed(TubeWrapper*,uint,QString,QString)));
+ }
+
+ mPriv->tubes.insert(tube, wrapper);
+
+ emit tubeOffered(acc, incoming);
+}
+
+void StreamTubeClient::onAcceptFinished(TubeWrapper *wrapper, PendingStreamTubeConnection *conn)
+{
+ Q_ASSERT(wrapper != NULL);
+ Q_ASSERT(conn != NULL);
+
+ if (!mPriv->tubes.contains(wrapper->mTube)) {
+ debug() << "StreamTubeClient ignoring Accept result for invalidated tube"
+ << wrapper->mTube->objectPath();
+ return;
+ }
+
+ if (conn->isError()) {
+ warning() << "StreamTubeClient couldn't accept tube" << wrapper->mTube->objectPath() << '-'
+ << conn->errorName() << ':' << conn->errorMessage();
+
+ if (wrapper->mTube->isValid()) {
+ wrapper->mTube->requestClose();
+ }
+
+ wrapper->mTube->disconnect(this);
+ emit tubeClosed(wrapper->mAcc, wrapper->mTube, conn->errorName(), conn->errorMessage());
+ mPriv->tubes.remove(wrapper->mTube);
+ wrapper->deleteLater();
+ return;
+ }
+
+ debug() << "StreamTubeClient accepted tube" << wrapper->mTube->objectPath();
+
+ if (conn->addressType() == SocketAddressTypeIPv4
+ || conn->addressType() == SocketAddressTypeIPv6) {
+ QPair<QHostAddress, quint16> addr = conn->ipAddress();
+ emit tubeAcceptedAsTcp(addr.first, addr.second, wrapper->mSourceAddress,
+ wrapper->mSourcePort, wrapper->mAcc, wrapper->mTube);
+ } else {
+ emit tubeAcceptedAsUnix(conn->localAddress(), conn->requiresCredentials(),
+ conn->credentialByte(), wrapper->mAcc, wrapper->mTube);
+ }
+}
+
+void StreamTubeClient::onTubeInvalidated(Tp::DBusProxy *proxy, const QString &error,
+ const QString &message)
+{
+ StreamTubeChannelPtr tube(qobject_cast<StreamTubeChannel *>(proxy));
+ Q_ASSERT(!tube.isNull());
+
+ TubeWrapper *wrapper = mPriv->tubes.value(tube);
+ if (!wrapper) {
+ // Accept finish with error already removed it
+ return;
+ }
+
+ debug() << "Client StreamTube" << tube->objectPath() << "invalidated - " << error << ':'
+ << message;
+
+ emit tubeClosed(wrapper->mAcc, wrapper->mTube, error, message);
+ mPriv->tubes.remove(tube);
+ delete wrapper;
+}
+
+void StreamTubeClient::onNewConnection(
+ TubeWrapper *wrapper,
+ uint conn)
+{
+ Q_ASSERT(monitorsConnections());
+ emit newConnection(wrapper->mAcc, wrapper->mTube, conn);
+}
+
+void StreamTubeClient::onConnectionClosed(
+ TubeWrapper *wrapper,
+ uint conn,
+ const QString &error,
+ const QString &message)
+{
+ Q_ASSERT(monitorsConnections());
+ emit connectionClosed(wrapper->mAcc, wrapper->mTube, conn, error, message);
+}
+
+/**
+ * \fn void StreamTubeClient::tubeOffered(const AccountPtr &account, const
+ * IncomingStreamTubeChannelPtr &tube)
+ *
+ * Emitted when one of the services we're interested in connecting to has been offered by us as a
+ * tube, which we've began handling.
+ *
+ * This is emitted before invoking the TcpSourceAddressGenerator, if any, for the tube.
+ *
+ * \param account A pointer to the account through which the tube was offered.
+ * \param tube A pointer to the actual tube channel.
+ */
+
+/**
+ * \fn void StreamTubeClient::tubeAcceptedAsTcp(const QHostAddress &listenAddress, quint16
+ * listenPort, const QHostAddress &sourceAddress, quint16 sourcePort, const AccountPtr &account,
+ * const IncomingStreamTubeChannelPtr &tube)
+ *
+ * Emitted when a tube offered to us (previously announced with tubeOffered()) has been successfully
+ * accepted and a TCP socket established as the local endpoint, as specified by setToAcceptAsTcp().
+ *
+ * The allowed source address and port are signaled here if there was a TcpSourceAddressGenerator set
+ * at the time of accepting this tube, it yielded a non-zero address, and the protocol backend
+ * supports source address based access control. This enables the application to use the correct one
+ * of the sockets it currently has bound to the generated addresses.
+ *
+ * \param listenAddress The listen address of the local endpoint socket.
+ * \param listenPort The listen port of the local endpoint socket.
+ * \param sourceAddress The host address allowed to connect to the tube, or QHostAddress::Any if
+ * source address based access control is not in use.
+ * \param sourcePort The port from which connections are allowed to the tube, or 0 if source address
+ * based access control is not in use.
+ * \param account A pointer to the account object through which the tube was offered.
+ * \param tube A pointer to the actual tube channel object.
+ */
+
+// Explicitly specifying Tp:: for this signal's argument types works around a doxygen bug causing it
+// to confuse the doc here being for StreamTubeServer::tubeClosed :/
+/**
+ * \fn void StreamTubeClient::tubeClosed(const Tp::AccountPtr &account, const
+ * Tp::IncomingStreamTubeChannelPtr &tube, const QString &error, const QString &message)
+ *
+ * Emitted when a tube we've been handling (previously announced with tubeOffered()) has
+ * encountered an error or has otherwise been closed from further communication.
+ *
+ * \param account A pointer to the account through which the tube was offered.
+ * \param tube A pointer to the actual tube channel.
+ * \param error The D-Bus error name corresponding to the reason for the closure.
+ * \param message A freeform debug message associated with the error.
+ */
+
+/**
+ * \fn void StreamTubeClient::tubeAcceptedAsUnix(const QHostAddress &listenAddress, quint16
+ * listenPort, const QHostAddress &sourceAddress, quint16 sourcePort, const AccountPtr &account,
+ * const IncomingStreamTubeChannelPtr &tube)
+ *
+ * Emitted when a tube offered to us (previously announced with tubeOffered()) has been successfully
+ * accepted and a Unix socket established as the local endpoint, as specified by setToAcceptAsUnix().
+ *
+ * The credential byte which should be sent after connecting to the tube (in a SCM_CREDENTIALS or
+ * SCM_CREDS message if supported by the platform) will be signaled here if the client was set to
+ * attempt requiring credentials at the time of accepting this tube, and the protocol backend
+ * supports credential passing based access control. Otherwise, \a requiresCredentials will be false
+ * and no byte or associated out-of-band credentials metadata should be sent.
+ *
+ * \param listenAddress The listen address of the local endpoint socket.
+ * \param requiresCredentials \c true if \a credentialByte should be sent after connecting to the
+ * socket, \c false if not.
+ * \param credentialByte The byte to send if \a requiresCredentials is \c true.
+ * \param account A pointer to the account object through which the tube was offered.
+ * \param tube A pointer to the actual tube channel object.
+ */
+
+/**
+ * \fn void StreamTubeClient::newConnection(const AccountPtr &account, const
+ * IncomingStreamTubeChannelPtr &tube, uint connectionId)
+ *
+ * Emitted when a new connection has been made to the local endpoint socket for \a tube.
+ *
+ * This can be used to later associate connection errors reported by connectionClosed() with the
+ * corresponding application sockets. However, establishing the association generally requires
+ * connecting only one socket at a time, waiting for newConnection() to be emitted, and only then
+ * proceeding, as there is no identification for the connections unlike the incoming connections in
+ * StreamTubeServer.
+ *
+ * Note that the connection IDs are only unique within a given tube, so identification of the tube
+ * channel must also be recorded together with the ID to establish global uniqueness. Even then, the
+ * a connection ID can be reused after the previous connection identified by it having been
+ * signaled as closed with connectionClosed().
+ *
+ * This is only emitted if connection monitoring was enabled when creating the StreamTubeClient.
+ *
+ * \param account A pointer to the account through which the tube was offered.
+ * \param tube A pointer to the tube channel through which the connection has been made.
+ * \param connectionId The integer ID of the new connection.
+ */
+
+/**
+ * \fn void StreamTubeClient::connectionClosed(const AccountPtr &account, const
+ * IncomingStreamTubeChannelPtr &tube, uint connectionId, const QString &error, const
+ * QString &message)
+ *
+ * Emitted when a connection (previously announced with newConnection()) through one of our
+ * handled tubes has been closed due to an error or by a graceful disconnect (in which case the
+ * error is ::TP_QT4_ERROR_CANCELLED).
+ *
+ * This is only emitted if connection monitoring was enabled when creating the StreamTubeClient.
+ *
+ * \param account A pointer to the account through which the tube was offered.
+ * \param tube A pointer to the tube channel through which the connection had been made.
+ * \param connectionId The integer ID of the connection closed.
+ * \param error The D-Bus error name corresponding to the reason for the closure.
+ * \param message A freeform debug message associated with the error.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/stream-tube-client.h b/qt4/TelepathyQt4/stream-tube-client.h
new file mode 100644
index 000000000..297bcfb57
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-client.h
@@ -0,0 +1,217 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_stream_tube_client_h_HEADER_GUARD_
+#define _TelepathyQt4_stream_tube_client_h_HEADER_GUARD_
+
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/RefCounted>
+#include <TelepathyQt4/Types>
+
+class QHostAddress;
+
+namespace Tp
+{
+
+class PendingStreamTubeConnection;
+
+class TELEPATHY_QT4_EXPORT StreamTubeClient : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StreamTubeClient)
+
+ class TubeWrapper;
+
+public:
+
+ class TcpSourceAddressGenerator
+ {
+ public:
+ virtual QPair<QHostAddress, quint16>
+ nextSourceAddress(const AccountPtr &account, const IncomingStreamTubeChannelPtr &tube) = 0;
+
+ protected:
+ virtual ~TcpSourceAddressGenerator() {}
+ };
+
+ class Tube : public QPair<AccountPtr, IncomingStreamTubeChannelPtr>
+ {
+ public:
+ Tube();
+ Tube(const AccountPtr &account, const IncomingStreamTubeChannelPtr &channel);
+ Tube(const Tube &other);
+ ~Tube();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ Tube &operator=(const Tube &other);
+
+ const AccountPtr &account() const
+ {
+ return first;
+ }
+
+ const IncomingStreamTubeChannelPtr &channel() const
+ {
+ return second;
+ }
+
+ private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ static StreamTubeClientPtr create(
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false,
+ bool bypassApproval = false,
+ const AccountFactoryConstPtr &accountFactory =
+ AccountFactory::create(QDBusConnection::sessionBus()),
+ const ConnectionFactoryConstPtr &connectionFactory =
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ const ChannelFactoryConstPtr &channelFactory =
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+
+ static StreamTubeClientPtr create(
+ const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false,
+ bool bypassApproval = false);
+
+ static StreamTubeClientPtr create(
+ const AccountManagerPtr &accountManager,
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false,
+ bool bypassApproval = false);
+
+ static StreamTubeClientPtr create(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false,
+ bool bypassApproval = false);
+
+ virtual ~StreamTubeClient();
+
+ ClientRegistrarPtr registrar() const;
+ QString clientName() const;
+ bool isRegistered() const;
+ bool monitorsConnections() const;
+
+ bool acceptsAsTcp() const;
+ TcpSourceAddressGenerator *tcpGenerator() const;
+ bool acceptsAsUnix() const;
+
+ void setToAcceptAsTcp(TcpSourceAddressGenerator *generator = 0);
+ void setToAcceptAsUnix(bool requireCredentials = false);
+
+ QList<Tube> tubes() const;
+ QHash<Tube, QSet<uint> > connections() const;
+
+Q_SIGNALS:
+ void tubeOffered(
+ const Tp::AccountPtr &account,
+ const Tp::IncomingStreamTubeChannelPtr &tube);
+ void tubeClosed(
+ const Tp::AccountPtr &account,
+ const Tp::IncomingStreamTubeChannelPtr &tube,
+ const QString &error,
+ const QString &message);
+
+ void tubeAcceptedAsTcp(
+ const QHostAddress &listenAddress,
+ quint16 listenPort,
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ const Tp::AccountPtr &account,
+ const Tp::IncomingStreamTubeChannelPtr &tube);
+ void tubeAcceptedAsUnix(
+ const QString &listenAddress,
+ bool requiresCredentials,
+ uchar credentialByte,
+ const Tp::AccountPtr &account,
+ const Tp::IncomingStreamTubeChannelPtr &tube);
+
+ void newConnection(
+ const Tp::AccountPtr &account,
+ const Tp::IncomingStreamTubeChannelPtr &tube,
+ uint connectionId);
+ void connectionClosed(
+ const Tp::AccountPtr &account,
+ const Tp::IncomingStreamTubeChannelPtr &tube,
+ uint connectionId,
+ const QString &error,
+ const QString &message);
+
+private Q_SLOTS:
+
+ TELEPATHY_QT4_NO_EXPORT void onInvokedForTube(
+ const Tp::AccountPtr &account,
+ const Tp::StreamTubeChannelPtr &tube,
+ const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &requestHints);
+
+ TELEPATHY_QT4_NO_EXPORT void onAcceptFinished(TubeWrapper *, Tp::PendingStreamTubeConnection *);
+ TELEPATHY_QT4_NO_EXPORT void onTubeInvalidated(Tp::DBusProxy *, const QString &, const QString &);
+
+ TELEPATHY_QT4_NO_EXPORT void onNewConnection(
+ TubeWrapper *wrapper,
+ uint conn);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionClosed(
+ TubeWrapper *wrapper,
+ uint conn,
+ const QString &error,
+ const QString &message);
+
+private:
+ TELEPATHY_QT4_NO_EXPORT StreamTubeClient(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ bool bypassApproval);
+
+ struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/stream-tube-server-internal.h b/qt4/TelepathyQt4/stream-tube-server-internal.h
new file mode 100644
index 000000000..8119a54b4
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-server-internal.h
@@ -0,0 +1,56 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/StreamTubeServer>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_NO_EXPORT StreamTubeServer::TubeWrapper :
+ public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(TubeWrapper)
+
+public:
+ TubeWrapper(const AccountPtr &acc, const OutgoingStreamTubeChannelPtr &tube,
+ const QHostAddress &exportedAddr, quint16 exportedPort, const QVariantMap &params,
+ StreamTubeServer *parent);
+ ~TubeWrapper() { }
+
+ AccountPtr mAcc;
+ OutgoingStreamTubeChannelPtr mTube;
+
+Q_SIGNALS:
+ void offerFinished(TubeWrapper *wrapper, Tp::PendingOperation *op);
+ void newConnection(TubeWrapper *wrapper, uint conn);
+ void connectionClosed(TubeWrapper *wrapper, uint conn, const QString &error,
+ const QString &message);
+
+private Q_SLOTS:
+ void onTubeOffered(Tp::PendingOperation *);
+ void onNewConnection(uint);
+ void onConnectionClosed(uint, const QString &, const QString &);
+};
+
+} // Tp
diff --git a/qt4/TelepathyQt4/stream-tube-server.cpp b/qt4/TelepathyQt4/stream-tube-server.cpp
new file mode 100644
index 000000000..2132750c1
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-server.cpp
@@ -0,0 +1,1134 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/StreamTubeServer>
+#include "TelepathyQt4/stream-tube-server-internal.h"
+
+#include "TelepathyQt4/_gen/stream-tube-server.moc.hpp"
+#include "TelepathyQt4/_gen/stream-tube-server-internal.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+#include "TelepathyQt4/simple-stream-tube-handler.h"
+
+#include <QScopedPointer>
+#include <QSharedData>
+#include <QTcpServer>
+
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/StreamTubeChannel>
+
+namespace Tp
+{
+
+/**
+ * \class StreamTubeServer::ParametersGenerator
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-server.h <TelepathyQt4/StreamTubeServer>
+ *
+ * \brief The StreamTubeServer::ParametersGenerator abstract interface allows sending a different
+ * set of parameters with each tube offer.
+ *
+ * %Tube parameters are arbitrary data sent with the tube offer, which can be retrieved in the
+ * receiving end with IncomingStreamTubeChannel::parameters(). They can be used to transfer
+ * e.g. session identification information, authentication credentials or alike, for bootstrapping
+ * the protocol used for communicating over the tube.
+ *
+ * For usecases where the parameters don't need to change between each tube, just passing a fixed
+ * set of parameters to a suitable StreamTubeServer::exportTcpSocket() overload is usually more
+ * convenient than implementing a ParametersGenerator. Note that StreamTubeServer::exportTcpSocket()
+ * can be called multiple times to change the parameters for future tubes when e.g. configuration
+ * settings have been changed, so a ParametersGenerator only needs to be implemented if each and
+ * every tube must have a different set of parameters.
+ */
+
+/**
+ * \fn QVariantMap StreamTubeServer::ParametersGenerator::nextParameters(const AccountPtr &, const
+ * OutgoingStreamTubeChannelPtr &, const ChannelRequestHints &)
+ *
+ * Return the parameters to send when offering the given \a tube.
+ *
+ * \param account The account from which the tube originates.
+ * \param tube The tube channel which is going to be offered by the StreamTubeServer.
+ * \param hints The hints associated with the request that led to the creation of this tube, if any.
+ *
+ * \return Parameters to send with the offer, or an empty QVariantMap if none are needed for this
+ * tube.
+ */
+
+/**
+ * \fn StreamTubeServer::ParametersGenerator::~ParametersGenerator
+ *
+ * Class destructor. Protected, because StreamTubeServer never deletes a ParametersGenerator passed
+ * to it.
+ */
+
+class TELEPATHY_QT4_NO_EXPORT FixedParametersGenerator : public StreamTubeServer::ParametersGenerator
+{
+public:
+
+ FixedParametersGenerator(const QVariantMap &params) : mParams(params) {}
+
+ QVariantMap nextParameters(const AccountPtr &, const OutgoingStreamTubeChannelPtr &,
+ const ChannelRequestHints &)
+ {
+ return mParams;
+ }
+
+private:
+
+ QVariantMap mParams;
+};
+
+struct TELEPATHY_QT4_NO_EXPORT StreamTubeServer::RemoteContact::Private : public QSharedData
+{
+ // empty placeholder for now
+};
+
+/**
+ * \class StreamTubeServer::RemoteContact
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-server.h <TelepathyQt4/StreamTubeServer>
+ *
+ * \brief The StreamTubeServer::RemoteContact class represents a contact from which a socket
+ * connection to our exported socket originates.
+ */
+
+/**
+ * Constructs a new invalid RemoteContact instance.
+ */
+StreamTubeServer::RemoteContact::RemoteContact()
+{
+ // invalid instance
+}
+
+/**
+ * Constructs a new RemoteContact for the given \a contact object from the given \a account.
+ *
+ * \param account A pointer to the account which this contact can be reached through.
+ * \param contact A pointer to the contact object.
+ */
+StreamTubeServer::RemoteContact::RemoteContact(
+ const AccountPtr &account,
+ const ContactPtr &contact)
+ : QPair<AccountPtr, ContactPtr>(account, contact), mPriv(new Private)
+{
+}
+
+/**
+ * Copy constructor.
+ */
+StreamTubeServer::RemoteContact::RemoteContact(
+ const RemoteContact &other)
+ : QPair<AccountPtr, ContactPtr>(other.account(), other.contact()), mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+StreamTubeServer::RemoteContact::~RemoteContact()
+{
+ // mPriv deleted automatically
+}
+
+/**
+ * Assignment operator.
+ */
+StreamTubeServer::RemoteContact &StreamTubeServer::RemoteContact::operator=(
+ const RemoteContact &other)
+{
+ if (&other == this) {
+ return *this;
+ }
+
+ first = other.account();
+ second = other.contact();
+ mPriv = other.mPriv;
+
+ return *this;
+}
+
+/**
+ * \fn bool StreamTubeServer::RemoteContact::isValid() const
+ *
+ * Return whether or not the contact is valid or is just the null object created using the default
+ * constructor.
+ *
+ * \return \c true if valid, \c false otherwise.
+ */
+
+/**
+ * \fn AccountPtr StreamTubeServer::RemoteContact::account() const
+ *
+ * Return the account through which the contact can be reached.
+ *
+ * \return A pointer to the account object.
+ */
+
+/**
+ * \fn ContactPtr StreamTubeServer::RemoteContact::contact() const
+ *
+ * Return the actual contact object.
+ *
+ * \return A pointer to the object.
+ */
+
+struct TELEPATHY_QT4_NO_EXPORT StreamTubeServer::Tube::Private : public QSharedData
+{
+ // empty placeholder for now
+};
+
+/**
+ * \class StreamTubeServer::Tube
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-server.h <TelepathyQt4/StreamTubeServer>
+ *
+ * \brief The StreamTubeServer::Tube class represents a tube being handled by the server.
+ */
+
+/**
+ * Constructs a new invalid Tube instance.
+ */
+StreamTubeServer::Tube::Tube()
+{
+ // invalid instance
+}
+
+/**
+ * Constructs a Tube instance for the given tube \a channel originating from the given \a account.
+ *
+ * \param account A pointer to the account object.
+ * \param channel A pointer to the tube channel object.
+ */
+StreamTubeServer::Tube::Tube(
+ const AccountPtr &account,
+ const OutgoingStreamTubeChannelPtr &channel)
+ : QPair<AccountPtr, OutgoingStreamTubeChannelPtr>(account, channel), mPriv(new Private)
+{
+}
+
+/**
+ * Copy constructor.
+ */
+StreamTubeServer::Tube::Tube(
+ const Tube &other)
+ : QPair<AccountPtr, OutgoingStreamTubeChannelPtr>(other.account(), other.channel()),
+ mPriv(other.mPriv)
+{
+}
+
+/**
+ * Class destructor.
+ */
+StreamTubeServer::Tube::~Tube()
+{
+ // mPriv deleted automatically
+}
+
+/**
+ * Assignment operator.
+ */
+StreamTubeServer::Tube &StreamTubeServer::Tube::operator=(
+ const Tube &other)
+{
+ if (&other == this) {
+ return *this;
+ }
+
+ first = other.account();
+ second = other.channel();
+ mPriv = other.mPriv;
+
+ return *this;
+}
+
+/**
+ * \fn bool StreamTubeServer::Tube::isValid() const
+ *
+ * Return whether or not the tube is valid or is just the null object created using the default
+ * constructor.
+ *
+ * \return \c true if valid, \c false otherwise.
+ */
+
+/**
+ * \fn AccountPtr StreamTubeServer::Tube::account() const
+ *
+ * Return the account from which the tube originates.
+ *
+ * \return A pointer to the account object.
+ */
+
+/**
+ * \fn OutgoingStreamTubeChannelPtr StreamTubeServer::Tube::channel() const
+ *
+ * Return the actual tube channel.
+ *
+ * \return A pointer to the channel.
+ */
+
+struct StreamTubeServer::Private
+{
+ Private(const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &maybeClientName,
+ bool monitorConnections)
+ : registrar(registrar),
+ handler(SimpleStreamTubeHandler::create(p2pServices, roomServices, true, monitorConnections)),
+ clientName(maybeClientName),
+ isRegistered(false),
+ exportedPort(0),
+ generator(0)
+ {
+ if (clientName.isEmpty()) {
+ clientName = QString::fromLatin1("TpQt4STubeServer_%1_%2")
+ .arg(registrar->dbusConnection().baseService()
+ .replace(QLatin1Char(':'), QLatin1Char('_'))
+ .replace(QLatin1Char('.'), QLatin1Char('_')))
+ .arg((intptr_t) this, 0, 16);
+ }
+ }
+
+ void ensureRegistered()
+ {
+ if (isRegistered) {
+ return;
+ }
+
+ debug() << "Register StreamTubeServer with name " << clientName;
+
+ if (registrar->registerClient(handler, clientName)) {
+ isRegistered = true;
+ } else {
+ warning() << "StreamTubeServer" << clientName
+ << "registration failed";
+ }
+ }
+
+ ClientRegistrarPtr registrar;
+ SharedPtr<SimpleStreamTubeHandler> handler;
+ QString clientName;
+ bool isRegistered;
+
+ QHostAddress exportedAddr;
+ quint16 exportedPort;
+ ParametersGenerator *generator;
+ QScopedPointer<FixedParametersGenerator> fixedGenerator;
+
+ QHash<StreamTubeChannelPtr, TubeWrapper *> tubes;
+
+};
+
+StreamTubeServer::TubeWrapper::TubeWrapper(const AccountPtr &acc,
+ const OutgoingStreamTubeChannelPtr &tube, const QHostAddress &exportedAddr,
+ quint16 exportedPort, const QVariantMap &params, StreamTubeServer *parent)
+ : QObject(parent), mAcc(acc), mTube(tube)
+{
+ connect(tube->offerTcpSocket(exportedAddr, exportedPort, params),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onTubeOffered(Tp::PendingOperation*)));
+ connect(tube.data(),
+ SIGNAL(newConnection(uint)),
+ SLOT(onNewConnection(uint)));
+ connect(tube.data(),
+ SIGNAL(connectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString)));
+}
+
+void StreamTubeServer::TubeWrapper::onTubeOffered(Tp::PendingOperation *op)
+{
+ emit offerFinished(this, op);
+}
+
+void StreamTubeServer::TubeWrapper::onNewConnection(uint conn)
+{
+ emit newConnection(this, conn);
+}
+
+void StreamTubeServer::TubeWrapper::onConnectionClosed(uint conn, const QString &error,
+ const QString &message)
+{
+ emit connectionClosed(this, conn, error, message);
+}
+
+/**
+ * \class StreamTubeServer
+ * \ingroup serverclient
+ * \headerfile TelepathyQt4/stream-tube-server.h <TelepathyQt4/StreamTubeServer>
+ *
+ * \brief The StreamTubeServer class is a Handler implementation for outgoing %Stream %Tube channels,
+ * allowing an application to easily export a TCP network server over Telepathy Tubes without
+ * worrying about the channel dispatching details.
+ *
+ * Telepathy Tubes is a technology for connecting arbitrary applications together through the IM
+ * network (and sometimes with direct peer-to-peer connections), such that issues like firewall/NAT
+ * traversal are automatically handled. Stream Tubes in particular offer properties similar to
+ * SOCK_STREAM sockets. The StreamTubeServer class exports such a bytestream socket \b server over
+ * the tubes it \em handles as a Telepathy Handler %Client; the StreamTubeClient class is the
+ * counterpart, enabling TCP/UNIX socket clients to connect to services from such exported servers
+ * offered to them via tubes.
+ *
+ * Both peer-to-peer (\c TargetHandleType == \ref HandleTypeContact) and group (\c TargetHandleType
+ * == \ref HandleTypeRoom) channels are supported, and it's possible to specify the tube services to
+ * handle for each separately. It is also possible to not advertise handling capability for ANY tube
+ * service; instead just using the StreamTubeServer to handle tubes on an one-off basis by passing
+ * its corresponding %Client service name as the \a preferredHandler when requesting tubes via the
+ * Account::createStreamTube() methods (or equivalent).
+ *
+ * %Connection monitoring allows associating incoming connections on the exported server socket with
+ * the corresponding remote contacts. This allows an application to show the details of and/or
+ * initiate further communication with the remote contacts, without considering the actual tube
+ * channels the connections are being made through at all (in particular, their
+ * Channel::targetContact() accessor for peer-to-peer and the
+ * OutgoingStreamTubeChannel::connectionsForSourceAddresses() accessor for group tubes).
+ *
+ * Enabling connection monitoring adds a small overhead and latency to handling each incoming tube
+ * and signaling each new incoming connection over them, though, so use it only when needed.
+ * Additionally, some protocol backends or environments they're running in might not support the
+ * ::SocketAccessControlPort mechanism, in which case the source address won't be reported for
+ * connections through them. Even in this case, the remote contacts can be associated by accepting
+ * one incoming socket connection at a time, and waiting for the corresponding contact to be
+ * signaled (although its source address will be invalid, it's the only possibility given its the
+ * only accepted connection). However, it's not necessary to do this e.g. with the Gabble XMPP
+ * backend, because it fully supports the required mechanism.
+ *
+ * A service activated Handler can be implemented using StreamTubeServer by passing a predefined \a
+ * clientName manually to the chosen create() method, and installing Telepathy \c .client and D-Bus
+ * \c .service files declaring the implemented tube services as channel classes and a path to the
+ * executable. If this is not needed, the \a clientName can be omitted, in which case a random
+ * unique client name is generated and used instead.
+ *
+ * StreamTubeServer shares Account, Connection and Channel proxies and Contact objects with the
+ * rest of the application as long as a reference to the AccountManager, ClientRegistrar, or the
+ * factories used elsewhere is passed to the create() method. A stand-alone tube service Handler can
+ * get away without passing these however, or just passing select factories to make the desired
+ * features prepared and subclasses employed for these objects for their own convenience.
+ *
+ * Whichever method is used, the ChannelFactory (perhaps indirectly) given must construct
+ * OutgoingStreamTubeChannel instances or subclasses thereof for all channel classes corresponding
+ * to the tube services to handle. This is the default; overriding it without obeying these
+ * constraints using ChannelFactory::setSubclassForOutgoingStreamTubes() or the related methods
+ * for room tubes prevents StreamTubeServer from operating correctly.
+ *
+ * \todo Coin up a small Python script or alike to easily generate the .client and .service files.
+ * (fd.o #41614)
+ * \todo Support exporting Unix sockets as well. (fd.o #41615)
+ */
+
+/**
+ * Create a new StreamTubeServer, which will register itself on the session bus using an internal
+ * ClientRegistrar and use the given factories.
+ *
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ */
+StreamTubeServerPtr StreamTubeServer::create(
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory)
+{
+ return create(
+ QDBusConnection::sessionBus(),
+ accountFactory,
+ connectionFactory,
+ channelFactory,
+ contactFactory,
+ p2pServices,
+ roomServices,
+ clientName,
+ monitorConnections);
+}
+
+/**
+ * Create a new StreamTubeServer, which will register itself on the given \a bus using an internal
+ * ClientRegistrar and use the given factories.
+ *
+ * The factories must all be created for the given \a bus.
+ *
+ * \param bus Connection to the bus to register on.
+ * \param accountFactory The account factory to use.
+ * \param connectionFactory The connection factory to use.
+ * \param channelFactory The channel factory to use.
+ * \param contactFactory The contact factory to use.
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ */
+StreamTubeServerPtr StreamTubeServer::create(
+ const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections)
+{
+ return create(
+ ClientRegistrar::create(
+ bus,
+ accountFactory,
+ connectionFactory,
+ channelFactory,
+ contactFactory),
+ p2pServices,
+ roomServices,
+ clientName,
+ monitorConnections);
+}
+
+/**
+ * Create a new StreamTubeServer, which will register itself on the bus of and share objects with
+ * the given \a accountManager, creating an internal ClientRegistrar.
+ *
+ * \param accountManager A pointer to the account manager to link up with.
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ */
+StreamTubeServerPtr StreamTubeServer::create(
+ const AccountManagerPtr &accountManager,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections)
+{
+ return create(
+ accountManager->dbusConnection(),
+ accountManager->accountFactory(),
+ accountManager->connectionFactory(),
+ accountManager->channelFactory(),
+ accountManager->contactFactory(),
+ p2pServices,
+ roomServices,
+ clientName,
+ monitorConnections);
+}
+
+/**
+ * Create a new StreamTubeServer, which will register itself on the bus of and using the given
+ * client \a registrar, and share objects with it.
+ *
+ * \param registrar The client registrar to use.
+ * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels.
+ * \param roomServices Names of the tube services to handle on room/group tube channels.
+ * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix).
+ * \param monitorConnections Whether to enable connection monitoring or not.
+ */
+StreamTubeServerPtr StreamTubeServer::create(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections)
+{
+ return StreamTubeServerPtr(
+ new StreamTubeServer(registrar, p2pServices, roomServices, clientName,
+ monitorConnections));
+}
+
+StreamTubeServer::StreamTubeServer(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections)
+ : mPriv(new Private(registrar, p2pServices, roomServices, clientName, monitorConnections))
+{
+ connect(mPriv->handler.data(),
+ SIGNAL(invokedForTube(
+ Tp::AccountPtr,
+ Tp::StreamTubeChannelPtr,
+ QDateTime,
+ Tp::ChannelRequestHints)),
+ SLOT(onInvokedForTube(
+ Tp::AccountPtr,
+ Tp::StreamTubeChannelPtr,
+ QDateTime,
+ Tp::ChannelRequestHints)));
+}
+
+/**
+ * Class destructor.
+ */
+StreamTubeServer::~StreamTubeServer()
+{
+ if (isRegistered()) {
+ mPriv->registrar->unregisterClient(mPriv->handler);
+ }
+
+ delete mPriv;
+}
+
+/**
+ * Return the client registrar used by the server to register itself as a Handler client.
+ *
+ * This is the registrar originally passed to
+ * create(const ClientRegistrarPtr &, const QStringList &, const QStringList &, const QString &, bool)
+ * if that was used, and an internally constructed one otherwise. In any case, it can be used to
+ * e.g. register further clients like any other ClientRegistrar.
+ *
+ * \return A pointer to the registrar.
+ */
+ClientRegistrarPtr StreamTubeServer::registrar() const
+{
+ return mPriv->registrar;
+}
+
+/**
+ * Return the Telepathy %Client name of the server.
+ *
+ * \return The name, without the \c org.freedesktop.Telepathy.Client. prefix of the full D-Bus service name.
+ */
+QString StreamTubeServer::clientName() const
+{
+ return mPriv->clientName;
+}
+
+/**
+ * Return whether the server has been successfully registered or not.
+ *
+ * Registration is attempted, at the latest, when a socket is first exported using exportTcpSocket().
+ * It can fail e.g. because the connection to the bus has failed, or a predefined \a clientName has
+ * been passed to create(), and a %Client with the same name is already registered. Typically, failure
+ * registering would be a fatal error for a stand-alone tube handler, but only a warning event for
+ * an application serving other purposes. In any case, a high-quality user of the API will check the
+ * return value of this accessor after exporting their socket.
+ *
+ * \return \c true if the server has been successfully registered, \c false if not.
+ */
+bool StreamTubeServer::isRegistered() const
+{
+ return mPriv->isRegistered;
+}
+
+/**
+ * Return whether connection monitoring is enabled on this server.
+ *
+ * For technical reasons, connection monitoring can't be enabled when the server is already running,
+ * so there is no corresponding setter method. It has to be enabled by passing \c true as the \a
+ * monitorConnections parameter to the create() method.
+ *
+ * If connection monitoring isn't enabled, newTcpConnection() and tcpConnectionClosed() won't be
+ * emitted and tcpConnections() won't be populated.
+ *
+ * \return \c true if monitoring is enabled, \c false if not.
+ */
+bool StreamTubeServer::monitorsConnections() const
+{
+ return mPriv->handler->monitorsConnections();
+}
+
+/**
+ * Return the host address and port of the currently exported TCP socket, if any.
+ *
+ * QHostAddress::Null is reported as the address and 0 as the port if no TCP socket has yet been
+ * successfully exported.
+ *
+ * \return The host address and port values in a pair structure.
+ */
+QPair<QHostAddress, quint16> StreamTubeServer::exportedTcpSocketAddress() const
+{
+ return qMakePair(mPriv->exportedAddr, mPriv->exportedPort);
+}
+
+/**
+ * Return the fixed parameters, if any, which are sent along when offering the exported socket on
+ * all handled tubes.
+ *
+ * To prevent accidentally leaving the current parameters to be sent when offering a different
+ * socket, or vice versa, the parameters can only be set together with the socket using
+ * exportTcpSocket(). Parameters often contain sensitive information such as session identifiers or
+ * authentication credentials, which could then be used to maliciously access the service listening
+ * on the other socket.
+ *
+ * If a custom dynamic ParametersGenerator was passed to exportTcpSocket() instead of a set of fixed
+ * parameters, an empty set of parameters is returned.
+ *
+ * \return The parameters in a string-variant map.
+ */
+QVariantMap StreamTubeServer::exportedParameters() const
+{
+ if (!mPriv->generator) {
+ return QVariantMap();
+ }
+
+ FixedParametersGenerator *generator =
+ dynamic_cast<FixedParametersGenerator *>(mPriv->generator);
+
+ if (generator) {
+ return generator->nextParameters(AccountPtr(), OutgoingStreamTubeChannelPtr(),
+ ChannelRequestHints());
+ } else {
+ return QVariantMap();
+ }
+}
+
+/**
+ * Set the server to offer the socket listening at the given (\a address, \a port) combination as the
+ * local endpoint of tubes handled in the future.
+ *
+ * A fixed set of protocol bootstrapping \a parameters can optionally be set to be sent along with all
+ * tube offers until the next call to exportTcpSocket(). See the ParametersGenerator documentation
+ * for an in-depth description of the parameter transfer mechanism, and a more flexible way to vary
+ * the parameters between each handled tube.
+ *
+ * The handler is registered on the bus at the latest when this method or another exportTcpSocket()
+ * overload is called for the first time, so one should check the return value of isRegistered() at
+ * that point to verify that was successful.
+ *
+ * \param address The listen address of the socket.
+ * \param port The port of the socket.
+ * \param parameters The bootstrapping parameters in a string-value map.
+ */
+void StreamTubeServer::exportTcpSocket(
+ const QHostAddress &address,
+ quint16 port,
+ const QVariantMap &parameters)
+{
+ if (address.isNull() || port == 0) {
+ warning() << "Attempted to export null TCP socket address or zero port, ignoring";
+ return;
+ }
+
+ mPriv->exportedAddr = address;
+ mPriv->exportedPort = port;
+
+ mPriv->generator = 0;
+ if (!parameters.isEmpty()) {
+ mPriv->fixedGenerator.reset(new FixedParametersGenerator(parameters));
+ mPriv->generator = mPriv->fixedGenerator.data();
+ }
+
+ mPriv->ensureRegistered();
+}
+
+/**
+ * Set the StreamTubeServer to offer the already listening TCP \a server as the local endpoint of tubes
+ * handled in the future.
+ *
+ * This is just a convenience wrapper around
+ * exportTcpSocket(const QHostAddress &, quint16, const QVariantMap &) to be used when the TCP
+ * server code is implemented using the QtNetwork facilities.
+ *
+ * A fixed set of protocol bootstrapping \a parameters can optionally be set to be sent along with all
+ * tube offers until the next call to exportTcpSocket(). See the ParametersGenerator documentation
+ * for an in-depth description of the parameter transfer mechanism, and a more flexible way to vary
+ * the parameters between each handled tube.
+ *
+ * \param server A pointer to the TCP server.
+ * \param parameters The bootstrapping parameters in a string-value map.
+ */
+void StreamTubeServer::exportTcpSocket(
+ const QTcpServer *server,
+ const QVariantMap &parameters)
+{
+ if (!server->isListening()) {
+ warning() << "Attempted to export non-listening QTcpServer, ignoring";
+ return;
+ }
+
+ if (server->serverAddress() == QHostAddress::Any) {
+ return exportTcpSocket(QHostAddress::LocalHost, server->serverPort(), parameters);
+ } else if (server->serverAddress() == QHostAddress::AnyIPv6) {
+ return exportTcpSocket(QHostAddress::LocalHostIPv6, server->serverPort(), parameters);
+ } else {
+ return exportTcpSocket(server->serverAddress(), server->serverPort(), parameters);
+ }
+}
+
+/**
+ * Set the server to offer the socket listening at the given \a address - \a port combination as the
+ * local endpoint of tubes handled in the future, sending the parameters from the given \a generator
+ * along with the offers.
+ *
+ * The handler is registered on the bus at the latest when this method or another exportTcpSocket()
+ * overload is called for the first time, so one should check the return value of isRegistered() at
+ * that point to verify that was successful.
+ *
+ * \param address The listen address of the socket.
+ * \param port The port of the socket.
+ * \param generator A pointer to the bootstrapping parameters generator.
+ */
+void StreamTubeServer::exportTcpSocket(
+ const QHostAddress &address,
+ quint16 port,
+ ParametersGenerator *generator)
+{
+ if (address.isNull() || port == 0) {
+ warning() << "Attempted to export null TCP socket address or zero port, ignoring";
+ return;
+ }
+
+ mPriv->exportedAddr = address;
+ mPriv->exportedPort = port;
+ mPriv->generator = generator;
+
+ mPriv->ensureRegistered();
+}
+
+/**
+ * Set the server to offer the already listening TCP \a server as the local endpoint of tubes
+ * handled in the future, sending the parameters from the given \a generator along with the offers.
+ *
+ * This is just a convenience wrapper around
+ * exportTcpSocket(const QHostAddress &, quint16, ParametersGenerator *) to be used when the TCP
+ * server code is implemented using the QtNetwork facilities.
+ *
+ * \param server A pointer to the TCP server.
+ * \param generator A pointer to the bootstrapping parameters generator.
+ */
+void StreamTubeServer::exportTcpSocket(
+ const QTcpServer *server,
+ ParametersGenerator *generator)
+{
+ if (!server->isListening()) {
+ warning() << "Attempted to export non-listening QTcpServer, ignoring";
+ return;
+ }
+
+ if (server->serverAddress() == QHostAddress::Any) {
+ return exportTcpSocket(QHostAddress::LocalHost, server->serverPort(), generator);
+ } else if (server->serverAddress() == QHostAddress::AnyIPv6) {
+ return exportTcpSocket(QHostAddress::LocalHostIPv6, server->serverPort(), generator);
+ } else {
+ return exportTcpSocket(server->serverAddress(), server->serverPort(), generator);
+ }
+}
+
+/**
+ * Return the tubes currently handled by the server.
+ *
+ * \return A list of Tube structures containing pointers to the account and tube channel for each
+ * tube.
+ */
+QList<StreamTubeServer::Tube> StreamTubeServer::tubes() const
+{
+ QList<Tube> tubes;
+
+ foreach (TubeWrapper *wrapper, mPriv->tubes.values()) {
+ tubes.push_back(Tube(wrapper->mAcc, wrapper->mTube));
+ }
+
+ return tubes;
+}
+
+/**
+ * Return the ongoing TCP connections over tubes handled by this server.
+ *
+ * The returned mapping has the connection source addresses as keys and the contacts along with the
+ * accounts which can be used to reach them as values. Connections through protocol backends which
+ * don't support SocketAccessControlPort will be included as the potentially many values for the
+ * null source address key, the pair (\c QHostAddress::Null, 0).
+ *
+ * This is effectively a state recovery accessor corresponding to the change notification signals
+ * newTcpConnection() and tcpConnectionClosed().
+ *
+ * The mapping is only populated if connection monitoring was requested when creating the server (so
+ * monitorsConnections() returns \c true).
+ *
+ * \return The connections in a mapping with pairs of their source host addresses and ports as keys
+ * and structures containing pointers to the account and remote contacts they're from as values.
+ */
+QHash<QPair<QHostAddress, quint16>,
+ StreamTubeServer::RemoteContact>
+ StreamTubeServer::tcpConnections() const
+{
+ QHash<QPair<QHostAddress /* sourceAddress */, quint16 /* sourcePort */>, RemoteContact> conns;
+ if (!monitorsConnections()) {
+ warning() << "StreamTubeServer::tcpConnections() used, but connection monitoring is disabled";
+ return conns;
+ }
+
+ foreach (const Tube &tube, tubes()) {
+ // Ignore invalid and non-Open tubes to prevent a few useless warnings in corner cases where
+ // a tube is still being opened, or has been invalidated but we haven't processed that event
+ // yet.
+ if (!tube.channel()->isValid() || tube.channel()->state() != TubeChannelStateOpen) {
+ continue;
+ }
+
+ if (tube.channel()->addressType() != SocketAddressTypeIPv4 &&
+ tube.channel()->addressType() != SocketAddressTypeIPv6) {
+ continue;
+ }
+
+ QHash<QPair<QHostAddress,quint16>, uint> srcAddrConns =
+ tube.channel()->connectionsForSourceAddresses();
+ QHash<uint, ContactPtr> connContacts =
+ tube.channel()->contactsForConnections();
+
+ QPair<QHostAddress, quint16> srcAddr;
+ foreach (srcAddr, srcAddrConns.keys()) {
+ ContactPtr contact = connContacts.take(srcAddrConns.value(srcAddr));
+ conns.insert(srcAddr, RemoteContact(tube.account(), contact));
+ }
+
+ // The remaining values in our copy of connContacts are those which didn't have a
+ // corresponding source address, probably because the service doesn't properly implement
+ // Port AC
+ foreach (const ContactPtr &contact, connContacts.values()) {
+ // Insert them with an invalid source address as the key
+ conns.insertMulti(qMakePair(QHostAddress(QHostAddress::Null), quint16(0)),
+ RemoteContact(tube.account(), contact));
+ }
+ }
+
+ return conns;
+}
+
+void StreamTubeServer::onInvokedForTube(
+ const AccountPtr &acc,
+ const StreamTubeChannelPtr &tube,
+ const QDateTime &time,
+ const ChannelRequestHints &hints)
+{
+ Q_ASSERT(isRegistered()); // our SSTH shouldn't be receiving any channels unless it's registered
+ Q_ASSERT(tube->isRequested());
+ Q_ASSERT(tube->isValid()); // SSTH won't emit invalid tubes
+
+ OutgoingStreamTubeChannelPtr outgoing = OutgoingStreamTubeChannelPtr::qObjectCast(tube);
+
+ if (outgoing) {
+ emit tubeRequested(acc, outgoing, time, hints);
+ } else {
+ warning() << "The ChannelFactory used by StreamTubeServer must construct" <<
+ "OutgoingStreamTubeChannel subclasses for Requested=true StreamTubes";
+ tube->requestClose();
+ return;
+ }
+
+ if (!mPriv->tubes.contains(tube)) {
+ debug().nospace() << "Offering socket " << mPriv->exportedAddr << ":" << mPriv->exportedPort
+ << " on tube " << tube->objectPath();
+
+ QVariantMap params;
+ if (mPriv->generator) {
+ params = mPriv->generator->nextParameters(acc, outgoing, hints);
+ }
+
+ Q_ASSERT(!mPriv->exportedAddr.isNull() && mPriv->exportedPort != 0);
+
+ TubeWrapper *wrapper =
+ new TubeWrapper(acc, outgoing, mPriv->exportedAddr, mPriv->exportedPort, params, this);
+
+ connect(wrapper,
+ SIGNAL(offerFinished(TubeWrapper*,Tp::PendingOperation*)),
+ SLOT(onOfferFinished(TubeWrapper*,Tp::PendingOperation*)));
+ connect(tube.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString)));
+
+ if (monitorsConnections()) {
+ connect(wrapper,
+ SIGNAL(newConnection(TubeWrapper*,uint)),
+ SLOT(onNewConnection(TubeWrapper*,uint)));
+ connect(wrapper,
+ SIGNAL(connectionClosed(TubeWrapper*,uint,QString,QString)),
+ SLOT(onConnectionClosed(TubeWrapper*,uint,QString,QString)));
+ }
+
+ mPriv->tubes.insert(outgoing, wrapper);
+ }
+}
+
+void StreamTubeServer::onOfferFinished(
+ TubeWrapper *wrapper,
+ Tp::PendingOperation *op)
+{
+ OutgoingStreamTubeChannelPtr tube = wrapper->mTube;
+
+ if (op->isError()) {
+ warning() << "Offer() failed, closing tube" << tube->objectPath() << '-' <<
+ op->errorName() << ':' << op->errorMessage();
+
+ if (wrapper->mTube->isValid()) {
+ wrapper->mTube->requestClose();
+ }
+
+ wrapper->mTube->disconnect(this);
+ emit tubeClosed(wrapper->mAcc, wrapper->mTube, op->errorName(), op->errorMessage());
+ mPriv->tubes.remove(wrapper->mTube);
+ wrapper->deleteLater();
+ } else {
+ debug() << "Tube" << tube->objectPath() << "offered successfully";
+ }
+}
+
+void StreamTubeServer::onTubeInvalidated(
+ Tp::DBusProxy *proxy,
+ const QString &error,
+ const QString &message)
+{
+ OutgoingStreamTubeChannelPtr tube(qobject_cast<OutgoingStreamTubeChannel *>(proxy));
+ Q_ASSERT(!tube.isNull());
+
+ TubeWrapper *wrapper = mPriv->tubes.value(tube);
+ if (!wrapper) {
+ // Offer finish with error already removed it
+ return;
+ }
+
+ debug() << "Tube" << tube->objectPath() << "invalidated with" << error << ':' << message;
+
+ emit tubeClosed(wrapper->mAcc, wrapper->mTube, error, message);
+ mPriv->tubes.remove(tube);
+ delete wrapper;
+}
+
+void StreamTubeServer::onNewConnection(
+ TubeWrapper *wrapper,
+ uint conn)
+{
+ Q_ASSERT(monitorsConnections());
+
+ if (wrapper->mTube->addressType() == SocketAddressTypeIPv4
+ || wrapper->mTube->addressType() == SocketAddressTypeIPv6) {
+ QHash<QPair<QHostAddress,quint16>, uint> srcAddrConns =
+ wrapper->mTube->connectionsForSourceAddresses();
+ QHash<uint, Tp::ContactPtr> connContacts =
+ wrapper->mTube->contactsForConnections();
+
+ QPair<QHostAddress, quint16> srcAddr = srcAddrConns.key(conn);
+ emit newTcpConnection(srcAddr.first, srcAddr.second, wrapper->mAcc,
+ connContacts.value(conn), wrapper->mTube);
+ } else {
+ // No UNIX socket should ever have been offered yet
+ Q_ASSERT(false);
+ }
+}
+
+void StreamTubeServer::onConnectionClosed(
+ TubeWrapper *wrapper,
+ uint conn,
+ const QString &error,
+ const QString &message)
+{
+ Q_ASSERT(monitorsConnections());
+
+ if (wrapper->mTube->addressType() == SocketAddressTypeIPv4
+ || wrapper->mTube->addressType() == SocketAddressTypeIPv6) {
+ QHash<QPair<QHostAddress,quint16>, uint> srcAddrConns =
+ wrapper->mTube->connectionsForSourceAddresses();
+ QHash<uint, Tp::ContactPtr> connContacts =
+ wrapper->mTube->contactsForConnections();
+
+ QPair<QHostAddress, quint16> srcAddr = srcAddrConns.key(conn);
+ emit tcpConnectionClosed(srcAddr.first, srcAddr.second, wrapper->mAcc,
+ connContacts.value(conn), error, message, wrapper->mTube);
+ } else {
+ // No UNIX socket should ever have been offered yet
+ Q_ASSERT(false);
+ }
+}
+
+/**
+ * \fn void StreamTubeServer::tubeRequested(const AccountPtr &account, const
+ * OutgoingStreamTubeChannelPtr &tube, const QDateTime &userActionTime, const ChannelRequestHints
+ * &hints)
+ *
+ * Emitted when a tube has been requested for one of our services, and we've began handling it.
+ *
+ * This is emitted before invoking the ParametersGenerator, if any, for the tube.
+ *
+ * \param account A pointer to the account from which the tube was requested from.
+ * \param tube A pointer to the actual tube channel.
+ * \param userActionTime The time the request occurred at, if it was an user action. Should be used
+ * for focus stealing prevention.
+ * \param hints The hints passed to the request, if any.
+ */
+
+/**
+ * \fn void StreamTubeServer::tubeClosed(const AccountPtr &account, const
+ * OutgoingStreamTubeChannelPtr &tube, const QString &error, const QString &message)
+ *
+ * Emitted when a tube we've been handling (previously announced with tubeRequested()) has
+ * encountered an error or has otherwise been closed from further communication.
+ *
+ * \param account A pointer to the account from which the tube was requested from.
+ * \param tube A pointer to the actual tube channel.
+ * \param error The D-Bus error name corresponding to the reason for the closure.
+ * \param message A freeform debug message associated with the error.
+ */
+
+/**
+ * \fn void StreamTubeServer::newTcpConnection(const QHostAddress &sourceAddress, quint16
+ * sourcePort, const AccountPtr &account, const ContactPtr &contact, const
+ * OutgoingStreamTubeChannelPtr &tube)
+ *
+ * Emitted when we have picked up a new TCP connection to the (current or previous) exported server
+ * socket. This can be used to associate connections the protocol backend relays to the exported
+ * socket with the remote contact who originally initiated them in the other end of the tube.
+ *
+ * This is only emitted if connection monitoring was enabled when creating the StreamTubeServer.
+ * Additionally, if the protocol backend the connection is from doesn't support the
+ * ::SocketAccessControlPort mechanism, the source address and port will always be invalid.
+ *
+ * \param sourceAddress The source address of the connection, or QHostAddress::Null if it can't be
+ * resolved.
+ * \param sourcePort The source port of the connection, or 0 if it can't be resolved.
+ * \param account A pointer to the account through which the remote contact can be reached.
+ * \param contact A pointer to the remote contact object.
+ * \param tube A pointer to the tube channel through which the connection has been made.
+ */
+
+/**
+ * \fn void StreamTubeServer::tcpConnectionClosed(const QHostAddress &sourceAddress, quint16
+ * sourcePort, const AccountPtr &account, const ContactPtr &contact, conts QString &error, const
+ * QString &message, const OutgoingStreamTubeChannelPtr &tube)
+ *
+ * Emitted when a TCP connection (previously announced with newTcpConnection()) through one of our
+ * handled tubes has been closed due to an error or by a graceful disconnect (in which case the
+ * error is ::TP_QT4_ERROR_DISCONNECTED).
+ *
+ * This is only emitted if connection monitoring was enabled when creating the StreamTubeServer.
+ * Additionally, if the protocol backend the connection is from doesn't support the
+ * ::SocketAccessControlPort mechanism, the source address and port will always be invalid.
+ *
+ * \param sourceAddress The source address of the connection, or QHostAddress::Null if it couldn't
+ * be resolved.
+ * \param sourcePort The source port of the connection, or 0 if it couldn't be resolved.
+ * \param account A pointer to the account through which the remote contact can be reached.
+ * \param contact A pointer to the remote contact object.
+ * \param error The D-Bus error name corresponding to the reason for the closure.
+ * \param message A freeform debug message associated with the error.
+ * \param tube A pointer to the tube channel through which the connection has been made.
+ */
+
+} // Tp
diff --git a/qt4/TelepathyQt4/stream-tube-server.h b/qt4/TelepathyQt4/stream-tube-server.h
new file mode 100644
index 000000000..39838cf4a
--- /dev/null
+++ b/qt4/TelepathyQt4/stream-tube-server.h
@@ -0,0 +1,253 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_stream_tube_server_h_HEADER_GUARD_
+#define _TelepathyQt4_stream_tube_server_h_HEADER_GUARD_
+
+#include <QPair>
+#include <QSharedDataPointer>
+
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/RefCounted>
+#include <TelepathyQt4/Types>
+
+class QHostAddress;
+class QTcpServer;
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT StreamTubeServer : public QObject, public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StreamTubeServer)
+
+ class TubeWrapper;
+
+public:
+
+ class ParametersGenerator
+ {
+ public:
+ virtual QVariantMap
+ nextParameters(const AccountPtr &account, const OutgoingStreamTubeChannelPtr &tube,
+ const ChannelRequestHints &hints) = 0;
+
+ protected:
+ virtual ~ParametersGenerator() {}
+ };
+
+ class RemoteContact : public QPair<AccountPtr, ContactPtr>
+ {
+ public:
+ RemoteContact();
+ RemoteContact(const AccountPtr &account, const ContactPtr &contact);
+ RemoteContact(const RemoteContact &other);
+ ~RemoteContact();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ RemoteContact &operator=(const RemoteContact &other);
+
+ const AccountPtr &account() const
+ {
+ return first;
+ }
+
+ const ContactPtr &contact() const
+ {
+ return second;
+ }
+
+ private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ class Tube : public QPair<AccountPtr, OutgoingStreamTubeChannelPtr>
+ {
+ public:
+ Tube();
+ Tube(const AccountPtr &account, const OutgoingStreamTubeChannelPtr &channel);
+ Tube(const Tube &other);
+ ~Tube();
+
+ bool isValid() const { return mPriv.constData() != 0; }
+
+ Tube &operator=(const Tube &other);
+
+ const AccountPtr &account() const
+ {
+ return first;
+ }
+
+ const OutgoingStreamTubeChannelPtr &channel() const
+ {
+ return second;
+ }
+
+ private:
+ struct Private;
+ friend struct Private;
+ QSharedDataPointer<Private> mPriv;
+ };
+
+ static StreamTubeServerPtr create(
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false,
+ const AccountFactoryConstPtr &accountFactory =
+ AccountFactory::create(QDBusConnection::sessionBus()),
+ const ConnectionFactoryConstPtr &connectionFactory =
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ const ChannelFactoryConstPtr &channelFactory =
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ const ContactFactoryConstPtr &contactFactory =
+ ContactFactory::create());
+
+ static StreamTubeServerPtr create(
+ const QDBusConnection &bus,
+ const AccountFactoryConstPtr &accountFactory,
+ const ConnectionFactoryConstPtr &connectionFactory,
+ const ChannelFactoryConstPtr &channelFactory,
+ const ContactFactoryConstPtr &contactFactory,
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false);
+
+ static StreamTubeServerPtr create(
+ const AccountManagerPtr &accountManager,
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false);
+
+ static StreamTubeServerPtr create(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices = QStringList(),
+ const QString &clientName = QString(),
+ bool monitorConnections = false);
+
+ virtual ~StreamTubeServer();
+
+ ClientRegistrarPtr registrar() const;
+ QString clientName() const;
+ bool isRegistered() const;
+ bool monitorsConnections() const;
+
+ QPair<QHostAddress, quint16> exportedTcpSocketAddress() const;
+ QVariantMap exportedParameters() const;
+
+ void exportTcpSocket(
+ const QHostAddress &address,
+ quint16 port,
+ const QVariantMap &parameters = QVariantMap());
+ void exportTcpSocket(
+ const QTcpServer *server,
+ const QVariantMap &parameters = QVariantMap());
+
+ void exportTcpSocket(
+ const QHostAddress &address,
+ quint16 port,
+ ParametersGenerator *generator);
+ void exportTcpSocket(
+ const QTcpServer *server,
+ ParametersGenerator *generator);
+
+ QList<Tube> tubes() const;
+
+ QHash<QPair<QHostAddress, quint16>, RemoteContact> tcpConnections() const;
+
+Q_SIGNALS:
+
+ void tubeRequested(
+ const Tp::AccountPtr &account,
+ const Tp::OutgoingStreamTubeChannelPtr &tube,
+ const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &hints);
+ void tubeClosed(
+ const Tp::AccountPtr &account,
+ const Tp::OutgoingStreamTubeChannelPtr &tube,
+ const QString &error,
+ const QString &message);
+
+ void newTcpConnection(
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ const Tp::AccountPtr &account,
+ const Tp::ContactPtr &contact,
+ const Tp::OutgoingStreamTubeChannelPtr &tube);
+ void tcpConnectionClosed(
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ const Tp::AccountPtr &account,
+ const Tp::ContactPtr &contact,
+ const QString &error,
+ const QString &message,
+ const Tp::OutgoingStreamTubeChannelPtr &tube);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onInvokedForTube(
+ const Tp::AccountPtr &account,
+ const Tp::StreamTubeChannelPtr &tube,
+ const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &requestHints);
+
+ TELEPATHY_QT4_NO_EXPORT void onOfferFinished(
+ TubeWrapper *wrapper,
+ Tp::PendingOperation *op);
+ TELEPATHY_QT4_NO_EXPORT void onTubeInvalidated(
+ Tp::DBusProxy *proxy,
+ const QString &error,
+ const QString &message);
+
+ TELEPATHY_QT4_NO_EXPORT void onNewConnection(
+ TubeWrapper *wrapper,
+ uint conn);
+ TELEPATHY_QT4_NO_EXPORT void onConnectionClosed(
+ TubeWrapper *wrapper,
+ uint conn,
+ const QString &error,
+ const QString &message);
+
+private:
+ TELEPATHY_QT4_NO_EXPORT StreamTubeServer(
+ const ClientRegistrarPtr &registrar,
+ const QStringList &p2pServices,
+ const QStringList &roomServices,
+ const QString &clientName,
+ bool monitorConnections);
+
+ struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/streamed-media-channel.cpp b/qt4/TelepathyQt4/streamed-media-channel.cpp
new file mode 100644
index 000000000..2f1803595
--- /dev/null
+++ b/qt4/TelepathyQt4/streamed-media-channel.cpp
@@ -0,0 +1,1539 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/StreamedMediaChannel>
+
+#include "TelepathyQt4/_gen/streamed-media-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingVoid>
+
+#include <QHash>
+
+namespace Tp
+{
+
+/* ====== PendingStreamedMediaStreams ====== */
+struct TELEPATHY_QT4_NO_EXPORT PendingStreamedMediaStreams::Private
+{
+ StreamedMediaStreams streams;
+ uint numStreams;
+ uint streamsReady;
+};
+
+/**
+ * \class PendingStreamedMediaStreams
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/streamed-media-channel.h <TelepathyQt4/PendingStreamedMediaStreams>
+ *
+ * \brief Class containing the result of an asynchronous streamed media stream creation
+ * request.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is via StreamedMediaChannel.
+ *
+ * See \ref async_model
+ */
+
+/**
+ * Construct a new PendingStreamedMediaStreams object.
+ *
+ * \param channel StreamedMediaChannel to use.
+ * \param contact The contact who the media stream is with.
+ * \param types A list of stream types to request.
+ */
+PendingStreamedMediaStreams::PendingStreamedMediaStreams(const StreamedMediaChannelPtr &channel,
+ const ContactPtr &contact,
+ const QList<MediaStreamType> &types)
+ : PendingOperation(channel),
+ mPriv(new Private)
+{
+ mPriv->numStreams = types.size();
+ mPriv->streamsReady = 0;
+
+ UIntList l;
+ foreach (MediaStreamType type, types) {
+ l << type;
+ }
+
+ Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface =
+ channel->interface<Client::ChannelTypeStreamedMediaInterface>();
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(
+ streamedMediaInterface->RequestStreams(
+ contact->handle()[0], l), this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotStreams(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * Class destructor.
+ */
+PendingStreamedMediaStreams::~PendingStreamedMediaStreams()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the channel through which the request was made.
+ *
+ * \return A pointer to the StreamedMediaChannel object.
+ */
+StreamedMediaChannelPtr PendingStreamedMediaStreams::channel() const
+{
+ return StreamedMediaChannelPtr(qobject_cast<StreamedMediaChannel*>(
+ (StreamedMediaChannel*) _object().data()));
+}
+
+/**
+ * Return a list of the newly created StreamedMediaStreamPtr objects.
+ *
+ * \return A list of pointers to StreamedMediaStream objects, or an empty list if an error occurred.
+ */
+StreamedMediaStreams PendingStreamedMediaStreams::streams() const
+{
+ if (!isFinished()) {
+ warning() << "PendingStreamedMediaStreams::streams called before finished, "
+ "returning empty list";
+ return StreamedMediaStreams();
+ } else if (!isValid()) {
+ warning() << "PendingStreamedMediaStreams::streams called when not valid, "
+ "returning empty list";
+ return StreamedMediaStreams();
+ }
+
+ return mPriv->streams;
+}
+
+void PendingStreamedMediaStreams::gotStreams(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<MediaStreamInfoList> reply = *watcher;
+ if (reply.isError()) {
+ warning().nospace() << "StreamedMedia::RequestStreams()"
+ " failed with " << reply.error().name() << ": " <<
+ reply.error().message();
+ setFinishedWithError(reply.error());
+ watcher->deleteLater();
+ return;
+ }
+
+ debug() << "Got reply to StreamedMedia::RequestStreams()";
+
+ MediaStreamInfoList list = reply.value();
+ foreach (const MediaStreamInfo &streamInfo, list) {
+ StreamedMediaStreamPtr stream = channel()->lookupStreamById(
+ streamInfo.identifier);
+ if (!stream) {
+ stream = channel()->addStream(streamInfo);
+ } else {
+ channel()->onStreamDirectionChanged(streamInfo.identifier,
+ streamInfo.direction, streamInfo.pendingSendFlags);
+ channel()->onStreamStateChanged(streamInfo.identifier,
+ streamInfo.state);
+ }
+ mPriv->streams.append(stream);
+ connect(channel().data(),
+ SIGNAL(streamRemoved(Tp::StreamedMediaStreamPtr)),
+ SLOT(onStreamRemoved(Tp::StreamedMediaStreamPtr)));
+ connect(stream->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onStreamReady(Tp::PendingOperation*)));
+ }
+
+ watcher->deleteLater();
+}
+
+void PendingStreamedMediaStreams::onStreamRemoved(const StreamedMediaStreamPtr &stream)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ if (mPriv->streams.contains(stream)) {
+ // the stream was removed before becoming ready
+ setFinishedWithError(QLatin1String(TELEPATHY_ERROR_CANCELLED),
+ QLatin1String("Stream removed before ready"));
+ }
+}
+
+void PendingStreamedMediaStreams::onStreamReady(PendingOperation *op)
+{
+ if (isFinished()) {
+ return;
+ }
+
+ if (op->isError()) {
+ setFinishedWithError(op->errorName(), op->errorMessage());
+ return;
+ }
+
+ mPriv->streamsReady++;
+ debug() << "PendingStreamedMediaStreams:";
+ debug() << " Streams count:" << mPriv->numStreams;
+ debug() << " Streams ready:" << mPriv->streamsReady;
+ if (mPriv->streamsReady == mPriv->numStreams) {
+ debug() << "All streams are ready";
+ setFinished();
+ }
+}
+
+/* ====== StreamedMediaStream ====== */
+struct TELEPATHY_QT4_NO_EXPORT StreamedMediaStream::Private
+{
+ Private(StreamedMediaStream *parent, const StreamedMediaChannelPtr &channel,
+ const MediaStreamInfo &info);
+
+ static void introspectContact(Private *self);
+
+ PendingOperation *updateDirection(bool send, bool receive);
+ SendingState localSendingStateFromDirection();
+ SendingState remoteSendingStateFromDirection();
+
+ StreamedMediaStream *parent;
+ QPointer<StreamedMediaChannel> channel;
+ ReadinessHelper *readinessHelper;
+
+ uint id;
+ uint type;
+ uint contactHandle;
+ ContactPtr contact;
+ uint direction;
+ uint pendingSend;
+ uint state;
+};
+
+StreamedMediaStream::Private::Private(StreamedMediaStream *parent,
+ const StreamedMediaChannelPtr &channel,
+ const MediaStreamInfo &streamInfo)
+ : parent(parent),
+ channel(channel.data()),
+ readinessHelper(parent->readinessHelper()),
+ id(streamInfo.identifier),
+ type(streamInfo.type),
+ contactHandle(streamInfo.contact),
+ direction(MediaStreamDirectionNone),
+ pendingSend(0),
+ state(MediaStreamStateDisconnected)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableCore(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features(), // dependsOnFeatures
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectContact,
+ this);
+ introspectables[FeatureCore] = introspectableCore;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+void StreamedMediaStream::Private::introspectContact(StreamedMediaStream::Private *self)
+{
+ debug() << "Introspecting stream";
+ if (self->contactHandle == 0) {
+ debug() << "Stream ready";
+ self->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ return;
+ }
+
+ debug() << "Introspecting stream contact";
+ ContactManagerPtr contactManager =
+ self->parent->channel()->connection()->contactManager();
+ debug() << "contact manager" << contactManager;
+ // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids
+ // from MediaStreamInfo or something similar.
+ self->parent->connect(contactManager->contactsForHandles(
+ UIntList() << self->contactHandle),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(gotContact(Tp::PendingOperation*)));
+}
+
+PendingOperation *StreamedMediaStream::Private::updateDirection(
+ bool send, bool receive)
+{
+ uint newDirection = 0;
+
+ if (send) {
+ newDirection |= MediaStreamDirectionSend;
+ }
+
+ if (receive) {
+ newDirection |= MediaStreamDirectionReceive;
+ }
+
+ Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface =
+ parent->channel()->interface<Client::ChannelTypeStreamedMediaInterface>();
+ return new PendingVoid(
+ streamedMediaInterface->RequestStreamDirection(
+ id, newDirection),
+ StreamedMediaStreamPtr(parent));
+}
+
+StreamedMediaStream::SendingState StreamedMediaStream::Private::localSendingStateFromDirection()
+{
+ if (pendingSend & MediaStreamPendingLocalSend) {
+ return SendingStatePendingSend;
+ }
+ if (direction & MediaStreamDirectionSend) {
+ return SendingStateSending;
+ }
+ return SendingStateNone;
+}
+
+StreamedMediaStream::SendingState StreamedMediaStream::Private::remoteSendingStateFromDirection()
+{
+ if (pendingSend & MediaStreamPendingRemoteSend) {
+ return SendingStatePendingSend;
+ }
+ if (direction & MediaStreamDirectionReceive) {
+ return SendingStateSending;
+ }
+ return SendingStateNone;
+}
+
+/**
+ * \class StreamedMediaStream
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/streamed-media-channel.h <TelepathyQt4/StreamedMediaStream>
+ *
+ * \brief The StreamedMediaStream class represents a Telepathy streamed media
+ * stream.
+ *
+ * Instances of this class cannot be constructed directly; the only way to get
+ * one is via StreamedMediaChannel.
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * StreamedMediaStream object usable.
+ *
+ * Note that this feature must be enabled in order to use most StreamedMediaStream
+ * methods. See specific methods documentation for more details.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature StreamedMediaStream::FeatureCore = Feature(QLatin1String(StreamedMediaStream::staticMetaObject.className()), 0);
+
+/**
+ * Construct a new StreamedMediaStream object.
+ *
+ * \param channel The channel ownding this media stream.
+ * \param streamInfo The stream info of this media stream.
+ */
+StreamedMediaStream::StreamedMediaStream(const StreamedMediaChannelPtr &channel,
+ const MediaStreamInfo &streamInfo)
+ : Object(),
+ ReadyObject(this, FeatureCore),
+ mPriv(new Private(this, channel, streamInfo))
+{
+ gotDirection(streamInfo.direction, streamInfo.pendingSendFlags);
+ gotStreamState(streamInfo.state);
+}
+
+/**
+ * Class destructor.
+ */
+StreamedMediaStream::~StreamedMediaStream()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the channel owning this media stream.
+ *
+ * \return A pointer to the StreamedMediaChannel object.
+ */
+StreamedMediaChannelPtr StreamedMediaStream::channel() const
+{
+ return StreamedMediaChannelPtr(mPriv->channel);
+}
+
+/**
+ * Return the id of this media stream.
+ *
+ * \return An integer representing the media stream id.
+ */
+uint StreamedMediaStream::id() const
+{
+ return mPriv->id;
+}
+
+/**
+ * Return the contact who this media stream is with.
+ *
+ * \return A pointer to the Contact object.
+ */
+ContactPtr StreamedMediaStream::contact() const
+{
+ return mPriv->contact;
+}
+
+/**
+ * Return the state of this media stream.
+ *
+ * \return The state as #MediaStreamState.
+ */
+MediaStreamState StreamedMediaStream::state() const
+{
+ return (MediaStreamState) mPriv->state;
+}
+
+/**
+ * Return the type of this media stream.
+ *
+ * \return The type as #MediaStreamType.
+ */
+MediaStreamType StreamedMediaStream::type() const
+{
+ return (MediaStreamType) mPriv->type;
+}
+
+/**
+ * Return whether media is being sent on this media stream.
+ *
+ * \return \c true if media is being sent, \c false otherwise.
+ * \sa localSendingStateChanged()
+ */
+bool StreamedMediaStream::sending() const
+{
+ return mPriv->direction & MediaStreamDirectionSend;
+}
+
+/**
+ * Return whether media is being received on this media stream.
+ *
+ * \return \c true if media is being received, \c false otherwise.
+ * \sa remoteSendingStateChanged()
+ */
+bool StreamedMediaStream::receiving() const
+{
+ return mPriv->direction & MediaStreamDirectionReceive;
+}
+
+/**
+ * Return whether the local user has been asked to send media by the
+ * remote user on this media stream.
+ *
+ * \return \c true if the local user has been asked to send media by the
+ * remote user, \c false otherwise.
+ * \sa localSendingStateChanged()
+ */
+bool StreamedMediaStream::localSendingRequested() const
+{
+ return mPriv->pendingSend & MediaStreamPendingLocalSend;
+}
+
+/**
+ * Return whether the remote user has been asked to send media by the local
+ * user on this media stream.
+ *
+ * \return \c true if the remote user has been asked to send media by the
+ * local user, \c false otherwise.
+ * \sa remoteSendingStateChanged()
+ */
+bool StreamedMediaStream::remoteSendingRequested() const
+{
+ return mPriv->pendingSend & MediaStreamPendingRemoteSend;
+}
+
+/**
+ * Return the direction of this media stream.
+ *
+ * \return The direction as #MediaStreamDirection.
+ * \sa localSendingState(), remoteSendingState(),
+ * localSendingStateChanged(), remoteSendingStateChanged(),
+ * sending(), receiving()
+ */
+MediaStreamDirection StreamedMediaStream::direction() const
+{
+ return (MediaStreamDirection) mPriv->direction;
+}
+
+/**
+ * Return the pending send flags of this media stream.
+ *
+ * \return The pending send flags as #MediaStreamPendingSend.
+ * \sa localSendingStateChanged()
+ */
+MediaStreamPendingSend StreamedMediaStream::pendingSend() const
+{
+ return (MediaStreamPendingSend) mPriv->pendingSend;
+}
+
+/**
+ * Request a change in the direction of this media stream. In particular, this
+ * might be useful to stop sending media of a particular type, or inform the
+ * peer that you are no longer using media that is being sent to you.
+ *
+ * \param direction The new direction.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa localSendingStateChanged(), remoteSendingStateChanged()
+ */
+PendingOperation *StreamedMediaStream::requestDirection(
+ MediaStreamDirection direction)
+{
+ StreamedMediaChannelPtr chan(channel());
+ Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface =
+ chan->interface<Client::ChannelTypeStreamedMediaInterface>();
+ return new PendingVoid(
+ streamedMediaInterface->RequestStreamDirection(
+ mPriv->id, direction),
+ StreamedMediaStreamPtr(this));
+}
+
+/**
+ * Start sending a DTMF tone on this media stream.
+ *
+ * Where possible, the tone will continue until stopDTMFTone() is called.
+ * On certain protocols, it may only be possible to send events with a predetermined
+ * length. In this case, the implementation may emit a fixed-length tone,
+ * and the stopDTMFTone() method call should return #TP_QT4_ERROR_NOT_AVAILABLE.
+ *
+ * If the channel() does not support the #TP_QT4_IFACE_CHANNEL_INTERFACE_DTMF
+ * interface, the resulting PendingOperation will fail with error code
+ * #TP_QT4_ERROR_NOT_IMPLEMENTED.
+
+ * \param event A numeric event code from the #DTMFEvent enum.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request finishes.
+ * \sa stopDTMFTone()
+ */
+PendingOperation *StreamedMediaStream::startDTMFTone(DTMFEvent event)
+{
+ StreamedMediaChannelPtr chan(channel());
+ if (!chan->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_DTMF))) {
+ warning() << "StreamedMediaStream::startDTMFTone() used with no dtmf interface";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("StreamedMediaChannel does not support dtmf interface"),
+ StreamedMediaStreamPtr(this));
+ }
+
+ Client::ChannelInterfaceDTMFInterface *dtmfInterface =
+ chan->interface<Client::ChannelInterfaceDTMFInterface>();
+ return new PendingVoid(
+ dtmfInterface->StartTone(mPriv->id, event),
+ StreamedMediaStreamPtr(this));
+}
+
+/**
+ * Stop sending any DTMF tone which has been started using the startDTMFTone()
+ * method.
+ *
+ * If there is no current tone, the resulting PendingOperation will
+ * finish successfully.
+ *
+ * If continuous tones are not supported by this media stream, the resulting
+ * PendingOperation will fail with error code #TP_QT4_ERROR_NOT_AVAILABLE.
+ *
+ * If the channel() does not support the #TP_QT4_IFACE_CHANNEL_INTERFACE_DTMF
+ * interface, the resulting PendingOperation will fail with error code
+ * #TP_QT4_ERROR_NOT_IMPLEMENTED.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request finishes.
+ * \sa startDTMFTone()
+ */
+PendingOperation *StreamedMediaStream::stopDTMFTone()
+{
+ StreamedMediaChannelPtr chan(channel());
+ if (!chan->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_DTMF))) {
+ warning() << "StreamedMediaStream::stopDTMFTone() used with no dtmf interface";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("StreamedMediaChannel does not support dtmf interface"),
+ StreamedMediaStreamPtr(this));
+ }
+
+ Client::ChannelInterfaceDTMFInterface *dtmfInterface =
+ chan->interface<Client::ChannelInterfaceDTMFInterface>();
+ return new PendingVoid(
+ dtmfInterface->StopTone(mPriv->id),
+ StreamedMediaStreamPtr(this));
+}
+
+/**
+ * Request a change in the direction of this media stream.
+ *
+ * In particular, this might be useful to stop sending media of a particular type,
+ * or inform the peer that you are no longer using media that is being sent to you.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa requestDirection(Tp::MediaStreamDirection direction),
+ * localSendingStateChanged(), remoteSendingStateChanged()
+ */
+PendingOperation *StreamedMediaStream::requestDirection(bool send, bool receive)
+{
+ uint dir = MediaStreamDirectionNone;
+ if (send) {
+ dir |= MediaStreamDirectionSend;
+ }
+ if (receive) {
+ dir |= MediaStreamDirectionReceive;
+ }
+
+ return requestDirection((MediaStreamDirection) dir);
+}
+
+/**
+ * Return the media stream local sending state.
+ *
+ * \return The local sending state as StreamedMediaStream::SendingState.
+ * \sa localSendingStateChanged()
+ */
+StreamedMediaStream::SendingState StreamedMediaStream::localSendingState() const
+{
+ return mPriv->localSendingStateFromDirection();
+}
+
+/**
+ * Return the media stream remote sending state.
+ *
+ * \return The remote sending state as StreamedMediaStream::SendingState.
+ * \sa remoteSendingStateChanged()
+ */
+StreamedMediaStream::SendingState StreamedMediaStream::remoteSendingState() const
+{
+ return mPriv->remoteSendingStateFromDirection();
+}
+
+/**
+ * Request that media starts or stops being sent on this media stream.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa localSendingStateChanged(), requestDirection()
+ */
+PendingOperation *StreamedMediaStream::requestSending(bool send)
+{
+ return mPriv->updateDirection(
+ send,
+ mPriv->direction & MediaStreamDirectionReceive);
+}
+
+/**
+ * Request that the remote contact stops or starts sending on this media stream.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa remoteSendingStateChanged(), requestDirection()
+ */
+PendingOperation *StreamedMediaStream::requestReceiving(bool receive)
+{
+ return mPriv->updateDirection(
+ mPriv->direction & MediaStreamDirectionSend,
+ receive);
+}
+
+/**
+ * \fn void StreamedMediaStream::localSendingStateChanged(
+ * Tp::StreamedMediaStream::SendingState localSendingState)
+ *
+ * Emitted when the local sending state of this media stream changes.
+ *
+ * \param localSendingState The new local sending state of this media stream.
+ * \sa localSendingState()
+ */
+
+/**
+ * \fn void MediaStream::remoteSendingStateChanged(
+ * Tp::MediaStream::SendingState &remoteSendingState)
+ *
+ * Emitted when the remote sending state of this media stream changes.
+ *
+ * \param remoteSendingState The new remote sending state of this media stream.
+ * \sa remoteSendingState()
+ */
+
+void StreamedMediaStream::gotContact(PendingOperation *op)
+{
+ PendingContacts *pc = qobject_cast<PendingContacts *>(op);
+ Q_ASSERT(pc->isForHandles());
+
+ if (op->isError()) {
+ warning().nospace() << "Gathering media stream contact failed: "
+ << op->errorName() << ": " << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ op->errorName(), op->errorMessage());
+ return;
+ }
+
+ QList<ContactPtr> contacts = pc->contacts();
+ UIntList invalidHandles = pc->invalidHandles();
+ if (contacts.size()) {
+ Q_ASSERT(contacts.size() == 1);
+ Q_ASSERT(invalidHandles.size() == 0);
+ mPriv->contact = contacts.first();
+
+ debug() << "Got stream contact";
+ debug() << "Stream ready";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
+ } else {
+ Q_ASSERT(invalidHandles.size() == 1);
+ warning().nospace() << "Error retrieving media stream contact (invalid handle)";
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
+ QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Invalid contact handle"));
+ }
+}
+
+void StreamedMediaStream::gotDirection(uint direction, uint pendingSend)
+{
+ if (direction == mPriv->direction &&
+ pendingSend == mPriv->pendingSend) {
+ return;
+ }
+
+ SendingState oldLocalState = mPriv->localSendingStateFromDirection();
+ SendingState oldRemoteState = mPriv->remoteSendingStateFromDirection();
+
+ mPriv->direction = direction;
+ mPriv->pendingSend = pendingSend;
+
+ if (!isReady()) {
+ return;
+ }
+
+ SendingState localSendingState =
+ mPriv->localSendingStateFromDirection();
+ if (localSendingState != oldLocalState) {
+ emit localSendingStateChanged(localSendingState);
+ }
+
+ SendingState remoteSendingState =
+ mPriv->remoteSendingStateFromDirection();
+ if (remoteSendingState != oldRemoteState) {
+ emit remoteSendingStateChanged(remoteSendingState);
+ }
+}
+
+void StreamedMediaStream::gotStreamState(uint state)
+{
+ if (state == mPriv->state) {
+ return;
+ }
+
+ mPriv->state = state;
+}
+
+/* ====== StreamedMediaChannel ====== */
+struct TELEPATHY_QT4_NO_EXPORT StreamedMediaChannel::Private
+{
+ Private(StreamedMediaChannel *parent);
+ ~Private();
+
+ static void introspectStreams(Private *self);
+ static void introspectLocalHoldState(Private *self);
+
+ // Public object
+ StreamedMediaChannel *parent;
+
+ // Mandatory properties interface proxy
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ StreamedMediaStreams incompleteStreams;
+ StreamedMediaStreams streams;
+
+ LocalHoldState localHoldState;
+ LocalHoldStateReason localHoldStateReason;
+};
+
+StreamedMediaChannel::Private::Private(StreamedMediaChannel *parent)
+ : parent(parent),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ localHoldState(LocalHoldStateUnheld),
+ localHoldStateReason(LocalHoldStateReasonNone)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableStreams(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectStreams,
+ this);
+ introspectables[FeatureStreams] = introspectableStreams;
+
+ ReadinessHelper::Introspectable introspectableLocalHoldState(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectLocalHoldState,
+ this);
+ introspectables[FeatureLocalHoldState] = introspectableLocalHoldState;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+StreamedMediaChannel::Private::~Private()
+{
+}
+
+void StreamedMediaChannel::Private::introspectStreams(StreamedMediaChannel::Private *self)
+{
+ StreamedMediaChannel *parent = self->parent;
+ Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface =
+ parent->interface<Client::ChannelTypeStreamedMediaInterface>();
+
+ parent->connect(streamedMediaInterface,
+ SIGNAL(StreamAdded(uint,uint,uint)),
+ SLOT(onStreamAdded(uint,uint,uint)));
+ parent->connect(streamedMediaInterface,
+ SIGNAL(StreamRemoved(uint)),
+ SLOT(onStreamRemoved(uint)));
+ parent->connect(streamedMediaInterface,
+ SIGNAL(StreamDirectionChanged(uint,uint,uint)),
+ SLOT(onStreamDirectionChanged(uint,uint,uint)));
+ parent->connect(streamedMediaInterface,
+ SIGNAL(StreamStateChanged(uint,uint)),
+ SLOT(onStreamStateChanged(uint,uint)));
+ parent->connect(streamedMediaInterface,
+ SIGNAL(StreamError(uint,uint,QString)),
+ SLOT(onStreamError(uint,uint,QString)));
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ streamedMediaInterface->ListStreams(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher *)),
+ SLOT(gotStreams(QDBusPendingCallWatcher *)));
+}
+
+void StreamedMediaChannel::Private::introspectLocalHoldState(StreamedMediaChannel::Private *self)
+{
+ StreamedMediaChannel *parent = self->parent;
+ Client::ChannelInterfaceHoldInterface *holdInterface =
+ parent->interface<Client::ChannelInterfaceHoldInterface>();
+
+ parent->connect(holdInterface,
+ SIGNAL(HoldStateChanged(uint,uint)),
+ SLOT(onLocalHoldStateChanged(uint,uint)));
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ holdInterface->GetHoldState(), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotLocalHoldState(QDBusPendingCallWatcher*)));
+}
+
+/**
+ * \class StreamedMediaChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/streamed-media-channel.h <TelepathyQt4/StreamedMediaChannel>
+ *
+ * \brief The StreamedMediaChannel class represents a Telepathy channel of type StreamedMedia.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * StreamedMediaChannel object usable.
+ *
+ * This is currently the same as Channel::FeatureCore, but may change to include more.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ * \sa awaitingLocalAnswer(), awaitingRemoteAnswer(), acceptCall(), hangupCall(), handlerStreamingRequired()
+ */
+const Feature StreamedMediaChannel::FeatureCore = Feature(QLatin1String(Channel::staticMetaObject.className()), 0, true);
+
+/**
+ * Feature used in order to access media stream specific methods.
+ *
+ * See media stream specific methods' documentation for more details.
+ * \sa streams(), streamsForType(),
+ * requestStream(), requestStreams(), streamAdded()
+ * removeStream(), removeStreams(), streamRemoved(),
+ * streamDirectionChanged(), streamStateChanged(), streamError()
+ */
+const Feature StreamedMediaChannel::FeatureStreams = Feature(QLatin1String(StreamedMediaChannel::staticMetaObject.className()), 0);
+
+/**
+ * Feature used in order to access local hold state info.
+ *
+ * See local hold state specific methods' documentation for more details.
+ * \sa localHoldState(), localHoldStateReason(), requestHold(), localHoldStateChanged()
+ */
+const Feature StreamedMediaChannel::FeatureLocalHoldState = Feature(QLatin1String(StreamedMediaChannel::staticMetaObject.className()), 1);
+
+/**
+ * Create a new StreamedMediaChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A StreamedMediaChannelPtr object pointing to the newly created
+ * StreamedMediaChannel object.
+ */
+StreamedMediaChannelPtr StreamedMediaChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return StreamedMediaChannelPtr(new StreamedMediaChannel(connection,
+ objectPath, immutableProperties, StreamedMediaChannel::FeatureCore));
+}
+
+/**
+ * Construct a new StreamedMediaChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on StreamedMediaChannel::FeatureCore.
+ */
+StreamedMediaChannel::StreamedMediaChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : Channel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+StreamedMediaChannel::~StreamedMediaChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return a list of media streams in this channel.
+ *
+ * This methods requires StreamedMediaChannel::FeatureStreams to be ready.
+ *
+ * \return A list of pointers to StreamedMediaStream objects.
+ * \sa streamAdded(), streamRemoved(), streamsForType(), requestStreams()
+ */
+StreamedMediaStreams StreamedMediaChannel::streams() const
+{
+ return mPriv->streams;
+}
+
+/**
+ * Return a list of media streams in this channel for the given type \a type.
+ *
+ * This methods requires StreamedMediaChannel::FeatureStreams to be ready.
+ *
+ * \param type The interested type.
+ * \return A list of pointers to StreamedMediaStream objects.
+ * \sa streamAdded(), streamRemoved(), streams(), requestStreams()
+ */
+StreamedMediaStreams StreamedMediaChannel::streamsForType(MediaStreamType type) const
+{
+ StreamedMediaStreams ret;
+ foreach (const StreamedMediaStreamPtr &stream, mPriv->streams) {
+ if (stream->type() == type) {
+ ret << stream;
+ }
+ }
+ return ret;
+}
+
+/**
+ * Return whether this channel is awaiting local answer.
+ *
+ * This method requires StreamedMediaChannel::FeatureCore to be ready.
+ *
+ * \return \c true if awaiting local answer, \c false otherwise.
+ * \sa awaitingRemoteAnswer(), acceptCall()
+ */
+bool StreamedMediaChannel::awaitingLocalAnswer() const
+{
+ return groupSelfHandleIsLocalPending();
+}
+
+/**
+ * Return whether this channel is awaiting remote answer.
+ *
+ * This method requires StreamedMediaChannel::FeatureCore to be ready.
+ *
+ * \return \c true if awaiting remote answer, \c false otherwise.
+ * \sa awaitingLocalAnswer()
+ */
+bool StreamedMediaChannel::awaitingRemoteAnswer() const
+{
+ return !groupRemotePendingContacts().isEmpty();
+}
+
+/**
+ * Accept an incoming call.
+ *
+ * This method requires StreamedMediaChannel::FeatureCore to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa awaitingLocalAnswer(), hangupCall()
+ */
+PendingOperation *StreamedMediaChannel::acceptCall()
+{
+ return groupAddSelfHandle();
+}
+
+/**
+ * Remove the specified media stream from this channel.
+ *
+ * This methods requires StreamedMediaChannel::FeatureStreams to be ready.
+ *
+ * \param stream Media stream to remove.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa streamRemoved(), streams(), streamsForType()
+ */
+PendingOperation *StreamedMediaChannel::removeStream(const StreamedMediaStreamPtr &stream)
+{
+ if (!stream) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Unable to remove a null stream"),
+ StreamedMediaChannelPtr(this));
+ }
+
+ // StreamedMedia.RemoveStreams will trigger StreamedMedia.StreamRemoved
+ // that will proper remove the stream
+ Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface =
+ interface<Client::ChannelTypeStreamedMediaInterface>();
+ return new PendingVoid(
+ streamedMediaInterface->RemoveStreams(
+ UIntList() << stream->id()),
+ StreamedMediaChannelPtr(this));
+}
+
+/**
+ * Remove the specified media streams from this channel.
+ *
+ * This methods requires StreamedMediaChannel::FeatureStreams to be ready.
+ *
+ * \param streams List of media streams to remove.
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ * \sa streamRemoved(), streams(), streamsForType()
+ */
+PendingOperation *StreamedMediaChannel::removeStreams(const StreamedMediaStreams &streams)
+{
+ UIntList ids;
+ foreach (const StreamedMediaStreamPtr &stream, streams) {
+ if (!stream) {
+ continue;
+ }
+ ids << stream->id();
+ }
+
+ if (ids.isEmpty()) {
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT),
+ QLatin1String("Unable to remove invalid streams"),
+ StreamedMediaChannelPtr(this));
+ }
+
+ Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface =
+ interface<Client::ChannelTypeStreamedMediaInterface>();
+ return new PendingVoid(
+ streamedMediaInterface->RemoveStreams(ids),
+ StreamedMediaChannelPtr(this));
+}
+
+/**
+ * Request that media streams be established to exchange the given type \a type
+ * of media with the given contact \a contact.
+ *
+ * This methods requires StreamedMediaChannel::FeatureStreams to be ready.
+ *
+ * \return A PendingStreamedMediaStreams which will emit PendingStreamedMediaStreams::finished
+ * when the call has finished.
+ * \sa streamAdded(), streams(), streamsForType()
+ */
+PendingStreamedMediaStreams *StreamedMediaChannel::requestStream(
+ const ContactPtr &contact,
+ MediaStreamType type)
+{
+ return new PendingStreamedMediaStreams(StreamedMediaChannelPtr(this),
+ contact,
+ QList<MediaStreamType>() << type);
+}
+
+/**
+ * Request that media streams be established to exchange the given types \a
+ * types of media with the given contact \a contact.
+ *
+ * This methods requires StreamedMediaChannel::FeatureStreams to be ready.
+ *
+ * \return A PendingStreamedMediaStreams which will emit PendingStreamedMediaStreams::finished
+ * when the call has finished.
+ * \sa streamAdded(), streams(), streamsForType()
+ */
+PendingStreamedMediaStreams *StreamedMediaChannel::requestStreams(
+ const ContactPtr &contact,
+ QList<MediaStreamType> types)
+{
+ return new PendingStreamedMediaStreams(StreamedMediaChannelPtr(this),
+ contact, types);
+}
+
+/**
+ * Request that the call is ended.
+ *
+ * This method requires StreamedMediaChannel::FeatureCore to be ready.
+ *
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the call has finished.
+ */
+PendingOperation *StreamedMediaChannel::hangupCall()
+{
+ return requestLeave();
+}
+
+/**
+ * Check whether media streaming by the handler is required for this channel.
+ *
+ * For channels with the #TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING interface,
+ * the main handler of the channel is responsible for doing the actual streaming, for instance by
+ * calling createFarsightChannel(channel) from TelepathyQt4-Farsight library
+ * and using the telepathy-farsight API on the resulting TfChannel.
+ *
+ * This method requires StreamedMediaChannel::FeatureCore to be ready.
+ *
+ * \return \c true if required, \c false otherwise.
+ */
+bool StreamedMediaChannel::handlerStreamingRequired() const
+{
+ return interfaces().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING));
+}
+
+/**
+ * Return the local hold state for this channel.
+ *
+ * Whether the local user has placed this channel on hold.
+ *
+ * This method requires StreamedMediaChannel::FeatureHoldState to be ready.
+ *
+ * \return The local hold state as #LocalHoldState.
+ * \sa requestHold(), localHoldStateChanged()
+ */
+LocalHoldState StreamedMediaChannel::localHoldState() const
+{
+ if (!isReady(FeatureLocalHoldState)) {
+ warning() << "StreamedMediaChannel::localHoldState() used with FeatureLocalHoldState not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD))) {
+ warning() << "StreamedMediaChannel::localHoldStateReason() used with no hold interface";
+ }
+
+ return mPriv->localHoldState;
+}
+
+/**
+ * Return the reason why localHoldState() changed to its current value.
+ *
+ * This method requires StreamedMediaChannel::FeatureLocalHoldState to be ready.
+ *
+ * \return The local hold state reason as #LocalHoldStateReason.
+ * \sa requestHold(), localHoldStateChanged()
+ */
+LocalHoldStateReason StreamedMediaChannel::localHoldStateReason() const
+{
+ if (!isReady(FeatureLocalHoldState)) {
+ warning() << "StreamedMediaChannel::localHoldStateReason() used with FeatureLocalHoldState not ready";
+ } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD))) {
+ warning() << "StreamedMediaChannel::localHoldStateReason() used with no hold interface";
+ }
+
+ return mPriv->localHoldStateReason;
+}
+
+/**
+ * Request that the channel be put on hold (be instructed not to send any media
+ * streams to you) or be taken off hold.
+ *
+ * If the CM can immediately tell that the requested state
+ * change could not possibly succeed, the resulting PendingOperation will fail
+ * with error code #TP_QT4_ERROR_NOT_AVAILABLE.
+ * If the requested state is the same as the current state, the resulting
+ * PendingOperation will finish successfully.
+ *
+ * Otherwise, the channel's local hold state will change to
+ * #LocalHoldStatePendingHold or #LocalHoldStatePendingUnhold (as
+ * appropriate), then the resulting PendingOperation will finish successfully.
+ *
+ * The eventual success or failure of the request is indicated by a subsequent
+ * localHoldStateChanged() signal, changing the local hold state to
+ * #LocalHoldStateHeld or #LocalHoldStateUnheld.
+ *
+ * If the channel has multiple streams, and the connection manager succeeds in
+ * changing the hold state of one stream but fails to change the hold state of
+ * another, it will attempt to revert all streams to their previous hold
+ * states.
+ *
+ * If the channel does not support the #TP_QT4_IFACE_CHANNEL_INTERFACE_HOLD
+ * interface, the PendingOperation will fail with error code
+ * #TP_QT4_ERROR_NOT_IMPLEMENTED.
+ *
+ * \param hold A boolean indicating whether or not the channel should be on hold
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the request finishes.
+ * \sa localHoldState(), localHoldStateReason(), localHoldStateChanged()
+ */
+PendingOperation *StreamedMediaChannel::requestHold(bool hold)
+{
+ if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD))) {
+ warning() << "StreamedMediaChannel::requestHold() used with no hold interface";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("StreamedMediaChannel does not support hold interface"),
+ StreamedMediaChannelPtr(this));
+ }
+
+ Client::ChannelInterfaceHoldInterface *holdInterface =
+ interface<Client::ChannelInterfaceHoldInterface>();
+ return new PendingVoid(holdInterface->RequestHold(hold),
+ StreamedMediaChannelPtr(this));
+}
+
+/**
+ * \fn void StreamedMediaChannel::streamAdded(const Tp::StreamedMediaStreamPtr &stream)
+ *
+ * Emitted when a media stream is added to this channel.
+ *
+ * \param stream The media stream that was added.
+ * \sa streams(), streamsForType(), streamRemoved()
+ */
+
+/**
+ * \fn void StreamedMediaChannel::streamRemoved(const Tp::StreamedMediaStreamPtr &stream)
+ *
+ * Emitted when a media stream is removed from this channel.
+ *
+ * \param stream The media stream that was removed.
+ * \sa streams(), streamsForType(), streamAdded()
+ */
+
+/**
+ * \fn void StreamedMediaChannel::streamDirectionChanged(
+ * const Tp::StreamedMediaStreamPtr &stream, Tp::MediaStreamDirection direction,
+ * Tp::MediaStreamPendingSend pendingSend)
+ *
+ * Emitted when a media stream direction changes.
+ *
+ * \param stream The media stream which the direction changed.
+ * \param direction The new direction of the stream that changed.
+ * \param pendingSend The new pending send flags of the stream that changed.
+ * \sa StreamedMediaStream::direction()
+ */
+
+/**
+ * \fn void StreamedMediaChannel::streamStateChanged(
+ * const Tp::StreamedMediaStreamPtr &stream, Tp::MediaStreamState state)
+ *
+ * Emitted when a media stream state changes.
+ *
+ * \param stream The media stream which the state changed.
+ * \param state The new state of the stream that changed.
+ * \sa StreamedMediaStream::state()
+ */
+
+/**
+ * \fn void StreamedMediaChannel::streamError(
+ * const Tp::StreamedMediaStreamPtr &stream,
+ * Tp::StreamedMediaStreamError errorCode, const QString &errorMessage)
+ *
+ * Emitted when an error occurs on a media stream.
+ *
+ * \param stream The media stream which the error occurred.
+ * \param errorCode The error code.
+ * \param errorMessage The error message.
+ */
+
+/**
+ * \fn void StreamedMediaChannel::localHoldStateChanged(Tp::LocalHoldState state, Tp::LocalHoldStateReason reason);
+ *
+ * Emitted when the local hold state of this channel changes.
+ *
+ * \param state The new local hold state of this channel.
+ * \param reason The reason why the change occurred.
+ * \sa localHoldState(), localHoldStateReason()
+ */
+
+void StreamedMediaChannel::onStreamReady(PendingOperation *op)
+{
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ StreamedMediaStreamPtr stream = StreamedMediaStreamPtr::qObjectCast(pr->proxy());
+
+ if (op->isError()) {
+ mPriv->incompleteStreams.removeOne(stream);
+ if (!isReady(FeatureStreams) && mPriv->incompleteStreams.size() == 0) {
+ // let's not fail because a stream could not become ready
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true);
+ }
+ return;
+ }
+
+ // the stream was removed before become ready
+ if (!mPriv->incompleteStreams.contains(stream)) {
+ if (!isReady(FeatureStreams) && mPriv->incompleteStreams.size() == 0) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true);
+ }
+ return;
+ }
+
+ mPriv->incompleteStreams.removeOne(stream);
+ mPriv->streams.append(stream);
+
+ if (isReady(FeatureStreams)) {
+ emit streamAdded(stream);
+ }
+
+ if (!isReady(FeatureStreams) && mPriv->incompleteStreams.size() == 0) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true);
+ }
+}
+
+void StreamedMediaChannel::gotStreams(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<MediaStreamInfoList> reply = *watcher;
+ if (reply.isError()) {
+ warning().nospace() << "StreamedMedia.ListStreams failed with" <<
+ reply.error().name() << ": " << reply.error().message();
+
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams,
+ false, reply.error());
+ watcher->deleteLater();
+ return;
+ }
+
+ debug() << "Got reply to StreamedMedia::ListStreams()";
+
+ MediaStreamInfoList streamInfoList = reply.value();
+ if (streamInfoList.size() > 0) {
+ foreach (const MediaStreamInfo &streamInfo, streamInfoList) {
+ StreamedMediaStreamPtr stream = lookupStreamById(
+ streamInfo.identifier);
+ if (!stream) {
+ addStream(streamInfo);
+ } else {
+ onStreamDirectionChanged(streamInfo.identifier,
+ streamInfo.direction, streamInfo.pendingSendFlags);
+ onStreamStateChanged(streamInfo.identifier,
+ streamInfo.state);
+ }
+ }
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true);
+ }
+
+ watcher->deleteLater();
+}
+
+void StreamedMediaChannel::onStreamAdded(uint streamId,
+ uint contactHandle, uint streamType)
+{
+ if (lookupStreamById(streamId)) {
+ debug() << "Received StreamedMedia.StreamAdded for an existing "
+ "stream, ignoring";
+ return;
+ }
+
+ MediaStreamInfo streamInfo = {
+ streamId,
+ contactHandle,
+ streamType,
+ MediaStreamStateDisconnected,
+ MediaStreamDirectionReceive,
+ MediaStreamPendingLocalSend
+ };
+ addStream(streamInfo);
+}
+
+void StreamedMediaChannel::onStreamRemoved(uint streamId)
+{
+ debug() << "Received StreamedMedia.StreamRemoved for stream" <<
+ streamId;
+
+ StreamedMediaStreamPtr stream = lookupStreamById(streamId);
+ if (!stream) {
+ return;
+ }
+ bool incomplete = mPriv->incompleteStreams.contains(stream);
+ if (incomplete) {
+ mPriv->incompleteStreams.removeOne(stream);
+ } else {
+ mPriv->streams.removeOne(stream);
+ }
+
+ if (isReady(FeatureStreams) && !incomplete) {
+ emit streamRemoved(stream);
+ }
+
+ // the stream was added/removed before become ready
+ if (!isReady(FeatureStreams) &&
+ mPriv->streams.size() == 0 &&
+ mPriv->incompleteStreams.size() == 0) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true);
+ }
+}
+
+void StreamedMediaChannel::onStreamDirectionChanged(uint streamId,
+ uint streamDirection, uint streamPendingFlags)
+{
+ debug() << "Received StreamedMedia.StreamDirectionChanged for stream" <<
+ streamId << "with direction changed to" << streamDirection;
+
+ StreamedMediaStreamPtr stream = lookupStreamById(streamId);
+ if (!stream) {
+ return;
+ }
+
+ uint oldDirection = stream->direction();
+ uint oldPendingFlags = stream->pendingSend();
+
+ stream->gotDirection(streamDirection, streamPendingFlags);
+
+ if (oldDirection != streamDirection ||
+ oldPendingFlags != streamPendingFlags) {
+ emit streamDirectionChanged(stream,
+ (MediaStreamDirection) streamDirection,
+ (MediaStreamPendingSend) streamPendingFlags);
+ }
+}
+
+void StreamedMediaChannel::onStreamStateChanged(uint streamId,
+ uint streamState)
+{
+ debug() << "Received StreamedMedia.StreamStateChanged for stream" <<
+ streamId << "with state changed to" << streamState;
+
+ StreamedMediaStreamPtr stream = lookupStreamById(streamId);
+ if (!stream) {
+ return;
+ }
+
+ uint oldState = stream->state();
+
+ stream->gotStreamState(streamState);
+
+ if (oldState != streamState) {
+ emit streamStateChanged(stream, (MediaStreamState) streamState);
+ }
+}
+
+void StreamedMediaChannel::onStreamError(uint streamId,
+ uint errorCode, const QString &errorMessage)
+{
+ debug() << "Received StreamedMedia.StreamError for stream" <<
+ streamId << "with error code" << errorCode <<
+ "and message:" << errorMessage;
+
+ StreamedMediaStreamPtr stream = lookupStreamById(streamId);
+ if (!stream) {
+ return;
+ }
+
+ emit streamError(stream, (MediaStreamError) errorCode, errorMessage);
+}
+
+void StreamedMediaChannel::gotLocalHoldState(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<uint, uint> reply = *watcher;
+ if (reply.isError()) {
+ warning().nospace() << "StreamedMedia::Hold::GetHoldState()"
+ " failed with " << reply.error().name() << ": " <<
+ reply.error().message();
+
+ debug() << "Ignoring error getting hold state and assuming we're not "
+ "on hold";
+ onLocalHoldStateChanged(mPriv->localHoldState,
+ mPriv->localHoldStateReason);
+ watcher->deleteLater();
+ return;
+ }
+
+ debug() << "Got reply to StreamedMedia::Hold::GetHoldState()";
+ onLocalHoldStateChanged(reply.argumentAt<0>(), reply.argumentAt<1>());
+ watcher->deleteLater();
+}
+
+void StreamedMediaChannel::onLocalHoldStateChanged(uint localHoldState,
+ uint localHoldStateReason)
+{
+ bool changed = false;
+ if (mPriv->localHoldState != static_cast<LocalHoldState>(localHoldState) ||
+ mPriv->localHoldStateReason != static_cast<LocalHoldStateReason>(localHoldStateReason)) {
+ changed = true;
+ }
+
+ mPriv->localHoldState = static_cast<LocalHoldState>(localHoldState);
+ mPriv->localHoldStateReason = static_cast<LocalHoldStateReason>(localHoldStateReason);
+
+ if (!isReady(FeatureLocalHoldState)) {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureLocalHoldState, true);
+ } else {
+ if (changed) {
+ emit localHoldStateChanged(mPriv->localHoldState,
+ mPriv->localHoldStateReason);
+ }
+ }
+}
+
+StreamedMediaStreamPtr StreamedMediaChannel::addStream(const MediaStreamInfo &streamInfo)
+{
+ StreamedMediaStreamPtr stream = StreamedMediaStreamPtr(
+ new StreamedMediaStream(StreamedMediaChannelPtr(this), streamInfo));
+
+ mPriv->incompleteStreams.append(stream);
+ connect(stream->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onStreamReady(Tp::PendingOperation*)));
+ return stream;
+}
+
+StreamedMediaStreamPtr StreamedMediaChannel::lookupStreamById(uint streamId)
+{
+ foreach (const StreamedMediaStreamPtr &stream, mPriv->streams) {
+ if (stream->id() == streamId) {
+ return stream;
+ }
+ }
+
+ foreach (const StreamedMediaStreamPtr &stream, mPriv->incompleteStreams) {
+ if (stream->id() == streamId) {
+ return stream;
+ }
+ }
+
+ return StreamedMediaStreamPtr();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/streamed-media-channel.h b/qt4/TelepathyQt4/streamed-media-channel.h
new file mode 100644
index 000000000..811cd9aa4
--- /dev/null
+++ b/qt4/TelepathyQt4/streamed-media-channel.h
@@ -0,0 +1,228 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_streamed_media_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_streamed_media_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Object>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+class StreamedMediaChannel;
+
+typedef QList<StreamedMediaStreamPtr> StreamedMediaStreams;
+
+class TELEPATHY_QT4_EXPORT PendingStreamedMediaStreams : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingStreamedMediaStreams)
+
+public:
+ ~PendingStreamedMediaStreams();
+
+ StreamedMediaChannelPtr channel() const;
+
+ StreamedMediaStreams streams() const;
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotStreams(QDBusPendingCallWatcher *op);
+
+ TELEPATHY_QT4_NO_EXPORT void onStreamRemoved(const Tp::StreamedMediaStreamPtr &stream);
+ TELEPATHY_QT4_NO_EXPORT void onStreamReady(Tp::PendingOperation *op);
+
+private:
+ friend class StreamedMediaChannel;
+
+ TELEPATHY_QT4_NO_EXPORT PendingStreamedMediaStreams(const StreamedMediaChannelPtr &channel,
+ const ContactPtr &contact,
+ const QList<MediaStreamType> &types);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT StreamedMediaStream : public Object, private ReadyObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StreamedMediaStream)
+
+public:
+ enum SendingState {
+ SendingStateNone = 0,
+ SendingStatePendingSend = 1,
+ SendingStateSending = 2
+ };
+
+ ~StreamedMediaStream();
+
+ StreamedMediaChannelPtr channel() const;
+
+ uint id() const;
+
+ ContactPtr contact() const;
+
+ MediaStreamState state() const;
+ MediaStreamType type() const;
+
+ SendingState localSendingState() const;
+ SendingState remoteSendingState() const;
+
+ bool sending() const;
+ bool receiving() const;
+
+ bool localSendingRequested() const;
+ bool remoteSendingRequested() const;
+
+ MediaStreamDirection direction() const;
+ MediaStreamPendingSend pendingSend() const;
+
+ PendingOperation *requestSending(bool send);
+ PendingOperation *requestReceiving(bool receive);
+
+ PendingOperation *requestDirection(MediaStreamDirection direction);
+ PendingOperation *requestDirection(bool send, bool receive);
+
+ PendingOperation *startDTMFTone(DTMFEvent event);
+ PendingOperation *stopDTMFTone();
+
+Q_SIGNALS:
+ void localSendingStateChanged(Tp::StreamedMediaStream::SendingState localSendingState);
+ void remoteSendingStateChanged(Tp::StreamedMediaStream::SendingState remoteSendingState);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void gotContact(Tp::PendingOperation *op);
+
+private:
+ friend class PendingStreamedMediaStreams;
+ friend class StreamedMediaChannel;
+
+ static const Feature FeatureCore;
+
+ TELEPATHY_QT4_NO_EXPORT StreamedMediaStream(const StreamedMediaChannelPtr &channel, const MediaStreamInfo &info);
+
+ TELEPATHY_QT4_NO_EXPORT void gotDirection(uint direction, uint pendingSend);
+ TELEPATHY_QT4_NO_EXPORT void gotStreamState(uint state);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+class TELEPATHY_QT4_EXPORT StreamedMediaChannel : public Channel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(StreamedMediaChannel)
+ Q_ENUMS(StateChangeReason)
+
+public:
+ static const Feature FeatureCore;
+ static const Feature FeatureStreams;
+ static const Feature FeatureLocalHoldState;
+
+ enum StateChangeReason {
+ StateChangeReasonUnknown = 0,
+ StateChangeReasonUserRequested = 1
+ };
+
+ static StreamedMediaChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~StreamedMediaChannel();
+
+ bool awaitingLocalAnswer() const;
+ bool awaitingRemoteAnswer() const;
+
+ PendingOperation *acceptCall();
+ PendingOperation *hangupCall();
+
+ StreamedMediaStreams streams() const;
+ StreamedMediaStreams streamsForType(MediaStreamType type) const;
+
+ PendingStreamedMediaStreams *requestStream(const ContactPtr &contact, MediaStreamType type);
+ PendingStreamedMediaStreams *requestStreams(const ContactPtr &contact, QList<MediaStreamType> types);
+
+ PendingOperation *removeStream(const StreamedMediaStreamPtr &stream);
+ PendingOperation *removeStreams(const StreamedMediaStreams &streams);
+
+ bool handlerStreamingRequired() const;
+
+ LocalHoldState localHoldState() const;
+ LocalHoldStateReason localHoldStateReason() const;
+ PendingOperation *requestHold(bool hold);
+
+Q_SIGNALS:
+ void streamAdded(const Tp::StreamedMediaStreamPtr &stream);
+ void streamRemoved(const Tp::StreamedMediaStreamPtr &stream);
+ void streamDirectionChanged(const Tp::StreamedMediaStreamPtr &stream,
+ Tp::MediaStreamDirection direction,
+ Tp::MediaStreamPendingSend pendingSend);
+ void streamStateChanged(const Tp::StreamedMediaStreamPtr &stream,
+ Tp::MediaStreamState state);
+ void streamError(const Tp::StreamedMediaStreamPtr &stream,
+ Tp::MediaStreamError errorCode,
+ const QString &errorMessage);
+
+ void localHoldStateChanged(Tp::LocalHoldState state,
+ Tp::LocalHoldStateReason reason);
+
+protected:
+ StreamedMediaChannel(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties,
+ const Feature &coreFeature = StreamedMediaChannel::FeatureCore);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onStreamReady(Tp::PendingOperation *op);
+
+ TELEPATHY_QT4_NO_EXPORT void gotStreams(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void onStreamAdded(uint, uint, uint);
+ TELEPATHY_QT4_NO_EXPORT void onStreamRemoved(uint);
+ TELEPATHY_QT4_NO_EXPORT void onStreamDirectionChanged(uint, uint, uint);
+ TELEPATHY_QT4_NO_EXPORT void onStreamStateChanged(uint streamId, uint streamState);
+ TELEPATHY_QT4_NO_EXPORT void onStreamError(uint, uint, const QString &);
+
+ TELEPATHY_QT4_NO_EXPORT void gotLocalHoldState(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void onLocalHoldStateChanged(uint, uint);
+
+private:
+ friend class PendingStreamedMediaStreams;
+
+ StreamedMediaStreamPtr addStream(const MediaStreamInfo &streamInfo);
+ StreamedMediaStreamPtr lookupStreamById(uint streamId);
+
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/test-backdoors.cpp b/qt4/TelepathyQt4/test-backdoors.cpp
new file mode 100644
index 000000000..50fa420c3
--- /dev/null
+++ b/qt4/TelepathyQt4/test-backdoors.cpp
@@ -0,0 +1,50 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/test-backdoors.h>
+
+#include <TelepathyQt4/DBusProxy>
+
+namespace Tp
+{
+
+void TestBackdoors::invalidateProxy(DBusProxy *proxy, const QString &reason, const QString &message)
+{
+ Q_ASSERT(proxy != 0);
+ Q_ASSERT(proxy->isValid());
+
+ proxy->invalidate(reason, message);
+}
+
+ConnectionCapabilities TestBackdoors::createConnectionCapabilities(
+ const RequestableChannelClassSpecList &rccSpecs)
+{
+ return ConnectionCapabilities(rccSpecs);
+}
+
+ContactCapabilities TestBackdoors::createContactCapabilities(
+ const RequestableChannelClassSpecList &rccSpecs, bool specificToContact)
+{
+ return ContactCapabilities(rccSpecs, specificToContact);
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/test-backdoors.h b/qt4/TelepathyQt4/test-backdoors.h
new file mode 100644
index 000000000..79e7d6bb7
--- /dev/null
+++ b/qt4/TelepathyQt4/test-backdoors.h
@@ -0,0 +1,59 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008-2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_test_backdoors_h_HEADER_GUARD_
+#define _TelepathyQt4_test_backdoors_h_HEADER_GUARD_
+
+#ifdef IN_TELEPATHY_QT4_HEADER
+#error "This file is an internal header and should never be included by a public one"
+#endif
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ContactCapabilities>
+
+#include <QString>
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+namespace Tp
+{
+
+class DBusProxy;
+
+// Exported so the tests can use it even if they link dynamically
+// The header is not installed though, so this should be considered private API
+struct TELEPATHY_QT4_EXPORT TestBackdoors
+{
+ static void invalidateProxy(DBusProxy *proxy, const QString &reason, const QString &message);
+
+ static ConnectionCapabilities createConnectionCapabilities(
+ const RequestableChannelClassSpecList &rccSpecs);
+ static ContactCapabilities createContactCapabilities(
+ const RequestableChannelClassSpecList &rccSpecs, bool specificToContact);
+};
+
+} // Tp
+
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+#endif
diff --git a/qt4/TelepathyQt4/text-channel.cpp b/qt4/TelepathyQt4/text-channel.cpp
new file mode 100644
index 000000000..91a34ac94
--- /dev/null
+++ b/qt4/TelepathyQt4/text-channel.cpp
@@ -0,0 +1,1277 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/TextChannel>
+
+#include "TelepathyQt4/_gen/text-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingFailure>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReceivedMessage>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include <QDateTime>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT TextChannel::Private
+{
+ Private(TextChannel *parent);
+ ~Private();
+
+ static void introspectMessageQueue(Private *self);
+ static void introspectMessageCapabilities(Private *self);
+ static void introspectMessageSentSignal(Private *self);
+ static void enableChatStateNotifications(Private *self);
+
+ void updateInitialMessages();
+ void updateCapabilities();
+
+ void processMessageQueue();
+ void processChatStateQueue();
+
+ void contactLost(uint handle);
+ void contactFound(ContactPtr contact);
+
+ // Public object
+ TextChannel *parent;
+
+ Client::ChannelTypeTextInterface *textInterface;
+ Client::DBus::PropertiesInterface *properties;
+
+ ReadinessHelper *readinessHelper;
+
+ // FeatureMessageCapabilities and FeatureMessageQueue
+ QVariantMap props;
+ bool getAllInFlight;
+ bool gotProperties;
+
+ // requires FeatureMessageCapabilities
+ QStringList supportedContentTypes;
+ MessagePartSupportFlags messagePartSupport;
+ DeliveryReportingSupportFlags deliveryReportingSupport;
+
+ // FeatureMessageQueue
+ bool initialMessagesReceived;
+ struct MessageEvent
+ {
+ MessageEvent(const ReceivedMessage &message)
+ : isMessage(true), message(message),
+ removed(0)
+ { }
+ MessageEvent(uint removed)
+ : isMessage(false), message(), removed(removed)
+ { }
+
+ bool isMessage;
+ ReceivedMessage message;
+ uint removed;
+ };
+ QList<ReceivedMessage> messages;
+ QList<MessageEvent *> incompleteMessages;
+ QHash<QDBusPendingCallWatcher *, UIntList> acknowledgeBatches;
+
+ // FeatureChatState
+ struct ChatStateEvent
+ {
+ ChatStateEvent(uint contactHandle, uint state)
+ : contactHandle(contactHandle), state(state)
+ { }
+
+ ContactPtr contact;
+ uint contactHandle;
+ uint state;
+ };
+ QList<ChatStateEvent *> chatStateQueue;
+ QHash<ContactPtr, ChannelChatState> chatStates;
+
+ QSet<uint> awaitingContacts;
+};
+
+TextChannel::Private::Private(TextChannel *parent)
+ : parent(parent),
+ textInterface(parent->interface<Client::ChannelTypeTextInterface>()),
+ properties(parent->interface<Client::DBus::PropertiesInterface>()),
+ readinessHelper(parent->readinessHelper()),
+ getAllInFlight(false),
+ gotProperties(false),
+ messagePartSupport(0),
+ deliveryReportingSupport(0),
+ initialMessagesReceived(false)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableMessageQueue(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMessageQueue,
+ this);
+ introspectables[FeatureMessageQueue] = introspectableMessageQueue;
+
+ ReadinessHelper::Introspectable introspectableMessageCapabilities(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMessageCapabilities,
+ this);
+ introspectables[FeatureMessageCapabilities] = introspectableMessageCapabilities;
+
+ ReadinessHelper::Introspectable introspectableMessageSentSignal(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList(), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::introspectMessageSentSignal,
+ this);
+ introspectables[FeatureMessageSentSignal] = introspectableMessageSentSignal;
+
+ ReadinessHelper::Introspectable introspectableChatState(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CHAT_STATE), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &Private::enableChatStateNotifications,
+ this);
+ introspectables[FeatureChatState] = introspectableChatState;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+TextChannel::Private::~Private()
+{
+ foreach (MessageEvent *e, incompleteMessages) {
+ delete e;
+ }
+
+ foreach (ChatStateEvent *e, chatStateQueue) {
+ delete e;
+ }
+}
+
+void TextChannel::Private::introspectMessageQueue(
+ TextChannel::Private *self)
+{
+ TextChannel *parent = self->parent;
+
+ if (parent->hasMessagesInterface()) {
+ Client::ChannelInterfaceMessagesInterface *messagesInterface =
+ parent->interface<Client::ChannelInterfaceMessagesInterface>();
+
+ // FeatureMessageQueue needs signal connections + Get (but we
+ // might as well do GetAll and reduce the number of code paths)
+ parent->connect(messagesInterface,
+ SIGNAL(MessageReceived(Tp::MessagePartList)),
+ SLOT(onMessageReceived(Tp::MessagePartList)));
+ parent->connect(messagesInterface,
+ SIGNAL(PendingMessagesRemoved(Tp::UIntList)),
+ SLOT(onPendingMessagesRemoved(Tp::UIntList)));
+
+ if (!self->gotProperties && !self->getAllInFlight) {
+ self->getAllInFlight = true;
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MESSAGES)),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotProperties(QDBusPendingCallWatcher*)));
+ } else if (self->gotProperties) {
+ self->updateInitialMessages();
+ }
+ } else {
+ // FeatureMessageQueue needs signal connections + ListPendingMessages
+ parent->connect(self->textInterface,
+ SIGNAL(Received(uint,uint,uint,uint,uint,QString)),
+ SLOT(onTextReceived(uint,uint,uint,uint,uint,const QString)));
+
+ // we present SendError signals as if they were incoming
+ // messages, to be consistent with Messages
+ parent->connect(self->textInterface,
+ SIGNAL(SendError(uint,uint,uint,QString)),
+ SLOT(onTextSendError(uint,uint,uint,QString)));
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ self->textInterface->ListPendingMessages(false), parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotPendingMessages(QDBusPendingCallWatcher*)));
+ }
+}
+
+void TextChannel::Private::introspectMessageCapabilities(
+ TextChannel::Private *self)
+{
+ TextChannel *parent = self->parent;
+
+ if (parent->hasMessagesInterface()) {
+ if (!self->gotProperties && !self->getAllInFlight) {
+ self->getAllInFlight = true;
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ self->properties->GetAll(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MESSAGES)),
+ parent);
+ parent->connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(gotProperties(QDBusPendingCallWatcher*)));
+ } else if (self->gotProperties) {
+ self->updateCapabilities();
+ }
+ } else {
+ self->supportedContentTypes =
+ (QStringList(QLatin1String("text/plain")));
+ parent->readinessHelper()->setIntrospectCompleted(
+ FeatureMessageCapabilities, true);
+ }
+}
+
+void TextChannel::Private::introspectMessageSentSignal(
+ TextChannel::Private *self)
+{
+ TextChannel *parent = self->parent;
+
+ if (parent->hasMessagesInterface()) {
+ Client::ChannelInterfaceMessagesInterface *messagesInterface =
+ parent->interface<Client::ChannelInterfaceMessagesInterface>();
+
+ parent->connect(messagesInterface,
+ SIGNAL(MessageSent(Tp::MessagePartList,uint,QString)),
+ SLOT(onMessageSent(Tp::MessagePartList,uint,QString)));
+ } else {
+ parent->connect(self->textInterface,
+ SIGNAL(Sent(uint,uint,QString)),
+ SLOT(onTextSent(uint,uint,QString)));
+ }
+
+ self->readinessHelper->setIntrospectCompleted(FeatureMessageSentSignal, true);
+}
+
+void TextChannel::Private::enableChatStateNotifications(
+ TextChannel::Private *self)
+{
+ TextChannel *parent = self->parent;
+ Client::ChannelInterfaceChatStateInterface *chatStateInterface =
+ parent->interface<Client::ChannelInterfaceChatStateInterface>();
+
+ parent->connect(chatStateInterface,
+ SIGNAL(ChatStateChanged(uint,uint)),
+ SLOT(onChatStateChanged(uint,uint)));
+
+ // FIXME fd.o#24882 - Download contacts' initial chat states
+
+ self->readinessHelper->setIntrospectCompleted(FeatureChatState, true);
+}
+
+void TextChannel::Private::updateInitialMessages()
+{
+ if (!readinessHelper->requestedFeatures().contains(FeatureMessageQueue) ||
+ readinessHelper->isReady(Features() << FeatureMessageQueue)) {
+ return;
+ }
+
+ Q_ASSERT(!initialMessagesReceived);
+ initialMessagesReceived = true;
+
+ MessagePartListList messages = qdbus_cast<MessagePartListList>(
+ props[QLatin1String("PendingMessages")]);
+ if (messages.isEmpty()) {
+ debug() << "Message queue empty: FeatureMessageQueue is now ready";
+ readinessHelper->setIntrospectCompleted(FeatureMessageQueue, true);
+ } else {
+ foreach (const MessagePartList &message, messages) {
+ parent->onMessageReceived(message);
+ }
+ }
+}
+
+void TextChannel::Private::updateCapabilities()
+{
+ if (!readinessHelper->requestedFeatures().contains(FeatureMessageCapabilities) ||
+ readinessHelper->isReady(Features() << FeatureMessageCapabilities)) {
+ return;
+ }
+
+ supportedContentTypes = qdbus_cast<QStringList>(
+ props[QLatin1String("SupportedContentTypes")]);
+ if (supportedContentTypes.isEmpty()) {
+ supportedContentTypes << QLatin1String("text/plain");
+ }
+ messagePartSupport = MessagePartSupportFlags(qdbus_cast<uint>(
+ props[QLatin1String("MessagePartSupportFlags")]));
+ deliveryReportingSupport = DeliveryReportingSupportFlags(
+ qdbus_cast<uint>(props[QLatin1String("DeliveryReportingSupport")]));
+ readinessHelper->setIntrospectCompleted(FeatureMessageCapabilities, true);
+}
+
+void TextChannel::Private::processMessageQueue()
+{
+ // Proceed as far as we can with the processing of incoming messages
+ // and message-removal events; message IDs aren't necessarily globally
+ // unique, so we need to process them in the correct order relative
+ // to incoming messages
+ while (!incompleteMessages.isEmpty()) {
+ const MessageEvent *e = incompleteMessages.first();
+ debug() << "MessageEvent:" << reinterpret_cast<const void *>(e);
+
+ if (e->isMessage) {
+ if (e->message.senderHandle() != 0 &&
+ !e->message.sender()) {
+ // the message doesn't have a sender Contact, but needs one.
+ // We'll have to stop processing here, and come back to it
+ // when we have more Contact objects
+ break;
+ }
+
+ // if we reach here, the message is ready
+ debug() << "Message is usable, copying to main queue";
+ messages << e->message;
+ emit parent->messageReceived(e->message);
+ } else {
+ // forget about the message(s) with ID e->removed (there should be
+ // at most one under normal circumstances)
+ int i = 0;
+ while (i < messages.size()) {
+ if (messages.at(i).pendingId() == e->removed) {
+ emit parent->pendingMessageRemoved(messages.at(i));
+ messages.removeAt(i);
+ } else {
+ i++;
+ }
+ }
+ }
+
+ debug() << "Dropping first event";
+ delete incompleteMessages.takeFirst();
+ }
+
+ if (incompleteMessages.isEmpty()) {
+ if (readinessHelper->requestedFeatures().contains(FeatureMessageQueue) &&
+ !readinessHelper->isReady(Features() << FeatureMessageQueue)) {
+ debug() << "incompleteMessages empty for the first time: "
+ "FeatureMessageQueue is now ready";
+ readinessHelper->setIntrospectCompleted(FeatureMessageQueue, true);
+ }
+ return;
+ }
+
+ // What Contact objects do we need in order to proceed, ignoring those
+ // for which we've already sent a request?
+ HandleIdentifierMap contactsRequired;
+ foreach (const MessageEvent *e, incompleteMessages) {
+ if (e->isMessage) {
+ uint handle = e->message.senderHandle();
+ if (handle != 0 && !e->message.sender()
+ && !awaitingContacts.contains(handle)) {
+ contactsRequired.insert(handle, e->message.senderId());
+ }
+ }
+ }
+
+ if (contactsRequired.isEmpty()) {
+ return;
+ }
+
+ ConnectionPtr conn = parent->connection();
+ conn->lowlevel()->injectContactIds(contactsRequired);
+
+ parent->connect(conn->contactManager()->contactsForHandles(
+ contactsRequired.keys()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactsFinished(Tp::PendingOperation*)));
+
+ awaitingContacts |= contactsRequired.keys().toSet();
+}
+
+void TextChannel::Private::processChatStateQueue()
+{
+ while (!chatStateQueue.isEmpty()) {
+ const ChatStateEvent *e = chatStateQueue.first();
+ debug() << "ChatStateEvent:" << reinterpret_cast<const void *>(e);
+
+ if (e->contact.isNull()) {
+ // the chat state Contact object wasn't retrieved yet, but needs
+ // one. We'll have to stop processing here, and come back to it
+ // when we have more Contact objects
+ break;
+ }
+
+ chatStates.insert(e->contact, (ChannelChatState) e->state);
+
+ // if we reach here, the Contact object is ready
+ emit parent->chatStateChanged(e->contact, (ChannelChatState) e->state);
+
+ debug() << "Dropping first event";
+ delete chatStateQueue.takeFirst();
+ }
+
+ // What Contact objects do we need in order to proceed, ignoring those
+ // for which we've already sent a request?
+ QSet<uint> contactsRequired;
+ foreach (const ChatStateEvent *e, chatStateQueue) {
+ if (!e->contact &&
+ !awaitingContacts.contains(e->contactHandle)) {
+ contactsRequired << e->contactHandle;
+ }
+ }
+
+ if (contactsRequired.isEmpty()) {
+ return;
+ }
+
+ // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids
+ // from ChatState.
+ parent->connect(parent->connection()->contactManager()->contactsForHandles(
+ contactsRequired.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onContactsFinished(Tp::PendingOperation*)));
+
+ awaitingContacts |= contactsRequired;
+}
+
+void TextChannel::Private::contactLost(uint handle)
+{
+ // we're not going to get a Contact object for this handle, so mark the
+ // messages from that handle as "unknown sender"
+ foreach (MessageEvent *e, incompleteMessages) {
+ if (e->isMessage && e->message.senderHandle() == handle
+ && !e->message.sender()) {
+ e->message.clearSenderHandle();
+ }
+ }
+
+ // there is no point in sending chat state notifications for unknown
+ // contacts, removing chat state events from queue that refer to this handle
+ foreach (ChatStateEvent *e, chatStateQueue) {
+ if (e->contactHandle == handle) {
+ chatStateQueue.removeOne(e);
+ delete e;
+ }
+ }
+}
+
+void TextChannel::Private::contactFound(ContactPtr contact)
+{
+ uint handle = contact->handle().at(0);
+
+ foreach (MessageEvent *e, incompleteMessages) {
+ if (e->isMessage && e->message.senderHandle() == handle
+ && !e->message.sender()) {
+ e->message.setSender(contact);
+ }
+ }
+
+ foreach (ChatStateEvent *e, chatStateQueue) {
+ if (e->contactHandle == handle) {
+ e->contact = contact;
+ }
+ }
+}
+
+/**
+ * \class TextChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/text-channel.h <TelepathyQt4/TextChannel>
+ *
+ * \brief The TextChannel class represents a Telepathy channel of type Text.
+ *
+ * For more details, please refer to \telepathy_spec.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * TextChannel object usable.
+ *
+ * This is currently the same as Channel::FeatureCore, but may change to include more.
+ *
+ * When calling isReady(), becomeReady(), this feature is implicitly added
+ * to the requested features.
+ */
+const Feature TextChannel::FeatureCore = Feature(QLatin1String(Channel::staticMetaObject.className()), 0, true);
+
+/**
+ * Feature used in order to access the message queue info.
+ *
+ * See message queue methods' documentation for more details.
+ *
+ * \sa messageQueue(), messageReceived(), pendingMessageRemoved(), forget(), acknowledge()
+ */
+const Feature TextChannel::FeatureMessageQueue = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 0);
+
+/**
+ * Feature used in order to access message capabilities info.
+ *
+ * See message capabilities methods' documentation for more details.
+ *
+ * \sa supportedContentTypes(), messagePartSupport(), deliveryReportingSupport()
+ */
+const Feature TextChannel::FeatureMessageCapabilities = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 1);
+
+/**
+ * Feature used in order to receive notification when a message is sent.
+ *
+ * \sa messageSent()
+ */
+const Feature TextChannel::FeatureMessageSentSignal = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 2);
+
+/**
+ * Feature used in order to keep track of chat state changes.
+ *
+ * See chat state methods' documentation for more details.
+ *
+ * \sa chatState(), chatStateChanged()
+ */
+const Feature TextChannel::FeatureChatState = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 3);
+
+/**
+ * \fn void TextChannel::messageSent(const Tp::Message &message,
+ * Tp::MessageSendingFlags flags,
+ * const QString &sentMessageToken)
+ *
+ * Emitted when a message is sent, if the TextChannel::FeatureMessageSentSignal
+ * has been enabled.
+ *
+ * This signal is emitted regardless of whether the message is sent by this
+ * client, or another client using the same channel via D-Bus.
+ *
+ * \param message A message. This may differ slightly from what the client
+ * requested to send, for instance if it has been altered due
+ * to limitations of the instant messaging protocol used.
+ * \param flags #MessageSendingFlags that were in effect when the message was
+ * sent. Clients can use these in conjunction with
+ * deliveryReportingSupport() to determine whether delivery
+ * reporting can be expected.
+ * \param sentMessageToken Either an empty QString, or an opaque token used
+ * to match the message to any delivery reports.
+ */
+
+/**
+ * \fn void TextChannel::messageReceived(const Tp::ReceivedMessage &message)
+ *
+ * Emitted when a message is added to messageQueue(), if the
+ * TextChannel::FeatureMessageQueue Feature has been enabled.
+ *
+ * This occurs slightly later than the message being received over D-Bus;
+ * see messageQueue() for details.
+ *
+ * \param message The message received.
+ * \sa messageQueue(), acknowledge(), forget()
+ */
+
+/**
+ * \fn void TextChannel::pendingMessageRemoved(
+ * const Tp::ReceivedMessage &message)
+ *
+ * Emitted when a message is removed from messageQueue(), if the
+ * TextChannel::FeatureMessageQueue Feature has been enabled. See messageQueue() for the
+ * circumstances in which this happens.
+ *
+ * \param message The message removed.
+ * \sa messageQueue(), acknowledge(), forget()
+ */
+
+/**
+ * \fn void TextChannel::chatStateChanged(const Tp::ContactPtr &contact,
+ * ChannelChatState state)
+ *
+ * Emitted when the state of a member of the channel has changed, if the
+ * TextChannel::FeatureChatState feature has been enabled.
+ *
+ * Local state changes are also emitted here.
+ *
+ * \param contact The contact whose chat state changed.
+ * \param state The new chat state for \a contact.
+ * \sa chatState()
+ */
+
+/**
+ * Create a new TextChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A TextChannelPtr object pointing to the newly created
+ * TextChannel object.
+ */
+TextChannelPtr TextChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return TextChannelPtr(new TextChannel(connection, objectPath,
+ immutableProperties, TextChannel::FeatureCore));
+}
+
+/**
+ * Construct a new TextChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on TextChannel::FeatureCore.
+ */
+TextChannel::TextChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : Channel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+TextChannel::~TextChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return whether this channel supports the Messages interface.
+ *
+ * If the interface is not supported, some advanced functionality will be unavailable.
+ *
+ * This method requires TextChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the Messages interface is supported, \c false otherwise.
+ */
+bool TextChannel::hasMessagesInterface() const
+{
+ return interfaces().contains(QLatin1String(
+ TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MESSAGES));
+}
+
+/**
+ * Return whether this channel supports the ChatState interface.
+ *
+ * If the interface is not supported, requestChatState() will fail and all contacts' chat states
+ * will appear to be #ChannelChatStateInactive.
+ *
+ * This method requires TextChannel::FeatureCore to be ready.
+ *
+ * \return \c true if the ChatState interface is supported, \c false otherwise.
+ * \sa requestChatState(), chatStateChanged()
+ */
+bool TextChannel::hasChatStateInterface() const
+{
+ return interfaces().contains(QLatin1String(
+ TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CHAT_STATE));
+}
+
+/**
+ * Return whether contacts can be invited into this channel using
+ * inviteContacts() (which is equivalent to Channel::groupAddContacts()).
+ *
+ * Whether this is the case depends on the underlying protocol, the type of channel,
+ * and the user's privileges (in some chatrooms, only a privileged user
+ * can invite other contacts).
+ *
+ * This is an alias for Channel::groupCanAddContacts(), to indicate its meaning more
+ * clearly for Text channels.
+ *
+ * This method requires Channel::FeatureCore to be ready.
+ *
+ * \return \c true if contacts can be invited, \c false otherwise.
+ * \sa inviteContacts(), Channel::groupCanAddContacts(), Channel::groupAddContacts()
+ */
+bool TextChannel::canInviteContacts() const
+{
+ return groupCanAddContacts();
+}
+
+/* <!--x--> in the block below is used to escape the star-slash sequence */
+/**
+ * Return a list of supported MIME content types for messages on this channel.
+ *
+ * For a simple text channel this will be a list containing one item,
+ * "text/plain".
+ *
+ * This list may contain the special value "*<!--x-->/<!--x-->*", which
+ * indicates that any content type is supported.
+ *
+ * This method requires TextChannel::FeatureMessageCapabilities to be ready.
+ *
+ * \return The list of MIME content types.
+ */
+QStringList TextChannel::supportedContentTypes() const
+{
+ return mPriv->supportedContentTypes;
+}
+
+/**
+ * Return a set of flags indicating support for multi-part messages on this
+ * channel.
+ *
+ * This is zero on simple text channels, or greater than zero if
+ * there is partial or full support for multi-part messages.
+ *
+ * This method requires TextChannel::FeatureMessageCapabilities to be ready.
+ *
+ * \return The flags as #MessagePartSupportFlags.
+ */
+MessagePartSupportFlags TextChannel::messagePartSupport() const
+{
+ return mPriv->messagePartSupport;
+}
+
+/**
+ * Return a set of flags indicating support for delivery reporting on this
+ * channel.
+ *
+ * This is zero if there are no particular guarantees, or greater
+ * than zero if delivery reports can be expected under certain circumstances.
+ *
+ * This method requires TextChannel::FeatureMessageCapabilities to be ready.
+ *
+ * \return The flags as #DeliveryReportingSupportFlags.
+ */
+DeliveryReportingSupportFlags TextChannel::deliveryReportingSupport() const
+{
+ return mPriv->deliveryReportingSupport;
+}
+
+/**
+ * Return a list of messages received in this channel.
+ *
+ * Messages are added to this list when they are received from the instant
+ * messaging service; the messageReceived() signal is emitted.
+ *
+ * There is a small delay between the message being received over D-Bus and
+ * becoming available to users of this C++ API, since a small amount of
+ * additional information needs to be fetched. However, the relative ordering
+ * of all the messages in a channel is preserved.
+ *
+ * Messages are removed from this list when they are acknowledged with the
+ * acknowledge() or forget() methods. On channels where hasMessagesInterface()
+ * returns \c true, they will also be removed when acknowledged by a different
+ * client. In either case, the pendingMessageRemoved() signal is emitted.
+ *
+ * This method requires TextChannel::FeatureMessageQueue to be ready.
+ *
+ * \return A list of ReceivedMessage objects.
+ * \sa messageReceived()
+ */
+QList<ReceivedMessage> TextChannel::messageQueue() const
+{
+ return mPriv->messages;
+}
+
+/**
+ * Return the current chat state for \a contact.
+ *
+ * If hasChatStateInterface() returns \c false, this method will always return
+ * #ChannelChatStateInactive.
+ *
+ * This method requires TextChannel::FeatureChatState to be ready.
+ *
+ * \return The contact chat state as #ChannelChatState.
+ */
+ChannelChatState TextChannel::chatState(const ContactPtr &contact) const
+{
+ if (!isReady(FeatureChatState)) {
+ warning() << "TextChannel::chatState() used with "
+ "FeatureChatState not ready";
+ return ChannelChatStateInactive;
+ }
+
+ if (mPriv->chatStates.contains(contact)) {
+ return mPriv->chatStates.value(contact);
+ }
+ return ChannelChatStateInactive;
+}
+
+void TextChannel::onAcknowledgePendingMessagesReply(
+ QDBusPendingCallWatcher *watcher)
+{
+ UIntList ids = mPriv->acknowledgeBatches.value(watcher);
+ QDBusPendingReply<> reply = *watcher;
+
+ if (reply.isError()) {
+ // One of the IDs was bad, and we can't know which one. Recover by
+ // doing as much as possible, and hope for the best...
+ debug() << "Recovering from AcknowledgePendingMessages failure for: "
+ << ids;
+ foreach (uint id, ids) {
+ mPriv->textInterface->AcknowledgePendingMessages(UIntList() << id);
+ }
+ }
+
+ mPriv->acknowledgeBatches.remove(watcher);
+ watcher->deleteLater();
+}
+
+/**
+ * Acknowledge that received messages have been displayed to the user.
+ *
+ * Note that this method should only be called by the main handler of a channel, usually
+ * meaning the user interface process that displays the channel to the user
+ * (when a channel dispatcher is used, the handler must acknowledge messages,
+ * and other approvers or observers must not acknowledge messages).
+ *
+ * Processes other than the main handler of a channel can free memory used
+ * by the library by calling forget() instead.
+ *
+ * This method requires TextChannel::FeatureMessageQueue to be ready.
+ *
+ * \param messages A list of received messages that have now been displayed.
+ * \sa forget(), messageQueue(), messageReceived(), pendingMessageRemoved()
+ */
+void TextChannel::acknowledge(const QList<ReceivedMessage> &messages)
+{
+ UIntList ids;
+
+ foreach (const ReceivedMessage &m, messages) {
+ if (m.isFromChannel(TextChannelPtr(this))) {
+ ids << m.pendingId();
+ } else {
+ warning() << "message did not come from this channel, ignoring";
+ }
+ }
+
+ if (ids.isEmpty()) {
+ return;
+ }
+
+ // we're going to acknowledge these messages (or as many as possible, if
+ // we lose a race with another acknowledging process), so let's remove
+ // them from the list immediately
+ forget(messages);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
+ mPriv->textInterface->AcknowledgePendingMessages(ids),
+ this);
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ SLOT(onAcknowledgePendingMessagesReply(QDBusPendingCallWatcher*)));
+ mPriv->acknowledgeBatches[watcher] = ids;
+}
+
+/**
+ * Remove messages from the message queue without acknowledging them.
+ *
+ * Note that this method frees memory used by the library, but
+ * does not free the corresponding memory in the CM process.
+ * It should be used by clients that are not the main handler for a channel;
+ * the main handler for a channel should use acknowledge() instead.
+ *
+ * This method requires TextChannel::FeatureMessageQueue to be ready.
+ *
+ * \param messages A list of received messages that have now been processed.
+ * \sa acknowledge(), messageQueue(), messageReceived(), pendingMessageRemoved()
+ */
+void TextChannel::forget(const QList<ReceivedMessage> &messages)
+{
+ foreach (const ReceivedMessage &m, messages) {
+ if (!m.isFromChannel(TextChannelPtr(this))) {
+ warning() << "message did not come from this channel, ignoring";
+ } else if (mPriv->messages.removeOne(m)) {
+ emit pendingMessageRemoved(m);
+ }
+ }
+}
+
+/**
+ * Request that a message be sent on this channel.
+ *
+ * When the message has been submitted for delivery,
+ * this method will return and the messageSent() signal will be emitted.
+ *
+ * If the message cannot be submitted for delivery, the returned pending operation will fail and no
+ * signal is emitted.
+ *
+ * This method requires TextChannel::FeatureCore to be ready.
+ *
+ * \param text The message body.
+ * \param type The message type.
+ * \param flags Flags affecting how the message is sent.
+ * Note that the channel may ignore some or all flags, depending on
+ * deliveryReportingSupport(); the flags that were handled by the CM are provided in
+ * messageSent().
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the message has been submitted for delivery.
+ * \sa messageSent()
+ */
+PendingSendMessage *TextChannel::send(const QString &text,
+ ChannelTextMessageType type, MessageSendingFlags flags)
+{
+ Message m(type, text);
+ PendingSendMessage *op = new PendingSendMessage(TextChannelPtr(this), m);
+
+ if (hasMessagesInterface()) {
+ Client::ChannelInterfaceMessagesInterface *messagesInterface =
+ interface<Client::ChannelInterfaceMessagesInterface>();
+
+ connect(new QDBusPendingCallWatcher(
+ messagesInterface->SendMessage(m.parts(),
+ (uint) flags)),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ op,
+ SLOT(onMessageSent(QDBusPendingCallWatcher*)));
+ } else {
+ connect(new QDBusPendingCallWatcher(mPriv->textInterface->Send(type, text)),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ op,
+ SLOT(onTextSent(QDBusPendingCallWatcher*)));
+ }
+ return op;
+}
+
+/**
+ * Request that a message be sent on this channel.
+ *
+ * When the message has been submitted for delivery,
+ * this method will return and the messageSent() signal will be emitted.
+ *
+ * If the message cannot be submitted for delivery, the returned pending operation will fail and no
+ * signal is emitted.
+ *
+ * This method requires TextChannel::FeatureCore to be ready.
+ *
+ * \param part The message parts.
+ * \param flags Flags affecting how the message is sent.
+ * Note that the channel may ignore some or all flags, depending on
+ * deliveryReportingSupport(); the flags that were handled by the CM are provided in
+ * messageSent().
+ * \return A PendingOperation which will emit PendingOperation::finished
+ * when the message has been submitted for delivery.
+ * \sa messageSent()
+ */
+PendingSendMessage *TextChannel::send(const MessagePartList &parts,
+ MessageSendingFlags flags)
+{
+ Message m(parts);
+ PendingSendMessage *op = new PendingSendMessage(TextChannelPtr(this), m);
+
+ if (hasMessagesInterface()) {
+ Client::ChannelInterfaceMessagesInterface *messagesInterface =
+ interface<Client::ChannelInterfaceMessagesInterface>();
+
+ connect(new QDBusPendingCallWatcher(
+ messagesInterface->SendMessage(m.parts(),
+ (uint) flags)),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ op,
+ SLOT(onMessageSent(QDBusPendingCallWatcher*)));
+ } else {
+ connect(new QDBusPendingCallWatcher(mPriv->textInterface->Send(
+ m.messageType(), m.text())),
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ op,
+ SLOT(onTextSent(QDBusPendingCallWatcher*)));
+ }
+ return op;
+}
+
+/**
+ * Set the local chat state and notify other members of the channel that it has
+ * changed.
+ *
+ * Note that only the primary handler of the channel should set its chat
+ * state.
+ *
+ * This method requires TextChannel::FeatureCore to be ready.
+ *
+ * \param state The new state.
+ * \sa chatStateChanged()
+ */
+PendingOperation *TextChannel::requestChatState(ChannelChatState state)
+{
+ if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CHAT_STATE))) {
+ warning() << "TextChannel::requestChatState() used with no chat "
+ "state interface";
+ return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED),
+ QLatin1String("TextChannel does not support chat state interface"),
+ TextChannelPtr(this));
+ }
+
+ Client::ChannelInterfaceChatStateInterface *chatStateInterface =
+ interface<Client::ChannelInterfaceChatStateInterface>();
+ return new PendingVoid(chatStateInterface->SetChatState(
+ (uint) state), TextChannelPtr(this));
+}
+
+void TextChannel::onMessageSent(const MessagePartList &parts,
+ uint flags,
+ const QString &sentMessageToken)
+{
+ emit messageSent(Message(parts), MessageSendingFlag(flags),
+ sentMessageToken);
+}
+
+void TextChannel::onContactsFinished(PendingOperation *op)
+{
+ PendingContacts *pc = qobject_cast<PendingContacts *>(op);
+ UIntList failed;
+
+ Q_ASSERT(pc->isForHandles());
+
+ foreach (uint handle, pc->handles()) {
+ mPriv->awaitingContacts -= handle;
+ }
+
+ if (pc->isError()) {
+ warning().nospace() << "Gathering contacts failed: "
+ << pc->errorName() << ": " << pc->errorMessage();
+ foreach (uint handle, pc->handles()) {
+ mPriv->contactLost(handle);
+ }
+ } else {
+ foreach (const ContactPtr &contact, pc->contacts()) {
+ mPriv->contactFound(contact);
+ }
+ foreach (uint handle, pc->invalidHandles()) {
+ mPriv->contactLost(handle);
+ }
+ }
+
+ // all contacts for messages and chat state events we were asking about
+ // should now be ready
+ mPriv->processMessageQueue();
+ mPriv->processChatStateQueue();
+}
+
+void TextChannel::onMessageReceived(const MessagePartList &parts)
+{
+ if (!mPriv->initialMessagesReceived) {
+ return;
+ }
+
+ mPriv->incompleteMessages << new Private::MessageEvent(
+ ReceivedMessage(parts, TextChannelPtr(this)));
+ mPriv->processMessageQueue();
+}
+
+void TextChannel::onPendingMessagesRemoved(const UIntList &ids)
+{
+ if (!mPriv->initialMessagesReceived) {
+ return;
+ }
+ foreach (uint id, ids) {
+ mPriv->incompleteMessages << new Private::MessageEvent(id);
+ }
+ mPriv->processMessageQueue();
+}
+
+void TextChannel::onTextSent(uint timestamp, uint type, const QString &text)
+{
+ emit messageSent(Message(timestamp, type, text), 0,
+ QLatin1String(""));
+}
+
+void TextChannel::onTextReceived(uint id, uint timestamp, uint sender,
+ uint type, uint flags, const QString &text)
+{
+ if (!mPriv->initialMessagesReceived) {
+ return;
+ }
+
+ MessagePart header;
+
+ if (timestamp == 0) {
+ timestamp = QDateTime::currentDateTime().toTime_t();
+ }
+ header.insert(QLatin1String("message-received"),
+ QDBusVariant(static_cast<qlonglong>(timestamp)));
+
+ header.insert(QLatin1String("pending-message-id"), QDBusVariant(id));
+ header.insert(QLatin1String("message-sender"), QDBusVariant(sender));
+ header.insert(QLatin1String("message-type"), QDBusVariant(type));
+
+ if (flags & ChannelTextMessageFlagScrollback) {
+ header.insert(QLatin1String("scrollback"), QDBusVariant(true));
+ }
+ if (flags & ChannelTextMessageFlagRescued) {
+ header.insert(QLatin1String("rescued"), QDBusVariant(true));
+ }
+
+ MessagePart body;
+
+ body.insert(QLatin1String("content-type"),
+ QDBusVariant(QLatin1String("text/plain")));
+ body.insert(QLatin1String("content"), QDBusVariant(text));
+
+ if (flags & ChannelTextMessageFlagTruncated) {
+ header.insert(QLatin1String("truncated"), QDBusVariant(true));
+ }
+
+ MessagePartList parts;
+ parts << header;
+ parts << body;
+
+ ReceivedMessage m(parts, TextChannelPtr(this));
+
+ if (flags & ChannelTextMessageFlagNonTextContent) {
+ // set the "you are not expected to understand this" flag
+ m.setForceNonText();
+ }
+
+ mPriv->incompleteMessages << new Private::MessageEvent(m);
+ mPriv->processMessageQueue();
+}
+
+void TextChannel::onTextSendError(uint error, uint timestamp, uint type,
+ const QString &text)
+{
+ if (!mPriv->initialMessagesReceived) {
+ return;
+ }
+
+ MessagePart header;
+
+ header.insert(QLatin1String("message-received"),
+ QDBusVariant(static_cast<qlonglong>(
+ QDateTime::currentDateTime().toTime_t())));
+ header.insert(QLatin1String("message-type"),
+ QDBusVariant(static_cast<uint>(
+ ChannelTextMessageTypeDeliveryReport)));
+
+ // we can't tell whether it's a temporary or permanent failure here,
+ // so guess based on the delivery-error
+ uint deliveryStatus;
+ switch (error) {
+ case ChannelTextSendErrorOffline:
+ case ChannelTextSendErrorPermissionDenied:
+ deliveryStatus = DeliveryStatusTemporarilyFailed;
+ break;
+
+ case ChannelTextSendErrorInvalidContact:
+ case ChannelTextSendErrorTooLong:
+ case ChannelTextSendErrorNotImplemented:
+ deliveryStatus = DeliveryStatusPermanentlyFailed;
+ break;
+
+ case ChannelTextSendErrorUnknown:
+ default:
+ deliveryStatus = DeliveryStatusTemporarilyFailed;
+ break;
+ }
+
+ header.insert(QLatin1String("delivery-status"),
+ QDBusVariant(deliveryStatus));
+ header.insert(QLatin1String("delivery-error"), QDBusVariant(error));
+
+ MessagePart echoHeader;
+ echoHeader.insert(QLatin1String("message-sent"),
+ QDBusVariant(timestamp));
+ echoHeader.insert(QLatin1String("message-type"),
+ QDBusVariant(type));
+
+ MessagePart echoBody;
+ echoBody.insert(QLatin1String("content-type"),
+ QDBusVariant(QLatin1String("text/plain")));
+ echoBody.insert(QLatin1String("content"), QDBusVariant(text));
+
+ MessagePartList echo;
+ echo << echoHeader;
+ echo << echoBody;
+ header.insert(QLatin1String("delivery-echo"),
+ QDBusVariant(QVariant::fromValue(echo)));
+
+ MessagePartList parts;
+ parts << header;
+}
+
+void TextChannel::gotProperties(QDBusPendingCallWatcher *watcher)
+{
+ Q_ASSERT(mPriv->getAllInFlight);
+ mPriv->getAllInFlight = false;
+ mPriv->gotProperties = true;
+
+ QDBusPendingReply<QVariantMap> reply = *watcher;
+ if (reply.isError()) {
+ warning().nospace() << "Properties::GetAll(Channel.Interface.Messages)"
+ " failed with " << reply.error().name() << ": " <<
+ reply.error().message();
+
+ ReadinessHelper *readinessHelper = mPriv->readinessHelper;
+ if (readinessHelper->requestedFeatures().contains(FeatureMessageQueue) &&
+ !readinessHelper->isReady(Features() << FeatureMessageQueue)) {
+ readinessHelper->setIntrospectCompleted(FeatureMessageQueue, false, reply.error());
+ }
+
+ if (readinessHelper->requestedFeatures().contains(FeatureMessageCapabilities) &&
+ !readinessHelper->isReady(Features() << FeatureMessageCapabilities)) {
+ readinessHelper->setIntrospectCompleted(FeatureMessageCapabilities, false, reply.error());
+ }
+ return;
+ }
+
+ debug() << "Properties::GetAll(Channel.Interface.Messages) returned";
+ mPriv->props = reply.value();
+
+ mPriv->updateInitialMessages();
+ mPriv->updateCapabilities();
+
+ watcher->deleteLater();
+}
+
+void TextChannel::gotPendingMessages(QDBusPendingCallWatcher *watcher)
+{
+ Q_ASSERT(!mPriv->initialMessagesReceived);
+ mPriv->initialMessagesReceived = true;
+
+ QDBusPendingReply<PendingTextMessageList> reply = *watcher;
+ if (reply.isError()) {
+ warning().nospace() << "Properties::GetAll(Channel.Interface.Messages)"
+ " failed with " << reply.error().name() << ": " <<
+ reply.error().message();
+
+ // TODO should we fail here?
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureMessageQueue, false, reply.error());
+ return;
+ }
+
+ debug() << "Text::ListPendingMessages returned";
+ PendingTextMessageList list = reply.value();
+
+ if (!list.isEmpty()) {
+ foreach (const PendingTextMessage &message, list) {
+ onTextReceived(message.identifier, message.unixTimestamp,
+ message.sender, message.messageType, message.flags,
+ message.text);
+ }
+ // processMessageQueue sets FeatureMessageQueue ready when the queue is empty for the first
+ // time
+ } else {
+ mPriv->readinessHelper->setIntrospectCompleted(FeatureMessageQueue, true);
+ }
+
+ watcher->deleteLater();
+}
+
+void TextChannel::onChatStateChanged(uint contactHandle, uint state)
+{
+ mPriv->chatStateQueue.append(new Private::ChatStateEvent(
+ contactHandle, state));
+ mPriv->processChatStateQueue();
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/text-channel.h b/qt4/TelepathyQt4/text-channel.h
new file mode 100644
index 000000000..71a46266c
--- /dev/null
+++ b/qt4/TelepathyQt4/text-channel.h
@@ -0,0 +1,139 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_text_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_text_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingSendMessage>
+
+namespace Tp
+{
+
+class Message;
+class ReceivedMessage;
+
+class TELEPATHY_QT4_EXPORT TextChannel : public Channel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(TextChannel)
+
+public:
+ static const Feature FeatureCore;
+ static const Feature FeatureMessageQueue;
+ static const Feature FeatureMessageCapabilities;
+ static const Feature FeatureMessageSentSignal;
+ static const Feature FeatureChatState;
+
+ static TextChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~TextChannel();
+
+ bool hasMessagesInterface() const;
+ bool hasChatStateInterface() const;
+ bool canInviteContacts() const;
+
+ // requires FeatureMessageCapabilities
+ QStringList supportedContentTypes() const;
+ MessagePartSupportFlags messagePartSupport() const;
+ DeliveryReportingSupportFlags deliveryReportingSupport() const;
+
+ // requires FeatureMessageQueue
+ QList<ReceivedMessage> messageQueue() const;
+
+ // requires FeatureChatState
+ ChannelChatState chatState(const ContactPtr &contact) const;
+
+public Q_SLOTS:
+ void acknowledge(const QList<ReceivedMessage> &messages);
+
+ void forget(const QList<ReceivedMessage> &messages);
+
+ PendingSendMessage *send(const QString &text,
+ ChannelTextMessageType type = ChannelTextMessageTypeNormal,
+ MessageSendingFlags flags = 0);
+
+ PendingSendMessage *send(const MessagePartList &parts,
+ MessageSendingFlags flags = 0);
+
+ inline PendingOperation *inviteContacts(
+ const QList<ContactPtr> &contacts,
+ const QString &message = QString())
+ {
+ return groupAddContacts(contacts, message);
+ }
+
+ PendingOperation *requestChatState(ChannelChatState state);
+
+Q_SIGNALS:
+ // FeatureMessageSentSignal
+ void messageSent(const Tp::Message &message,
+ Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken);
+
+ // FeatureMessageQueue
+ void messageReceived(const Tp::ReceivedMessage &message);
+ void pendingMessageRemoved(
+ const Tp::ReceivedMessage &message);
+
+ // FeatureChatState
+ void chatStateChanged(const Tp::ContactPtr &contact,
+ Tp::ChannelChatState state);
+
+protected:
+ TextChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = TextChannel::FeatureCore);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onContactsFinished(Tp::PendingOperation *);
+ TELEPATHY_QT4_NO_EXPORT void onAcknowledgePendingMessagesReply(QDBusPendingCallWatcher *);
+
+ TELEPATHY_QT4_NO_EXPORT void onMessageSent(const Tp::MessagePartList &, uint,
+ const QString &);
+ TELEPATHY_QT4_NO_EXPORT void onMessageReceived(const Tp::MessagePartList &);
+ TELEPATHY_QT4_NO_EXPORT void onPendingMessagesRemoved(const Tp::UIntList &);
+
+ TELEPATHY_QT4_NO_EXPORT void onTextSent(uint, uint, const QString &);
+ TELEPATHY_QT4_NO_EXPORT void onTextReceived(uint, uint, uint, uint, uint, const QString &);
+ TELEPATHY_QT4_NO_EXPORT void onTextSendError(uint, uint, uint, const QString &);
+
+ TELEPATHY_QT4_NO_EXPORT void gotProperties(QDBusPendingCallWatcher *);
+ TELEPATHY_QT4_NO_EXPORT void gotPendingMessages(QDBusPendingCallWatcher *);
+
+ TELEPATHY_QT4_NO_EXPORT void onChatStateChanged(uint, uint);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/tls-certificate.cpp b/qt4/TelepathyQt4/tls-certificate.cpp
new file mode 100644
index 000000000..80f874a0f
--- /dev/null
+++ b/qt4/TelepathyQt4/tls-certificate.cpp
@@ -0,0 +1,26 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define IN_TELEPATHY_QT4_HEADER
+#include <TelepathyQt4/tls-certificate.h>
+
+#include "TelepathyQt4/_gen/cli-tls-certificate-body.hpp"
+#include "TelepathyQt4/_gen/cli-tls-certificate.moc.hpp"
diff --git a/qt4/TelepathyQt4/tls-certificate.h b/qt4/TelepathyQt4/tls-certificate.h
new file mode 100644
index 000000000..eb6d81d2e
--- /dev/null
+++ b/qt4/TelepathyQt4/tls-certificate.h
@@ -0,0 +1,31 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_tls_certificate_h_HEADER_GUARD_
+#define _TelepathyQt4_tls_certificate_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/cli-tls-certificate.h>
+
+#endif
diff --git a/qt4/TelepathyQt4/tls-certificate.xml b/qt4/TelepathyQt4/tls-certificate.xml
new file mode 100644
index 000000000..0ba3d2d06
--- /dev/null
+++ b/qt4/TelepathyQt4/tls-certificate.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>TLSCertificate</tp:title>
+
+<xi:include href="../spec/Authentication_TLS_Certificate.xml"/>
+
+</tp:spec>
diff --git a/qt4/TelepathyQt4/tube-channel.cpp b/qt4/TelepathyQt4/tube-channel.cpp
new file mode 100644
index 000000000..403c35df5
--- /dev/null
+++ b/qt4/TelepathyQt4/tube-channel.cpp
@@ -0,0 +1,281 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/TubeChannel>
+
+#include "TelepathyQt4/_gen/tube-channel.moc.hpp"
+
+#include "TelepathyQt4/debug-internal.h"
+
+#include <TelepathyQt4/PendingVariantMap>
+
+namespace Tp
+{
+
+struct TELEPATHY_QT4_NO_EXPORT TubeChannel::Private
+{
+ Private(TubeChannel *parent);
+
+ static void introspectTube(TubeChannel::Private *self);
+
+ void extractTubeProperties(const QVariantMap &props);
+
+ // Public object
+ TubeChannel *parent;
+
+ ReadinessHelper *readinessHelper;
+
+ // Introspection
+ TubeChannelState state;
+ QVariantMap parameters;
+};
+
+TubeChannel::Private::Private(TubeChannel *parent)
+ : parent(parent),
+ readinessHelper(parent->readinessHelper()),
+ state((TubeChannelState) -1)
+{
+ ReadinessHelper::Introspectables introspectables;
+
+ ReadinessHelper::Introspectable introspectableTube(
+ QSet<uint>() << 0, // makesSenseForStatuses
+ Features() << Channel::FeatureCore, // dependsOnFeatures (core)
+ QStringList() << QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_TUBE), // dependsOnInterfaces
+ (ReadinessHelper::IntrospectFunc) &TubeChannel::Private::introspectTube,
+ this);
+ introspectables[TubeChannel::FeatureCore] = introspectableTube;
+
+ readinessHelper->addIntrospectables(introspectables);
+}
+
+void TubeChannel::Private::introspectTube(TubeChannel::Private *self)
+{
+ TubeChannel *parent = self->parent;
+
+ debug() << "Introspecting tube properties";
+ Client::ChannelInterfaceTubeInterface *tubeInterface =
+ parent->interface<Client::ChannelInterfaceTubeInterface>();
+
+ parent->connect(tubeInterface,
+ SIGNAL(TubeChannelStateChanged(uint)),
+ SLOT(onTubeChannelStateChanged(uint)));
+
+ PendingVariantMap *pvm = tubeInterface->requestAllProperties();
+ parent->connect(pvm,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(gotTubeProperties(Tp::PendingOperation *)));
+}
+
+void TubeChannel::Private::extractTubeProperties(const QVariantMap &props)
+{
+ state = (Tp::TubeChannelState) qdbus_cast<uint>(props[QLatin1String("State")]);
+ parameters = qdbus_cast<QVariantMap>(props[QLatin1String("Parameters")]);
+}
+
+/**
+ * \class TubeChannel
+ * \ingroup clientchannel
+ * \headerfile TelepathyQt4/tube-channel.h <TelepathyQt4/TubeChannel>
+ *
+ * \brief The TubeChannel class is a base class for all tube types.
+ *
+ * A tube is a mechanism for arbitrary data transfer between two or more IM users,
+ * used to allow applications on the users' systems to communicate without having
+ * to establish network connections themselves.
+ *
+ * Note that TubeChannel should never be instantiated directly, instead one of its
+ * subclasses (e.g. IncomingStreamTubeChannel or OutgoingStreamTubeChannel) should be used.
+ *
+ * See \ref async_model, \ref shared_ptr
+ */
+
+/**
+ * Feature representing the core that needs to become ready to make the
+ * TubeChannel object usable.
+ *
+ * Note that this feature must be enabled in order to use most
+ * TubeChannel methods.
+ * See specific methods documentation for more details.
+ */
+const Feature TubeChannel::FeatureCore = Feature(QLatin1String(TubeChannel::staticMetaObject.className()), 0);
+
+/**
+ * \deprecated Use TubeChannel::FeatureCore instead.
+ */
+const Feature TubeChannel::FeatureTube = TubeChannel::FeatureCore;
+
+/**
+ * Create a new TubeChannel channel.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \return A TubeChannelPtr object pointing to the newly created
+ * TubeChannel object.
+ */
+TubeChannelPtr TubeChannel::create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties)
+{
+ return TubeChannelPtr(new TubeChannel(connection, objectPath,
+ immutableProperties));
+}
+
+/**
+ * Construct a new TubeChannel object.
+ *
+ * \param connection Connection owning this channel, and specifying the
+ * service.
+ * \param objectPath The channel object path.
+ * \param immutableProperties The channel immutable properties.
+ * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
+ * depend on TubeChannel::FeatureCore.
+ */
+TubeChannel::TubeChannel(const ConnectionPtr &connection,
+ const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature)
+ : Channel(connection, objectPath, immutableProperties, coreFeature),
+ mPriv(new Private(this))
+{
+}
+
+/**
+ * Class destructor.
+ */
+TubeChannel::~TubeChannel()
+{
+ delete mPriv;
+}
+
+/**
+ * Return the parameters associated with this tube, if any.
+ *
+ * The parameters are populated when an outgoing tube is offered, but they are most useful in the
+ * receiving end, where the parameters passed to the offer can be extracted for the tube's entire
+ * lifetime to bootstrap legacy protocols. All parameters are passed unchanged.
+ *
+ * This method requires TubeChannel::FeatureCore to be ready.
+ *
+ * \return The parameters as QVariantMap.
+ */
+QVariantMap TubeChannel::parameters() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "TubeChannel::parameters() used with FeatureCore not ready";
+ return QVariantMap();
+ }
+
+ return mPriv->parameters;
+}
+
+/**
+ * Return the state of this tube.
+ *
+ * Change notification is via the stateChanged() signal.
+ *
+ * This method requires TubeChannel::FeatureCore to be ready.
+ *
+ * \return The state as #TubeChannelState.
+ * \sa stateChanged()
+ */
+TubeChannelState TubeChannel::state() const
+{
+ if (!isReady(FeatureCore)) {
+ warning() << "TubeChannel::state() used with FeatureCore not ready";
+ return TubeChannelStateNotOffered;
+ }
+
+ return mPriv->state;
+}
+
+/**
+ * \deprecated Use state() instead.
+ */
+TubeChannelState TubeChannel::tubeState() const
+{
+ return state();
+}
+
+void TubeChannel::setParameters(const QVariantMap &parameters)
+{
+ mPriv->parameters = parameters;
+}
+
+void TubeChannel::onTubeChannelStateChanged(uint newState)
+{
+ if (newState == mPriv->state) {
+ return;
+ }
+
+ uint oldState = mPriv->state;
+
+ debug() << "Tube state changed to" << newState;
+ mPriv->state = (Tp::TubeChannelState) newState;
+
+ /* only emit stateChanged if we already received the state from initial introspection */
+ if (oldState != (uint) -1) {
+ emit stateChanged((Tp::TubeChannelState) newState);
+ // FIXME (API/ABI break) Remove tubeStateChanged call
+ emit tubeStateChanged((Tp::TubeChannelState) newState);
+ }
+}
+
+void TubeChannel::gotTubeProperties(PendingOperation *op)
+{
+ if (!op->isError()) {
+ PendingVariantMap *pvm = qobject_cast<PendingVariantMap *>(op);
+
+ mPriv->extractTubeProperties(pvm->result());
+
+ debug() << "Got reply to Properties::GetAll(TubeChannel)";
+ mPriv->readinessHelper->setIntrospectCompleted(TubeChannel::FeatureCore, true);
+ } else {
+ warning().nospace() << "Properties::GetAll(TubeChannel) failed "
+ "with " << op->errorName() << ": " << op->errorMessage();
+ mPriv->readinessHelper->setIntrospectCompleted(TubeChannel::FeatureCore, false,
+ op->errorName(), op->errorMessage());
+ }
+}
+
+/**
+ * \fn void TubeChannel::stateChanged(Tp::TubeChannelState state)
+ *
+ * Emitted when the value of state() changes.
+ *
+ * \sa state The new state of this tube.
+ */
+
+/**
+ * \fn void TubeChannel::tubeStateChanged(Tp::TubeChannelState state)
+ *
+ * \deprecated Use stateChanged() instead.
+ */
+
+void TubeChannel::connectNotify(const char *signalName)
+{
+ if (qstrcmp(signalName, SIGNAL(tubeStateChanged(Tp::TubeChannelState))) == 0) {
+ warning() << "Connecting to deprecated signal tubeStateChanged(Tp::TubeChannelState)";
+ }
+}
+
+
+} // Tp
diff --git a/qt4/TelepathyQt4/tube-channel.h b/qt4/TelepathyQt4/tube-channel.h
new file mode 100644
index 000000000..876ef55da
--- /dev/null
+++ b/qt4/TelepathyQt4/tube-channel.h
@@ -0,0 +1,80 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_tube_channel_h_HEADER_GUARD_
+#define _TelepathyQt4_tube_channel_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Channel>
+
+namespace Tp
+{
+
+class TELEPATHY_QT4_EXPORT TubeChannel : public Channel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(TubeChannel)
+
+public:
+ static const Feature FeatureCore;
+ // FIXME (API/ABI break) Remove FeatureTube in favour of FeatureCore
+ static const Feature FeatureTube;
+
+ static TubeChannelPtr create(const ConnectionPtr &connection,
+ const QString &objectPath, const QVariantMap &immutableProperties);
+
+ virtual ~TubeChannel();
+
+ TubeChannelState state() const;
+ TELEPATHY_QT4_DEPRECATED TubeChannelState tubeState() const;
+
+ QVariantMap parameters() const;
+
+Q_SIGNALS:
+ void stateChanged(Tp::TubeChannelState state);
+ void tubeStateChanged(Tp::TubeChannelState state);
+
+protected:
+ TubeChannel(const ConnectionPtr &connection, const QString &objectPath,
+ const QVariantMap &immutableProperties,
+ const Feature &coreFeature = TubeChannel::FeatureCore);
+
+ void setParameters(const QVariantMap &parameters);
+
+ // FIXME (API/ABI break) Remove connectNotify
+ void connectNotify(const char *);
+
+private Q_SLOTS:
+ TELEPATHY_QT4_NO_EXPORT void onTubeChannelStateChanged(uint newstate);
+ TELEPATHY_QT4_NO_EXPORT void gotTubeProperties(Tp::PendingOperation *op);
+
+private:
+ struct Private;
+ friend struct Private;
+ Private *mPriv;
+};
+
+}
+
+#endif
diff --git a/qt4/TelepathyQt4/types-internal.h b/qt4/TelepathyQt4/types-internal.h
new file mode 100644
index 000000000..88a1c4293
--- /dev/null
+++ b/qt4/TelepathyQt4/types-internal.h
@@ -0,0 +1,156 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_types_internal_h_HEADER_GUARD_
+#define _TelepathyQt4_types_internal_h_HEADER_GUARD_
+
+#include <QDBusVariant>
+#include <QDBusArgument>
+#include <TelepathyQt4/Types>
+
+namespace Tp
+{
+
+/**
+ * Private structure used to circumvent QtDBus' strict demarshalling
+ */
+struct TELEPATHY_QT4_EXPORT SUSocketAddress
+{
+ /**
+ * A dotted-quad IPv4 address literal: four ASCII decimal numbers, each
+ * between 0 and 255 inclusive, e.g. &quot;192.168.0.1&quot;.
+ */
+ QString address;
+ /**
+ * The TCP or UDP port number.
+ */
+ uint port;
+};
+
+TELEPATHY_QT4_EXPORT bool operator==(const SUSocketAddress& v1, const SUSocketAddress& v2);
+TELEPATHY_QT4_EXPORT inline bool operator!=(const SUSocketAddress& v1, const SUSocketAddress& v2)
+{
+ return !operator==(v1, v2);
+}
+TELEPATHY_QT4_EXPORT QDBusArgument& operator<<(QDBusArgument& arg, const SUSocketAddress& val);
+TELEPATHY_QT4_EXPORT const QDBusArgument& operator>>(const QDBusArgument& arg, SUSocketAddress& val);
+
+} // Tp
+
+// specialise for Tp::SocketAddressIPv4, allowing it to be used in place of QDBusVariant
+template<> inline Tp::SocketAddressIPv4 qdbus_cast<Tp::SocketAddressIPv4>(const QDBusArgument &arg,
+Tp::SocketAddressIPv4 *)
+{
+ if (arg.currentSignature() == QLatin1String("(su)")) {
+ // Use Tp::SUSocketAddress
+ Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg);
+ Tp::SocketAddressIPv4 addr;
+ addr.address = saddr.address;
+ addr.port = saddr.port;
+ return addr;
+ } else if (arg.currentSignature() == QLatin1String("(sq)")) {
+ // Keep it standard
+ Tp::SocketAddressIPv4 item;
+ arg >> item;
+ return item;
+ } else {
+ // This should never happen...
+ return Tp::SocketAddressIPv4();
+ }
+}
+
+template<> inline Tp::SocketAddressIPv4 qdbus_cast<Tp::SocketAddressIPv4>(const QVariant &v, Tp::SocketAddressIPv4 *)
+{
+ int id = v.userType();
+ if (id == qMetaTypeId<QDBusArgument>()) {
+ QDBusArgument arg = qvariant_cast<QDBusArgument>(v);
+
+ if (arg.currentSignature() == QLatin1String("(su)")) {
+ // Use Tp::SUSocketAddress
+ Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg);
+ Tp::SocketAddressIPv4 addr;
+ addr.address = saddr.address;
+ addr.port = saddr.port;
+ return addr;
+ } else if (arg.currentSignature() == QLatin1String("(sq)")) {
+ // Keep it standard
+ Tp::SocketAddressIPv4 item;
+ arg >> item;
+ return item;
+ } else {
+ // This should never happen...
+ return Tp::SocketAddressIPv4();
+ }
+ } else
+ return qvariant_cast<Tp::SocketAddressIPv4>(v);
+}
+
+// specialise for Tp::SocketAddressIPv6, allowing it to be used in place of QDBusVariant
+template<> inline Tp::SocketAddressIPv6 qdbus_cast<Tp::SocketAddressIPv6>(const QDBusArgument &arg,
+Tp::SocketAddressIPv6 *)
+{
+ if (arg.currentSignature() == QLatin1String("(su)")) {
+ // Use Tp::SUSocketAddress
+ Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg);
+ Tp::SocketAddressIPv6 addr;
+ addr.address = saddr.address;
+ addr.port = saddr.port;
+ return addr;
+ } else if (arg.currentSignature() == QLatin1String("(sq)")) {
+ // Keep it standard
+ Tp::SocketAddressIPv6 item;
+ arg >> item;
+ return item;
+ } else {
+ // This should never happen...
+ return Tp::SocketAddressIPv6();
+ }
+}
+
+template<> inline Tp::SocketAddressIPv6 qdbus_cast<Tp::SocketAddressIPv6>(const QVariant &v, Tp::SocketAddressIPv6 *)
+{
+ int id = v.userType();
+ if (id == qMetaTypeId<QDBusArgument>()) {
+ QDBusArgument arg = qvariant_cast<QDBusArgument>(v);
+
+ if (arg.currentSignature() == QLatin1String("(su)")) {
+ // Use Tp::SUSocketAddress
+ Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg);
+ Tp::SocketAddressIPv6 addr;
+ addr.address = saddr.address;
+ addr.port = saddr.port;
+ return addr;
+ } else if (arg.currentSignature() == QLatin1String("(sq)")) {
+ // Keep it standard
+ Tp::SocketAddressIPv6 item;
+ arg >> item;
+ return item;
+ } else {
+ // This should never happen...
+ return Tp::SocketAddressIPv6();
+ }
+ } else
+ return qvariant_cast<Tp::SocketAddressIPv6>(v);
+}
+
+Q_DECLARE_METATYPE(Tp::SUSocketAddress)
+
+#endif
diff --git a/qt4/TelepathyQt4/types.cpp b/qt4/TelepathyQt4/types.cpp
new file mode 100644
index 000000000..fe5d125ea
--- /dev/null
+++ b/qt4/TelepathyQt4/types.cpp
@@ -0,0 +1,73 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Types>
+
+#include <TelepathyQt4/types-internal.h>
+
+#include "TelepathyQt4/_gen/types-body.hpp"
+
+#include "TelepathyQt4/future-internal.h"
+#include "TelepathyQt4/_gen/future-types-body.hpp"
+
+namespace Tp {
+/**
+ * \\ingroup types
+ * \headerfile TelepathyQt4/types.h <TelepathyQt4/Types>
+ *
+ * Register the types used by the library with the QtDBus type system.
+ *
+ * Call this function to register the types used before using anything else in
+ * the library.
+ */
+void registerTypes()
+{
+ qDBusRegisterMetaType<Tp::SUSocketAddress>();
+
+ Tp::_registerTypes();
+ TpFuture::_registerTypes();
+}
+
+bool operator==(const SUSocketAddress& v1, const SUSocketAddress& v2)
+{
+ return ((v1.address == v2.address)
+ && (v1.port == v2.port)
+ );
+}
+
+QDBusArgument& operator<<(QDBusArgument& arg, const SUSocketAddress& val)
+{
+ arg.beginStructure();
+ arg << val.address << val.port;
+ arg.endStructure();
+ return arg;
+}
+
+const QDBusArgument& operator>>(const QDBusArgument& arg, SUSocketAddress& val)
+{
+ arg.beginStructure();
+ arg >> val.address >> val.port;
+ arg.endStructure();
+ return arg;
+}
+
+}
diff --git a/qt4/TelepathyQt4/types.h b/qt4/TelepathyQt4/types.h
new file mode 100644
index 000000000..039302cea
--- /dev/null
+++ b/qt4/TelepathyQt4/types.h
@@ -0,0 +1,171 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2008 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_types_h_HEADER_GUARD_
+#define _TelepathyQt4_types_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/_gen/types.h>
+
+#include <TelepathyQt4/Global>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/MethodInvocationContext>
+
+#include <QDBusVariant>
+
+namespace Tp
+{
+
+TELEPATHY_QT4_EXPORT void registerTypes();
+
+template <typename T> class Filter;
+template <typename T> class GenericCapabilityFilter;
+template <typename T> class GenericPropertyFilter;
+
+class AbstractClient;
+class AbstractClientApprover;
+class AbstractClientHandler;
+class AbstractClientObserver;
+class Account;
+typedef GenericCapabilityFilter<Account> AccountCapabilityFilter;
+class AccountFactory;
+typedef Filter<Account> AccountFilter;
+class AccountManager;
+class AccountPropertyFilter;
+class AccountSet;
+class Channel;
+class ChannelDispatchOperation;
+class ChannelFactory;
+class ChannelRequest;
+class ClientObject;
+class ClientRegistrar;
+class Connection;
+class ConnectionFactory;
+class ConnectionLowlevel;
+class ConnectionManager;
+class ConnectionManagerLowlevel;
+class Contact;
+class ContactFactory;
+class ContactManager;
+class ContactMessenger;
+class ContactSearchChannel;
+class DBusProxy;
+class FileTransferChannel;
+class IncomingFileTransferChannel;
+class IncomingStreamTubeChannel;
+class OutgoingFileTransferChannel;
+class OutgoingStreamTubeChannel;
+class Profile;
+class ProfileManager;
+class RoomListChannel;
+class SimpleObserver;
+class SimpleCallObserver;
+class SimpleTextObserver;
+class StreamedMediaChannel;
+class StreamedMediaStream;
+class StreamTubeChannel;
+class StreamTubeClient;
+class StreamTubeServer;
+class TextChannel;
+class TubeChannel;
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+typedef SharedPtr<AbstractClient> AbstractClientPtr;
+typedef SharedPtr<AbstractClientApprover> AbstractClientApproverPtr;
+typedef SharedPtr<AbstractClientHandler> AbstractClientHandlerPtr;
+typedef SharedPtr<AbstractClientObserver> AbstractClientObserverPtr;
+typedef SharedPtr<Account> AccountPtr;
+typedef SharedPtr<AccountCapabilityFilter> AccountCapabilityFilterPtr;
+typedef SharedPtr<const AccountCapabilityFilter> AccountCapabilityFilterConstPtr;
+typedef SharedPtr<AccountFactory> AccountFactoryPtr;
+typedef SharedPtr<const AccountFactory> AccountFactoryConstPtr;
+typedef SharedPtr<AccountFilter> AccountFilterPtr;
+typedef SharedPtr<const AccountFilter> AccountFilterConstPtr;
+typedef SharedPtr<AccountManager> AccountManagerPtr;
+typedef SharedPtr<AccountPropertyFilter> AccountPropertyFilterPtr;
+typedef SharedPtr<const AccountPropertyFilter> AccountPropertyFilterConstPtr;
+typedef SharedPtr<AccountSet> AccountSetPtr;
+typedef SharedPtr<Channel> ChannelPtr;
+typedef SharedPtr<ChannelDispatchOperation> ChannelDispatchOperationPtr;
+typedef SharedPtr<ChannelFactory> ChannelFactoryPtr;
+typedef SharedPtr<const ChannelFactory> ChannelFactoryConstPtr;
+typedef SharedPtr<ChannelRequest> ChannelRequestPtr;
+typedef SharedPtr<ClientObject> ClientObjectPtr;
+typedef SharedPtr<ClientRegistrar> ClientRegistrarPtr;
+typedef SharedPtr<Connection> ConnectionPtr;
+typedef SharedPtr<ConnectionFactory> ConnectionFactoryPtr;
+typedef SharedPtr<const ConnectionFactory> ConnectionFactoryConstPtr;
+typedef SharedPtr<ConnectionLowlevel> ConnectionLowlevelPtr;
+typedef SharedPtr<const ConnectionLowlevel> ConnectionLowlevelConstPtr;
+typedef SharedPtr<ConnectionManager> ConnectionManagerPtr;
+typedef SharedPtr<ConnectionManagerLowlevel> ConnectionManagerLowlevelPtr;
+typedef SharedPtr<const ConnectionManagerLowlevel> ConnectionManagerLowlevelConstPtr;
+typedef SharedPtr<Contact> ContactPtr;
+typedef QSet<ContactPtr> Contacts;
+typedef SharedPtr<ContactFactory> ContactFactoryPtr;
+typedef SharedPtr<const ContactFactory> ContactFactoryConstPtr;
+typedef SharedPtr<ContactManager> ContactManagerPtr;
+typedef SharedPtr<ContactMessenger> ContactMessengerPtr;
+typedef SharedPtr<ContactSearchChannel> ContactSearchChannelPtr;
+typedef SharedPtr<DBusProxy> DBusProxyPtr;
+typedef SharedPtr<FileTransferChannel> FileTransferChannelPtr;
+typedef SharedPtr<IncomingFileTransferChannel> IncomingFileTransferChannelPtr;
+typedef SharedPtr<IncomingStreamTubeChannel> IncomingStreamTubeChannelPtr;
+typedef SharedPtr<OutgoingFileTransferChannel> OutgoingFileTransferChannelPtr;
+typedef SharedPtr<OutgoingStreamTubeChannel> OutgoingStreamTubeChannelPtr;
+typedef SharedPtr<Profile> ProfilePtr;
+typedef SharedPtr<ProfileManager> ProfileManagerPtr;
+typedef SharedPtr<RoomListChannel> RoomListChannelPtr;
+typedef SharedPtr<SimpleObserver> SimpleObserverPtr;
+typedef SharedPtr<SimpleCallObserver> SimpleCallObserverPtr;
+typedef SharedPtr<SimpleTextObserver> SimpleTextObserverPtr;
+typedef SharedPtr<StreamedMediaChannel> StreamedMediaChannelPtr;
+typedef SharedPtr<StreamedMediaStream> StreamedMediaStreamPtr;
+typedef SharedPtr<StreamTubeChannel> StreamTubeChannelPtr;
+typedef SharedPtr<StreamTubeClient> StreamTubeClientPtr;
+typedef SharedPtr<StreamTubeServer> StreamTubeServerPtr;
+typedef SharedPtr<TextChannel> TextChannelPtr;
+typedef SharedPtr<TubeChannel> TubeChannelPtr;
+
+template<typename T1 = MethodInvocationContextTypes::Nil, typename T2 = MethodInvocationContextTypes::Nil,
+ typename T3 = MethodInvocationContextTypes::Nil, typename T4 = MethodInvocationContextTypes::Nil,
+ typename T5 = MethodInvocationContextTypes::Nil, typename T6 = MethodInvocationContextTypes::Nil,
+ typename T7 = MethodInvocationContextTypes::Nil, typename T8 = MethodInvocationContextTypes::Nil>
+class MethodInvocationContextPtr : public SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> >
+{
+public:
+ inline MethodInvocationContextPtr() { }
+ explicit inline MethodInvocationContextPtr(MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> *d)
+ : SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> >(d) { }
+ inline MethodInvocationContextPtr(const SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> > &o)
+ : SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> >(o) { }
+};
+
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+} // Tp
+
+#endif
diff --git a/qt4/TelepathyQt4/utils.cpp b/qt4/TelepathyQt4/utils.cpp
new file mode 100644
index 000000000..f216abbd5
--- /dev/null
+++ b/qt4/TelepathyQt4/utils.cpp
@@ -0,0 +1,120 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Utils>
+
+#include <QByteArray>
+#include <QString>
+
+/**
+ * \defgroup utility functions
+ */
+
+namespace Tp
+{
+
+static inline bool isBad(char c, bool isFirst)
+{
+ return ((c < 'a' || c > 'z') &&
+ (c < 'A' || c > 'Z') &&
+ (c < '0' || c > '9' || isFirst));
+}
+
+/**
+ * Escape an arbitrary string so it follows the rules for a C identifier,
+ * and hence an object path component, interface element component,
+ * bus name component or member name in D-Bus.
+ *
+ * This is a reversible encoding, so it preserves distinctness.
+ *
+ * The escaping consists of replacing all non-alphanumerics, and the first
+ * character if it's a digit, with an underscore and two lower-case hex
+ * digits:
+ *
+ * "0123abc_xyz\x01\xff" -> _30123abc_5fxyz_01_ff
+ *
+ * i.e. similar to URI encoding, but with _ taking the role of %, and a
+ * smaller allowed set. As a special case, "" is escaped to "_" (just for
+ * completeness, really).
+ *
+ * \param string The string to be escaped.
+ * \return the escaped string.
+ */
+QString escapeAsIdentifier(const QString &string)
+{
+ bool bad = false;
+ QByteArray op;
+ QByteArray utf8;
+ const char *name;
+ const char *ptr, *firstOk;
+
+ /* This function is copy/pasted from tp_escape_as_identified from
+ * telepathy-glib. */
+
+ /* fast path for empty name */
+ if (string.isEmpty()) {
+ return QString::fromLatin1("_");
+ }
+
+ utf8 = string.toUtf8();
+ name = utf8.constData();
+ for (ptr = name; *ptr; ptr++) {
+ if (isBad(*ptr, ptr == name)) {
+ bad = true;
+ break;
+ }
+ }
+
+ /* fast path if it's clean */
+ if (!bad) {
+ return string;
+ }
+
+ /* If strictly less than ptr, firstOk is the first uncopied safe character.
+ */
+ firstOk = name;
+ for (ptr = name; *ptr; ptr++) {
+ if (isBad(*ptr, ptr == name)) {
+ char buf[4] = { 0, };
+
+ /* copy preceding safe characters if any */
+ if (firstOk < ptr) {
+ op.append(firstOk, ptr - firstOk);
+ }
+
+ /* escape the unsafe character */
+ qsnprintf(buf, sizeof (buf), "_%02x", (unsigned char)(*ptr));
+ op.append(buf);
+
+ /* restart after it */
+ firstOk = ptr + 1;
+ }
+ }
+ /* copy trailing safe characters if any */
+ if (firstOk < ptr) {
+ op.append(firstOk, ptr - firstOk);
+ }
+
+ return QString::fromLatin1(op.constData());
+}
+
+} // Tp
diff --git a/qt4/TelepathyQt4/utils.h b/qt4/TelepathyQt4/utils.h
new file mode 100644
index 000000000..9bfe0a8cb
--- /dev/null
+++ b/qt4/TelepathyQt4/utils.h
@@ -0,0 +1,41 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_utils_h_HEADER_GUARD_
+#define _TelepathyQt4_utils_h_HEADER_GUARD_
+
+#ifndef IN_TELEPATHY_QT4_HEADER
+#error IN_TELEPATHY_QT4_HEADER
+#endif
+
+#include <TelepathyQt4/Global>
+
+#include <QString>
+
+namespace Tp
+{
+
+TELEPATHY_QT4_EXPORT QString escapeAsIdentifier(const QString &string);
+
+} // Tp
+
+#endif
diff --git a/qt4/cmake/modules/CompilerWarnings.cmake b/qt4/cmake/modules/CompilerWarnings.cmake
new file mode 100644
index 000000000..81367ff14
--- /dev/null
+++ b/qt4/cmake/modules/CompilerWarnings.cmake
@@ -0,0 +1,63 @@
+include(CheckCXXCompilerFlag)
+include(CheckCCompilerFlag)
+
+macro(check_lang_compiler_flag lang flag variable)
+
+ if(${lang} STREQUAL c)
+ check_c_compiler_flag(${flag} ${variable})
+ endif(${lang} STREQUAL c)
+
+ if(${lang} STREQUAL cxx)
+ check_cxx_compiler_flag(${flag} ${variable})
+ endif(${lang} STREQUAL cxx)
+
+endmacro(check_lang_compiler_flag flag variable)
+
+macro(compiler_warnings ret lang werror_by_default desirable_flags undesirable_flags)
+ set(warning_flags "")
+ foreach(flag ${desirable_flags})
+ check_lang_compiler_flag(${lang} -W${flag} ${flag}_${lang}_result)
+ if(${${flag}_${lang}_result})
+ set(warning_flags "${warning_flags} -W${flag}")
+ endif( ${${flag}_${lang}_result} )
+ endforeach(flag ${desirable_flags})
+
+ check_lang_compiler_flag(${lang} -Werror error_${lang}_result)
+
+ if(${error_${lang}_result})
+ set(error_flags "-Werror")
+ endif(${error_${lang}_result})
+
+ set(all_nowarning_flags_supported 1)
+
+ foreach(flag ${undesirable_flags})
+ check_lang_compiler_flag(${lang} -Wno-${flag} ${flag}_${lang}_result)
+
+ if(${${flag}_${lang}_result})
+ set(warning_flags "${warning_flags} -Wno-${flag}")
+ else(${${flag}_${lang}_result})
+ set(all_nowarning_flags_supported 0)
+ break()
+ endif(${${flag}_${lang}_result})
+
+ check_lang_compiler_flag(${lang} -Wno-error=${flag} noerror_${flag}_${lang}_result)
+
+ if(${noerror_${flag}_${lang}_result})
+ set(error_flags "${error_flags} -Wno-error=${flag}")
+ endif(${noerror_${flag}_${lang}_result})
+
+ endforeach(flag ${undesirable_flags})
+
+ if(${DISABLE_WERROR} STREQUAL ON)
+ set(enable_werror 0)
+ else(${DISABLE_WERROR} STREQUAL ON)
+ set(enable_werror 1)
+ endif(${DISABLE_WERROR} STREQUAL ON)
+
+ if(${werror_by_default} AND ${enable_werror} AND ${all_nowarning_flags_supported})
+ set(${ret} "${warning_flags} ${error_flags}")
+ else(${werror_by_default} AND ${enable_werror} AND ${all_nowarning_flags_supported})
+ set(${ret} "${warning_flags}")
+ endif(${werror_by_default} AND ${enable_werror} AND ${all_nowarning_flags_supported})
+
+endmacro(compiler_warnings ret lang werror_by_default desirable_flags undesirable_flags)
diff --git a/qt4/cmake/modules/Doxygen.cmake b/qt4/cmake/modules/Doxygen.cmake
new file mode 100644
index 000000000..804c217f0
--- /dev/null
+++ b/qt4/cmake/modules/Doxygen.cmake
@@ -0,0 +1,37 @@
+# generate documentation on 'make doxygen-doc'
+file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/doc)
+
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+ find_program(QHELPGENERATOR_EXECUTABLE qhelpgenerator)
+ mark_as_advanced(QHELPGENERATOR_EXECUTABLE)
+
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(QHELPGENERATOR DEFAULT_MSG QHELPGENERATOR_EXECUTABLE)
+
+ set(QT_TAGS_FILE ${QT_DOC_DIR}/html/qt.tags)
+ if(EXISTS ${QT_TAGS_FILE})
+ find_package(Perl)
+
+ if (NOT PERL_FOUND)
+ message(WARNING "Perl was not found. Qt crosslinks in uploaded docs won't be valid.")
+ endif (NOT PERL_FOUND)
+ else(EXISTS ${QT_TAGS_FILE})
+ message(WARNING "html/qt.tags not found in ${QT_DOC_DIR}. Set the QT_DOC_DIR variable to
+point to its location to enable crosslinking.")
+ unset(QT_TAGS_FILE)
+ endif(EXISTS ${QT_TAGS_FILE})
+
+ set(abs_top_builddir ${CMAKE_BINARY_DIR})
+ set(abs_top_srcdir ${CMAKE_SOURCE_DIR})
+ set(GENERATE_HTML YES)
+ set(GENERATE_RTF NO)
+ set(GENERATE_CHM NO)
+ set(GENERATE_CHI NO)
+ set(GENERATE_LATEX NO)
+ set(GENERATE_MAN NO)
+ set(GENERATE_XML NO)
+ set(GENERATE_QHP ${QHELPGENERATOR_FOUND})
+ configure_file(doxygen.cfg.in ${CMAKE_BINARY_DIR}/doxygen.cfg)
+ add_custom_target(doxygen-doc ${DOXYGEN_EXECUTABLE} ${CMAKE_BINARY_DIR}/doxygen.cfg)
+endif(DOXYGEN_FOUND)
diff --git a/qt4/cmake/modules/FindDBus.cmake b/qt4/cmake/modules/FindDBus.cmake
new file mode 100644
index 000000000..a0b7c3af5
--- /dev/null
+++ b/qt4/cmake/modules/FindDBus.cmake
@@ -0,0 +1,72 @@
+# - Try to find the low-level D-Bus library
+# Once done this will define
+#
+# DBUS_FOUND - system has D-Bus
+# DBUS_INCLUDE_DIR - the D-Bus include directory
+# DBUS_ARCH_INCLUDE_DIR - the D-Bus architecture-specific include directory
+# DBUS_LIBRARIES - the libraries needed to use D-Bus
+
+# Copyright (c) 2008, Kevin Kofler, <kevin.kofler@chello.at>
+# modeled after FindLibArt.cmake:
+# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+if (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES)
+
+ # in cache already
+ SET(DBUS_FOUND TRUE)
+
+else (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES)
+
+ IF (NOT WIN32)
+ FIND_PACKAGE(PkgConfig)
+ IF (PKG_CONFIG_FOUND)
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ pkg_check_modules(_DBUS_PC dbus-1)
+ ENDIF (PKG_CONFIG_FOUND)
+ ENDIF (NOT WIN32)
+
+ FIND_PATH(DBUS_INCLUDE_DIR dbus/dbus.h
+ ${_DBUS_PC_INCLUDE_DIRS}
+ /usr/include
+ /usr/include/dbus-1.0
+ /usr/local/include
+ )
+
+ FIND_PATH(DBUS_ARCH_INCLUDE_DIR dbus/dbus-arch-deps.h
+ ${_DBUS_PC_INCLUDE_DIRS}
+ /usr/lib${LIB_SUFFIX}/include
+ /usr/lib${LIB_SUFFIX}/dbus-1.0/include
+ /usr/lib64/include
+ /usr/lib64/dbus-1.0/include
+ /usr/lib/include
+ /usr/lib/dbus-1.0/include
+ )
+
+ FIND_LIBRARY(DBUS_LIBRARIES NAMES dbus-1 dbus
+ PATHS
+ ${_DBUS_PC_LIBDIR}
+ )
+
+
+ if (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES)
+ set(DBUS_FOUND TRUE)
+ endif (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES)
+
+
+ if (DBUS_FOUND)
+ if (NOT DBus_FIND_QUIETLY)
+ message(STATUS "Found D-Bus: ${DBUS_LIBRARIES}")
+ endif (NOT DBus_FIND_QUIETLY)
+ else (DBUS_FOUND)
+ if (DBus_FIND_REQUIRED)
+ message(FATAL_ERROR "Could NOT find D-Bus")
+ endif (DBus_FIND_REQUIRED)
+ endif (DBUS_FOUND)
+
+ MARK_AS_ADVANCED(DBUS_INCLUDE_DIR DBUS_ARCH_INCLUDE_DIR DBUS_LIBRARIES)
+
+endif (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES)
diff --git a/qt4/cmake/modules/FindDBusGLib.cmake b/qt4/cmake/modules/FindDBusGLib.cmake
new file mode 100644
index 000000000..af4bbe8c2
--- /dev/null
+++ b/qt4/cmake/modules/FindDBusGLib.cmake
@@ -0,0 +1,48 @@
+# Try to find the GLib binding of the DBus library
+# DBUS_GLIB_FOUND - system has dbus-glib
+# DBUS_GLIB_INCLUDE_DIR - the dbus-glib include directory
+# DBUS_GLIB_LIBRARIES - Link these to use dbus-glib
+
+# Copyright (c) 2008, Allen Winter <winter@kde.org>
+# Copyright (c) 2009, Andre Moreira Magalhaes <andrunko@gmail.com>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+set(DBUS_GLIB_FIND_REQUIRED ${DBusGLib_FIND_REQUIRED})
+if(DBUS_GLIB_INCLUDE_DIR AND DBUS_GLIB_LIBRARIES)
+ # Already in cache, be silent
+ set(DBUS_GLIB_FIND_QUIETLY TRUE)
+endif(DBUS_GLIB_INCLUDE_DIR AND DBUS_GLIB_LIBRARIES)
+
+find_package(PkgConfig)
+if(PKG_CONFIG_FOUND)
+ if (DBusGLib_FIND_VERSION_EXACT)
+ pkg_check_modules(PC_DBUS_GLIB QUIET dbus-glib=${DBusGLib_FIND_VERSION})
+ else (DBusGLib_FIND_VERSION_EXACT)
+ pkg_check_modules(PC_DBUS_GLIB QUIET dbus-glib>=${DBusGLib_FIND_VERSION})
+ endif (DBusGLib_FIND_VERSION_EXACT)
+endif(PKG_CONFIG_FOUND)
+
+find_path(DBUS_GLIB_INCLUDE_DIR
+ NAMES dbus-1.0/dbus/dbus-glib.h
+ HINTS
+ ${PC_DBUS_GLIB_INCLUDEDIR}
+ ${PC_DBUS_GLIB_INCLUDE_DIRS}
+)
+
+# HACK! Workaround appending "/dbus-1.0" to the HINTS above not working for some reason.
+set (DBUS_GLIB_INCLUDE_DIR
+ "${DBUS_GLIB_INCLUDE_DIR}/dbus-1.0"
+)
+
+find_library(DBUS_GLIB_LIBRARIES
+ NAMES dbus-glib-1
+ HINTS
+ ${PC_DBUS_GLIB_LIBDIR}
+ ${PC_DBUS_GLIB_LIBRARY_DIRS}
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(DBUS_GLIB DEFAULT_MSG
+ DBUS_GLIB_LIBRARIES DBUS_GLIB_INCLUDE_DIR)
diff --git a/qt4/cmake/modules/FindGIO.cmake b/qt4/cmake/modules/FindGIO.cmake
new file mode 100644
index 000000000..3f5e6d320
--- /dev/null
+++ b/qt4/cmake/modules/FindGIO.cmake
@@ -0,0 +1,37 @@
+# - Try to find the GIO libraries
+# Once done this will define
+#
+# GIO_FOUND - system has GIO
+# GIO_INCLUDE_DIR - the GIO include directory
+# GIO_LIBRARIES - GIO library
+#
+# Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright (C) 2011 Nokia Corporation
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+if(GIO_INCLUDE_DIR AND GIO_LIBRARIES)
+ # Already in cache, be silent
+ set(GIO_FIND_QUIETLY TRUE)
+endif(GIO_INCLUDE_DIR AND GIO_LIBRARIES)
+
+include(UsePkgConfig)
+pkg_check_modules(PC_LibGIO gio-2.0)
+
+find_path(GIO_MAIN_INCLUDE_DIR
+ NAMES gio/gio.h
+ HINTS ${PC_LibGIO_INCLUDEDIR}
+ PATH_SUFFIXES glib-2.0)
+
+find_library(GIO_LIBRARY
+ NAMES gio-2.0
+ HINTS ${PC_LibGIO_LIBDIR})
+
+set(GIO_INCLUDE_DIR "${GIO_MAIN_INCLUDE_DIR}")
+set(GIO_LIBRARIES "${GIO_LIBRARY}")
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GIO DEFAULT_MSG GIO_LIBRARIES GIO_MAIN_INCLUDE_DIR)
+
+mark_as_advanced(GIO_INCLUDE_DIR GIO_LIBRARIES)
diff --git a/qt4/cmake/modules/FindGIOUnix.cmake b/qt4/cmake/modules/FindGIOUnix.cmake
new file mode 100644
index 000000000..228a86add
--- /dev/null
+++ b/qt4/cmake/modules/FindGIOUnix.cmake
@@ -0,0 +1,31 @@
+# - Try to find the GIO unix libraries
+# Once done this will define
+#
+# GIOUNIX_FOUND - system has GIO unix
+# GIOUNIX_INCLUDE_DIR - the GIO unix include directory
+#
+# Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright (C) 2011 Nokia Corporation
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+if(GIOUNIX_INCLUDE_DIR)
+ # Already in cache, be silent
+ set(GIOUNIX_FIND_QUIETLY TRUE)
+endif(GIOUNIX_INCLUDE_DIR)
+
+include(UsePkgConfig)
+pkg_check_modules(PC_LibGIOUnix gio-unix-2.0)
+
+find_path(GIOUNIX_MAIN_INCLUDE_DIR
+ NAMES gio/gunixconnection.h
+ HINTS ${PC_LibGIOUnix_INCLUDEDIR}
+ PATH_SUFFIXES gio-unix-2.0)
+
+set(GIOUNIX_INCLUDE_DIR "${GIOUNIX_MAIN_INCLUDE_DIR}")
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GIOUNIX DEFAULT_MSG GIOUNIX_MAIN_INCLUDE_DIR)
+
+mark_as_advanced(GIOUNIX_INCLUDE_DIR)
diff --git a/qt4/cmake/modules/FindGLIB2.cmake b/qt4/cmake/modules/FindGLIB2.cmake
new file mode 100644
index 000000000..e40f05f9a
--- /dev/null
+++ b/qt4/cmake/modules/FindGLIB2.cmake
@@ -0,0 +1,52 @@
+# - Try to find the GLIB2 libraries
+# Once done this will define
+#
+# GLIB2_FOUND - system has glib2
+# GLIB2_INCLUDE_DIR - the glib2 include directory
+# GLIB2_LIBRARIES - glib2 library
+
+# Copyright (c) 2008 Laurent Montel, <montel@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+
+if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
+ # Already in cache, be silent
+ set(GLIB2_FIND_QUIETLY TRUE)
+endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
+
+find_package(PkgConfig)
+pkg_check_modules(PC_LibGLIB2 glib-2.0)
+
+find_path(GLIB2_MAIN_INCLUDE_DIR
+ NAMES glib.h
+ HINTS ${PC_LibGLIB2_INCLUDEDIR}
+ PATH_SUFFIXES glib-2.0)
+
+find_library(GLIB2_LIBRARY
+ NAMES glib-2.0
+ HINTS ${PC_LibGLIB2_LIBDIR}
+)
+
+set(GLIB2_LIBRARIES ${GLIB2_LIBRARY})
+
+# search the glibconfig.h include dir under the same root where the library is found
+get_filename_component(glib2LibDir "${GLIB2_LIBRARIES}" PATH)
+
+find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h
+ PATH_SUFFIXES glib-2.0/include
+ HINTS ${PC_LibGLIB2_INCLUDEDIR} "${glib2LibDir}" ${CMAKE_SYSTEM_LIBRARY_PATH})
+
+set(GLIB2_INCLUDE_DIR "${GLIB2_MAIN_INCLUDE_DIR}")
+
+# not sure if this include dir is optional or required
+# for now it is optional
+if(GLIB2_INTERNAL_INCLUDE_DIR)
+ set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} "${GLIB2_INTERNAL_INCLUDE_DIR}")
+endif(GLIB2_INTERNAL_INCLUDE_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GLIB2 DEFAULT_MSG GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR)
+
+mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES)
diff --git a/qt4/cmake/modules/FindGObject.cmake b/qt4/cmake/modules/FindGObject.cmake
new file mode 100644
index 000000000..1507b4303
--- /dev/null
+++ b/qt4/cmake/modules/FindGObject.cmake
@@ -0,0 +1,75 @@
+# - Try to find GObject
+# Once done this will define
+#
+# GOBJECT_FOUND - system has GObject
+# GOBJECT_INCLUDE_DIR - the GObject include directory
+# GOBJECT_LIBRARIES - the libraries needed to use GObject
+# GOBJECT_DEFINITIONS - Compiler switches required for using GObject
+
+# Copyright (c) 2008 Helio Chissini de Castro, <helio@kde.org>
+# (c)2006, Tim Beaulen <tbscope@gmail.com>
+
+
+IF (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
+ # in cache already
+ SET(GObject_FIND_QUIETLY TRUE)
+ELSE (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
+ SET(GObject_FIND_QUIETLY FALSE)
+ENDIF (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
+
+IF (NOT WIN32)
+ FIND_PACKAGE(PkgConfig REQUIRED)
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ PKG_CHECK_MODULES(PKG_GOBJECT2 REQUIRED gobject-2.0)
+ SET(GOBJECT_DEFINITIONS ${PKG_GOBJECT2_CFLAGS})
+ENDIF (NOT WIN32)
+
+FIND_PATH(GOBJECT_INCLUDE_DIR gobject/gobject.h
+ PATHS
+ ${PKG_GOBJECT2_INCLUDE_DIRS}
+ /usr/include/glib-2.0/
+ PATH_SUFFIXES glib-2.0
+ )
+
+FIND_LIBRARY(_GObjectLibs NAMES gobject-2.0
+ PATHS
+ ${PKG_GOBJECT2_LIBRARY_DIRS}
+ )
+FIND_LIBRARY(_GModuleLibs NAMES gmodule-2.0
+ PATHS
+ ${PKG_GOBJECT2_LIBRARY_DIRS}
+ )
+FIND_LIBRARY(_GThreadLibs NAMES gthread-2.0
+ PATHS
+ ${PKG_GOBJECT2_LIBRARY_DIRS}
+ )
+FIND_LIBRARY(_GLibs NAMES glib-2.0
+ PATHS
+ ${PKG_GOBJECT2_LIBRARY_DIRS}
+ )
+
+IF (WIN32)
+SET (GOBJECT_LIBRARIES ${_GObjectLibs} ${_GModuleLibs} ${_GThreadLibs} ${_GLibs})
+ELSE (WIN32)
+SET (GOBJECT_LIBRARIES ${PKG_GOBJECT2_LIBRARIES})
+ENDIF (WIN32)
+
+IF (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
+ SET(GOBJECT_FOUND TRUE)
+ELSE (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
+ SET(GOBJECT_FOUND FALSE)
+ENDIF (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
+
+IF (GOBJECT_FOUND)
+ IF (NOT GObject_FIND_QUIETLY)
+ MESSAGE(STATUS "Found GObject libraries: ${GOBJECT_LIBRARIES}")
+ MESSAGE(STATUS "Found GObject includes : ${GOBJECT_INCLUDE_DIR}")
+ ENDIF (NOT GObject_FIND_QUIETLY)
+ELSE (GOBJECT_FOUND)
+ IF (GObject_FIND_REQUIRED)
+ MESSAGE(STATUS "Could NOT find GObject")
+ ENDIF(GObject_FIND_REQUIRED)
+ENDIF (GOBJECT_FOUND)
+
+MARK_AS_ADVANCED(GOBJECT_INCLUDE_DIR GOBJECT_LIBRARIES)
diff --git a/qt4/cmake/modules/FindGStreamer.cmake b/qt4/cmake/modules/FindGStreamer.cmake
new file mode 100644
index 000000000..f6c5681cd
--- /dev/null
+++ b/qt4/cmake/modules/FindGStreamer.cmake
@@ -0,0 +1,80 @@
+# - Try to find GStreamer
+# Once done this will define
+#
+# GSTREAMER_FOUND - system has GStreamer
+# GSTREAMER_INCLUDE_DIR - the GStreamer include directory
+# GSTREAMER_LIBRARIES - the libraries needed to use GStreamer
+# GSTREAMER_DEFINITIONS - Compiler switches required for using GStreamer
+
+# Copyright (c) 2006, Tim Beaulen <tbscope@gmail.com>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+# TODO: Other versions --> GSTREAMER_X_Y_FOUND (Example: GSTREAMER_0_8_FOUND and GSTREAMER_0_10_FOUND etc)
+
+IF (GSTREAMER_INCLUDE_DIR AND GSTREAMER_LIBRARIES AND GSTREAMER_BASE_LIBRARY AND GSTREAMER_INTERFACE_LIBRARY)
+ # in cache already
+ SET(GSTREAMER_FIND_QUIETLY TRUE)
+ELSE (GSTREAMER_INCLUDE_DIR AND GSTREAMER_LIBRARIES AND GSTREAMER_BASE_LIBRARY AND GSTREAMER_INTERFACE_LIBRARY)
+ SET(GSTREAMER_FIND_QUIETLY FALSE)
+ENDIF (GSTREAMER_INCLUDE_DIR AND GSTREAMER_LIBRARIES AND GSTREAMER_BASE_LIBRARY AND GSTREAMER_INTERFACE_LIBRARY)
+
+IF (NOT WIN32)
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ FIND_PACKAGE(PkgConfig)
+ PKG_CHECK_MODULES(PC_GSTREAMER gstreamer-0.10)
+ #MESSAGE(STATUS "DEBUG: GStreamer include directory = ${GSTREAMER_INCLUDE_DIRS}")
+ #MESSAGE(STATUS "DEBUG: GStreamer link directory = ${GSTREAMER_LIBRARY_DIRS}")
+ #MESSAGE(STATUS "DEBUG: GStreamer CFlags = ${GSTREAMER_CFLAGS_OTHER}")
+ SET(GSTREAMER_DEFINITIONS ${PC_GSTREAMER_CFLAGS_OTHER})
+ENDIF (NOT WIN32)
+
+FIND_PATH(GSTREAMER_INCLUDE_DIR gst/gst.h
+ PATHS
+ ${PC_GSTREAMER_INCLUDEDIR}
+ ${PC_GSTREAMER_INCLUDE_DIRS}
+ PATH_SUFFIXES gstreamer-0.10
+ )
+
+FIND_LIBRARY(GSTREAMER_LIBRARIES NAMES gstreamer-0.10
+ PATHS
+ ${PC_GSTREAMER_LIBDIR}
+ ${PC_GSTREAMER_LIBRARY_DIRS}
+ )
+
+FIND_LIBRARY(GSTREAMER_BASE_LIBRARY NAMES gstbase-0.10
+ PATHS
+ ${PC_GSTREAMER_LIBDIR}
+ ${PC_GSTREAMER_LIBRARY_DIRS}
+ )
+
+FIND_LIBRARY(GSTREAMER_INTERFACE_LIBRARY NAMES gstinterfaces-0.10
+ PATHS
+ ${PC_GSTREAMER_LIBDIR}
+ ${PC_GSTREAMER_LIBRARY_DIRS}
+ )
+
+IF (GSTREAMER_INCLUDE_DIR)
+ #MESSAGE(STATUS "DEBUG: Found GStreamer include dir: ${GSTREAMER_INCLUDE_DIR}")
+ELSE (GSTREAMER_INCLUDE_DIR)
+ MESSAGE(STATUS "GStreamer: WARNING: include dir not found")
+ENDIF (GSTREAMER_INCLUDE_DIR)
+
+IF (GSTREAMER_LIBRARIES)
+ #MESSAGE(STATUS "DEBUG: Found GStreamer library: ${GSTREAMER_LIBRARIES}")
+ELSE (GSTREAMER_LIBRARIES)
+ MESSAGE(STATUS "GStreamer: WARNING: library not found")
+ENDIF (GSTREAMER_LIBRARIES)
+
+IF (GSTREAMER_INTERFACE_LIBRARY)
+ #MESSAGE(STATUS "DEBUG: Found GStreamer interface library: ${GSTREAMER_INTERFACE_LIBRARY}")
+ELSE (GSTREAMER_INTERFACE_LIBRARY)
+ MESSAGE(STATUS "GStreamer: WARNING: interface library not found")
+ENDIF (GSTREAMER_INTERFACE_LIBRARY)
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(GStreamer DEFAULT_MSG GSTREAMER_LIBRARIES GSTREAMER_INCLUDE_DIR GSTREAMER_BASE_LIBRARY GSTREAMER_INTERFACE_LIBRARY)
+
+MARK_AS_ADVANCED(GSTREAMER_INCLUDE_DIR GSTREAMER_LIBRARIES GSTREAMER_BASE_LIBRARY GSTREAMER_INTERFACE_LIBRARY)
diff --git a/qt4/cmake/modules/FindLibPython.py b/qt4/cmake/modules/FindLibPython.py
new file mode 100644
index 000000000..0cbd53c51
--- /dev/null
+++ b/qt4/cmake/modules/FindLibPython.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+import sys
+import distutils.sysconfig
+
+print("exec_prefix:%s" % sys.exec_prefix)
+print("short_version:%s" % sys.version[:3])
+print("long_version:%s" % sys.version.split()[0])
+print("py_inc_dir:%s" % distutils.sysconfig.get_python_inc())
+print("site_packages_dir:%s" % distutils.sysconfig.get_python_lib(plat_specific=1))
diff --git a/qt4/cmake/modules/FindLibXml2.cmake b/qt4/cmake/modules/FindLibXml2.cmake
new file mode 100644
index 000000000..353be5580
--- /dev/null
+++ b/qt4/cmake/modules/FindLibXml2.cmake
@@ -0,0 +1,57 @@
+# - Try to find LibXml2
+# Once done this will define
+#
+# LIBXML2_FOUND - System has LibXml2
+# LIBXML2_INCLUDE_DIR - The LibXml2 include directory
+# LIBXML2_LIBRARIES - The libraries needed to use LibXml2
+# LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2
+# LIBXML2_XMLLINT_EXECUTABLE - The XML checking tool xmllint coming with LibXml2
+
+# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+
+IF (LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARIES)
+ # in cache already
+ SET(LibXml2_FIND_QUIETLY TRUE)
+ENDIF (LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARIES)
+
+IF (NOT WIN32)
+ # use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ FIND_PACKAGE(PkgConfig)
+ PKG_CHECK_MODULES(PC_LIBXML libxml-2.0)
+ SET(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER})
+ENDIF (NOT WIN32)
+
+FIND_PATH(LIBXML2_INCLUDE_DIR libxml/xpath.h
+ HINTS
+ ${PC_LIBXML_INCLUDEDIR}
+ ${PC_LIBXML_INCLUDE_DIRS}
+ PATH_SUFFIXES libxml2
+ )
+
+FIND_LIBRARY(LIBXML2_LIBRARIES NAMES xml2 libxml2
+ HINTS
+ ${PC_LIBXML_LIBDIR}
+ ${PC_LIBXML_LIBRARY_DIRS}
+ )
+
+FIND_PROGRAM(LIBXML2_XMLLINT_EXECUTABLE xmllint)
+# for backwards compat. with KDE 4.0.x:
+SET(XMLLINT_EXECUTABLE "${LIBXML2_XMLLINT_EXECUTABLE}")
+
+IF( NOT LIBXML2_XMLLINT_EXECUTABLE )
+ MESSAGE(STATUS "xmllint program not found. Install it if you want validate generated doc file.")
+ENDIF(NOT LIBXML2_XMLLINT_EXECUTABLE )
+
+
+INCLUDE(FindPackageHandleStandardArgs)
+
+# handle the QUIETLY and REQUIRED arguments and set LIBXML2_FOUND to TRUE if
+# all listed variables are TRUE
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibXml2 DEFAULT_MSG LIBXML2_LIBRARIES LIBXML2_INCLUDE_DIR)
+
+MARK_AS_ADVANCED(LIBXML2_INCLUDE_DIR LIBXML2_LIBRARIES LIBXML2_XMLLINT_EXECUTABLE)
diff --git a/qt4/cmake/modules/FindPythonLibrary.cmake b/qt4/cmake/modules/FindPythonLibrary.cmake
new file mode 100644
index 000000000..258585936
--- /dev/null
+++ b/qt4/cmake/modules/FindPythonLibrary.cmake
@@ -0,0 +1,83 @@
+# FindPythonLibrary.cmake
+# ~~~~~~~~~~~~~~~~~~~~~~~
+# Find the Python interpreter and related Python directories.
+#
+# This file defines the following variables:
+#
+# PYTHON_EXECUTABLE - The path and filename of the Python interpreter.
+#
+# PYTHON_SHORT_VERSION - The version of the Python interpreter found,
+# excluding the patch version number. (e.g. 2.5 and not 2.5.1))
+#
+# PYTHON_LONG_VERSION - The version of the Python interpreter found as a human
+# readable string.
+#
+# PYTHON_SITE_PACKAGES_DIR - Location of the Python site-packages directory.
+#
+# PYTHON_INCLUDE_PATH - Directory holding the python.h include file.
+#
+# PYTHON_LIBRARY, PYTHON_LIBRARIES- Location of the Python library.
+
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+INCLUDE(CMakeFindFrameworks)
+
+if(EXISTS PYTHON_LIBRARY)
+ # Already in cache, be silent
+ set(PYTHONLIBRARY_FOUND TRUE)
+else(EXISTS PYTHON_LIBRARY)
+ FIND_PACKAGE(PythonInterp)
+
+ if(PYTHONINTERP_FOUND)
+ FIND_FILE(_find_lib_python_py FindLibPython.py PATHS ${CMAKE_MODULE_PATH})
+
+ EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_lib_python_py} OUTPUT_VARIABLE python_config)
+ if(python_config)
+ STRING(REGEX REPLACE ".*exec_prefix:([^\n]+).*$" "\\1" PYTHON_PREFIX ${python_config})
+ STRING(REGEX REPLACE ".*\nshort_version:([^\n]+).*$" "\\1" PYTHON_SHORT_VERSION ${python_config})
+ STRING(REGEX REPLACE ".*\nlong_version:([^\n]+).*$" "\\1" PYTHON_LONG_VERSION ${python_config})
+ STRING(REGEX REPLACE ".*\npy_inc_dir:([^\n]+).*$" "\\1" PYTHON_INCLUDE_PATH ${python_config})
+ STRING(REGEX REPLACE ".*\nsite_packages_dir:([^\n]+).*$" "\\1" PYTHON_SITE_PACKAGES_DIR ${python_config})
+ STRING(REGEX REPLACE "([0-9]+).([0-9]+)" "\\1\\2" PYTHON_SHORT_VERSION_NO_DOT ${PYTHON_SHORT_VERSION})
+ set(PYTHON_LIBRARY_NAMES python${PYTHON_SHORT_VERSION} python${PYTHON_SHORT_VERSION_NO_DOT})
+ if(WIN32)
+ STRING(REPLACE "\\" "/" PYTHON_SITE_PACKAGES_DIR ${PYTHON_SITE_PACKAGES_DIR})
+ endif(WIN32)
+ FIND_LIBRARY(PYTHON_LIBRARY NAMES ${PYTHON_LIBRARY_NAMES} PATHS ${PYTHON_PREFIX}/lib ${PYTHON_PREFIX}/libs NO_DEFAULT_PATH)
+ set(PYTHONLIBRARY_FOUND TRUE)
+ endif(python_config)
+
+ # adapted from cmake's builtin FindPythonLibs
+ if(APPLE)
+ CMAKE_FIND_FRAMEWORKS(Python)
+ set(PYTHON_FRAMEWORK_INCLUDES)
+ if(Python_FRAMEWORKS)
+ # If a framework has been selected for the include path,
+ # make sure "-framework" is used to link it.
+ if("${PYTHON_INCLUDE_PATH}" MATCHES "Python\\.framework")
+ set(PYTHON_LIBRARY "")
+ set(PYTHON_DEBUG_LIBRARY "")
+ endif("${PYTHON_INCLUDE_PATH}" MATCHES "Python\\.framework")
+ if(NOT PYTHON_LIBRARY)
+ set (PYTHON_LIBRARY "-framework Python" CACHE FILEPATH "Python Framework" FORCE)
+ endif(NOT PYTHON_LIBRARY)
+ set(PYTHONLIBRARY_FOUND TRUE)
+ endif(Python_FRAMEWORKS)
+ endif(APPLE)
+ endif(PYTHONINTERP_FOUND)
+
+ if(PYTHONLIBRARY_FOUND)
+ set(PYTHON_LIBRARIES ${PYTHON_LIBRARY})
+ if(NOT PYTHONLIBRARY_FIND_QUIETLY)
+ message(STATUS "Found Python executable: ${PYTHON_EXECUTABLE}")
+ message(STATUS "Found Python version: ${PYTHON_LONG_VERSION}")
+ endif(NOT PYTHONLIBRARY_FIND_QUIETLY)
+ else(PYTHONLIBRARY_FOUND)
+ if(PYTHONLIBRARY_FIND_REQUIRED)
+ message(FATAL_ERROR "Could not find Python")
+ endif(PYTHONLIBRARY_FIND_REQUIRED)
+ endif(PYTHONLIBRARY_FOUND)
+
+endif (EXISTS PYTHON_LIBRARY)
diff --git a/qt4/cmake/modules/FindTelepathyFarsight.cmake b/qt4/cmake/modules/FindTelepathyFarsight.cmake
new file mode 100644
index 000000000..65215d692
--- /dev/null
+++ b/qt4/cmake/modules/FindTelepathyFarsight.cmake
@@ -0,0 +1,49 @@
+# - Try to find Telepathy-Farsight
+# Once done this will define
+#
+# TELEPATHY_FARSIGHT_FOUND - system has TelepathyFarsight
+# TELEPATHY_FARSIGHT_INCLUDE_DIR - the TelepathyFarsight include directory
+# TELEPATHY_FARSIGHT_LIBRARIES - the libraries needed to use TelepathyFarsight
+# TELEPATHY_FARSIGHT_DEFINITIONS - Compiler switches required for using TelepathyFarsight
+
+# Copyright (c) 2010, Dario Freddi <dario.freddi@collabora.co.uk>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+
+if (TELEPATHY_FARSIGHT_INCLUDE_DIR AND TELEPATHY_FARSIGHT_LIBRARIES)
+ # in cache already
+ set(TelepathyFarsight_FIND_QUIETLY TRUE)
+else (TELEPATHY_FARSIGHT_INCLUDE_DIR AND TELEPATHY_FARSIGHT_LIBRARIES)
+ set(TelepathyFarsight_FIND_QUIETLY FALSE)
+endif (TELEPATHY_FARSIGHT_INCLUDE_DIR AND TELEPATHY_FARSIGHT_LIBRARIES)
+
+if (NOT WIN32)
+ # use pkg-config to get the directories and then use these values
+ # in the find_path() and find_library() calls
+ find_package(PkgConfig)
+ if (TELEPATHY_FARSIGHT_MIN_VERSION)
+ PKG_CHECK_MODULES(PC_TELEPATHY_FARSIGHT telepathy-farsight>=${TELEPATHY_FARSIGHT_MIN_VERSION})
+ else (TELEPATHY_FARSIGHT_MIN_VERSION)
+ PKG_CHECK_MODULES(PC_TELEPATHY_FARSIGHT telepathy-farsight)
+ endif (TELEPATHY_FARSIGHT_MIN_VERSION)
+ set(TELEPATHY_FARSIGHT_DEFINITIONS ${PC_TELEPATHY_FARSIGHT_CFLAGS_OTHER})
+endif (NOT WIN32)
+
+find_path(TELEPATHY_FARSIGHT_INCLUDE_DIR telepathy-farsight/channel.h
+ PATHS
+ ${PC_TELEPATHY_FARSIGHT_INCLUDEDIR}
+ ${PC_TELEPATHY_FARSIGHT_INCLUDE_DIRS}
+ PATH_SUFFIXES telepathy-1.0
+ )
+
+find_library(TELEPATHY_FARSIGHT_LIBRARIES NAMES telepathy-farsight
+ PATHS
+ ${PC_TELEPATHY_FARSIGHT_LIBDIR}
+ ${PC_TELEPATHY_FARSIGHT_LIBRARY_DIRS}
+ )
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(TelepathyFarsight DEFAULT_MSG TELEPATHY_FARSIGHT_LIBRARIES
+ TELEPATHY_FARSIGHT_INCLUDE_DIR)
+
+mark_as_advanced(TELEPATHY_FARSIGHT_INCLUDE_DIR TELEPATHY_FARSIGHT_LIBRARIES)
diff --git a/qt4/cmake/modules/FindTelepathyGlib.cmake b/qt4/cmake/modules/FindTelepathyGlib.cmake
new file mode 100644
index 000000000..8728dabba
--- /dev/null
+++ b/qt4/cmake/modules/FindTelepathyGlib.cmake
@@ -0,0 +1,54 @@
+# - Try to find Telepathy-Glib
+# Once done this will define
+#
+# TELEPATHY_GLIB_FOUND - system has Telepathy-Glib
+# TELEPATHY_GLIB_INCLUDE_DIR - the Telepathy-Glib include directory
+# TELEPATHY_GLIB_LIBRARIES - the libraries needed to use Telepathy-Glib
+# TELEPATHY_GLIB_DEFINITIONS - Compiler switches required for using Telepathy-Glib
+
+# Copyright (c) 2010, Dario Freddi <dario.freddi@collabora.co.uk>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+
+if (TELEPATHY_GLIB_INCLUDE_DIR AND TELEPATHY_GLIB_LIBRARIES)
+ # in cache already
+ set(TELEPATHYGLIB_FIND_QUIETLY TRUE)
+else (TELEPATHY_GLIB_INCLUDE_DIR AND TELEPATHY_GLIB_LIBRARIES)
+ set(TELEPATHYGLIB_FIND_QUIETLY FALSE)
+endif (TELEPATHY_GLIB_INCLUDE_DIR AND TELEPATHY_GLIB_LIBRARIES)
+
+if (NOT WIN32)
+ # use pkg-config to get the directories and then use these values
+ # in the find_path() and find_library() calls
+ find_package(PkgConfig)
+ if (TELEPATHY_GLIB_MIN_VERSION)
+ PKG_CHECK_MODULES(PC_TELEPATHY_GLIB telepathy-glib>=${TELEPATHY_GLIB_MIN_VERSION})
+ else (TELEPATHY_GLIB_MIN_VERSION)
+ PKG_CHECK_MODULES(PC_TELEPATHY_GLIB telepathy-glib)
+ endif (TELEPATHY_GLIB_MIN_VERSION)
+ set(TELEPATHY_GLIB_DEFINITIONS ${PC_TELEPATHY_GLIB_CFLAGS_OTHER})
+endif (NOT WIN32)
+
+if (TELEPATHY_GLIB_MIN_VERSION AND PKG_CONFIG_FOUND AND NOT PC_TELEPATHY_GLIB_FOUND)
+ message(STATUS "Telepathy-glib not found or its version is < ${TELEPATHY_GLIB_MIN_VERSION}")
+else (TELEPATHY_GLIB_MIN_VERSION AND PKG_CONFIG_FOUND AND NOT PC_TELEPATHY_GLIB_FOUND)
+ find_path(TELEPATHY_GLIB_INCLUDE_DIR telepathy-glib/client.h
+ PATHS
+ ${PC_TELEPATHY_GLIB_INCLUDEDIR}
+ ${PC_TELEPATHY_GLIB_INCLUDE_DIRS}
+ PATH_SUFFIXES telepathy-1.0
+ )
+
+ find_library(TELEPATHY_GLIB_LIBRARIES NAMES telepathy-glib
+ PATHS
+ ${PC_TELEPATHY_GLIB_LIBDIR}
+ ${PC_TELEPATHY_GLIB_LIBRARY_DIRS}
+ )
+
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(TelepathyGlib DEFAULT_MSG TELEPATHY_GLIB_LIBRARIES
+ TELEPATHY_GLIB_INCLUDE_DIR)
+
+ mark_as_advanced(TELEPATHY_GLIB_INCLUDE_DIR TELEPATHY_GLIB_LIBRARIES)
+
+endif (TELEPATHY_GLIB_MIN_VERSION AND PKG_CONFIG_FOUND AND NOT PC_TELEPATHY_GLIB_FOUND)
diff --git a/qt4/cmake/modules/MacroLogFeature.cmake b/qt4/cmake/modules/MacroLogFeature.cmake
new file mode 100644
index 000000000..86e1c3cbe
--- /dev/null
+++ b/qt4/cmake/modules/MacroLogFeature.cmake
@@ -0,0 +1,146 @@
+# This file defines the Feature Logging macros.
+#
+# MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]])
+# Logs the information so that it can be displayed at the end
+# of the configure run
+# VAR : TRUE or FALSE, indicating whether the feature is supported
+# FEATURE: name of the feature, e.g. "libjpeg"
+# DESCRIPTION: description what this feature provides
+# URL: home page
+# REQUIRED: TRUE or FALSE, indicating whether the featue is required
+# MIN_VERSION: minimum version number. empty string if unneeded
+# COMMENTS: More info you may want to provide. empty string if unnecessary
+#
+# MACRO_DISPLAY_FEATURE_LOG()
+# Call this to display the collected results.
+# Exits CMake with a FATAL error message if a required feature is missing
+#
+# Example:
+#
+# INCLUDE(MacroLogFeature)
+#
+# FIND_PACKAGE(JPEG)
+# MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "")
+# ...
+# MACRO_DISPLAY_FEATURE_LOG()
+
+# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
+# Copyright (c) 2006, Allen Winter, <winter@kde.org>
+# Copyright (c) 2009, Sebastian Trueg, <trueg@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+IF (NOT _macroLogFeatureAlreadyIncluded)
+ SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
+ IF (EXISTS ${_file})
+ FILE(REMOVE ${_file})
+ ENDIF (EXISTS ${_file})
+
+ SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
+ IF (EXISTS ${_file})
+ FILE(REMOVE ${_file})
+ ENDIF (EXISTS ${_file})
+
+ SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
+ IF (EXISTS ${_file})
+ FILE(REMOVE ${_file})
+ ENDIF (EXISTS ${_file})
+
+ SET(_macroLogFeatureAlreadyIncluded TRUE)
+ENDIF (NOT _macroLogFeatureAlreadyIncluded)
+
+
+MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments)
+
+ STRING(TOUPPER "${ARGV4}" _required)
+ SET(_minvers "${ARGV5}")
+ SET(_comments "${ARGV6}")
+
+ IF (${_var})
+ SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
+ ELSE (${_var})
+ IF ("${_required}" STREQUAL "TRUE")
+ SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
+ ELSE ("${_required}" STREQUAL "TRUE")
+ SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
+ ENDIF ("${_required}" STREQUAL "TRUE")
+ ENDIF (${_var})
+
+ SET(_logtext " * ${_package}")
+
+ IF (NOT ${_var})
+ IF (${_minvers} MATCHES ".*")
+ SET(_logtext "${_logtext} (${_minvers} or higher)")
+ ENDIF (${_minvers} MATCHES ".*")
+ SET(_logtext "${_logtext} <${_url}>\n ")
+ ELSE (NOT ${_var})
+ SET(_logtext "${_logtext} - ")
+ ENDIF (NOT ${_var})
+
+ SET(_logtext "${_logtext}${_description}")
+
+ IF (NOT ${_var})
+ IF (${_comments} MATCHES ".*")
+ SET(_logtext "${_logtext}\n ${_comments}")
+ ENDIF (${_comments} MATCHES ".*")
+# SET(_logtext "${_logtext}\n") #double-space missing features?
+ ENDIF (NOT ${_var})
+
+ FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n")
+
+ENDMACRO(MACRO_LOG_FEATURE)
+
+
+MACRO(MACRO_DISPLAY_FEATURE_LOG)
+
+ SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
+ SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
+ SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
+
+ IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile})
+ SET(_printSummary TRUE)
+ ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile})
+
+ IF(_printSummary)
+ SET(_missingDeps 0)
+ IF (EXISTS ${_enabledFile})
+ FILE(READ ${_enabledFile} _enabled)
+ FILE(REMOVE ${_enabledFile})
+ SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}")
+ ENDIF (EXISTS ${_enabledFile})
+
+
+ IF (EXISTS ${_disabledFile})
+ SET(_missingDeps 1)
+ FILE(READ ${_disabledFile} _disabled)
+ FILE(REMOVE ${_disabledFile})
+ SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}")
+ ENDIF (EXISTS ${_disabledFile})
+
+
+ IF (EXISTS ${_missingFile})
+ SET(_missingDeps 1)
+ FILE(READ ${_missingFile} _requirements)
+ SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}")
+ FILE(REMOVE ${_missingFile})
+ SET(_haveMissingReq 1)
+ ENDIF (EXISTS ${_missingFile})
+
+
+ IF (NOT ${_missingDeps})
+ SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.")
+ ENDIF (NOT ${_missingDeps})
+
+
+ MESSAGE(${_summary})
+ MESSAGE("-----------------------------------------------------------------------------\n")
+
+
+ IF(_haveMissingReq)
+ MESSAGE(FATAL_ERROR "Exiting: Missing Requirements")
+ ENDIF(_haveMissingReq)
+
+ ENDIF(_printSummary)
+
+ENDMACRO(MACRO_DISPLAY_FEATURE_LOG)
diff --git a/qt4/cmake/modules/TelepathyDefaults.cmake b/qt4/cmake/modules/TelepathyDefaults.cmake
new file mode 100644
index 000000000..0886fecf2
--- /dev/null
+++ b/qt4/cmake/modules/TelepathyDefaults.cmake
@@ -0,0 +1,146 @@
+# Enable testing using CTest
+enable_testing()
+
+# Always include srcdir and builddir in include path
+# This saves typing ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} in about every subdir
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# put the include dirs which are in the source or build tree
+# before all other include dirs, so the headers in the sources
+# are prefered over the already installed ones
+set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON)
+
+# Use colored output
+set(CMAKE_COLOR_MAKEFILE ON)
+
+# Set compiler flags
+if(CMAKE_COMPILER_IS_GNUCXX)
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -ggdb")
+ set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")
+ set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O2 -fno-reorder-blocks -fno-schedule-insns -fno-inline")
+ set(CMAKE_CXX_FLAGS_DEBUGFULL "-O0 -g3 -ggdb -fno-inline")
+ set(CMAKE_CXX_FLAGS_PROFILE "-pg -g3 -ggdb -DNDEBUG")
+
+ set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -ggdb")
+ set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
+ set(CMAKE_C_FLAGS_DEBUG "-ggdb -O2 -fno-reorder-blocks -fno-schedule-insns -fno-inline")
+ set(CMAKE_C_FLAGS_DEBUGFULL "-O0 -g3 -ggdb -fno-inline")
+ set(CMAKE_C_FLAGS_PROFILE "-pg -g3 -ggdb -DNDEBUG")
+
+ set(CMAKE_EXE_LINKER_FLAGS_PROFILE "-pg -ggdb")
+ set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "-pg -ggdb")
+
+ set(DISABLE_WERROR 0 CACHE BOOL "compile without -Werror (normally enabled in development builds)")
+
+ include(CompilerWarnings)
+ include(TestCXXAcceptsFlag)
+
+ CHECK_CXX_ACCEPTS_FLAG("-fvisibility=hidden" CXX_FVISIBILITY_HIDDEN)
+ if (CXX_FVISIBILITY_HIDDEN)
+ set(VISIBILITY_HIDDEN_FLAGS "-fvisibility=hidden")
+ else (CXX_FVISIBILITY_HIDDEN)
+ set(VISIBILITY_HIDDEN_FLAGS)
+ endif (CXX_FVISIBILITY_HIDDEN)
+
+ CHECK_CXX_ACCEPTS_FLAG("-Wdeprecated-declarations" CXX_DEPRECATED_DECLARATIONS)
+ if (CXX_DEPRECATED_DECLARATIONS)
+ set(DEPRECATED_DECLARATIONS_FLAGS "-Wdeprecated-declarations -DTELEPATHY_QT4_DEPRECATED_WARNINGS")
+ else (CXX_DEPRECATED_DECLARATIONS)
+ set(DEPRECATED_DECLARATIONS_FLAGS)
+ endif (CXX_DEPRECATED_DECLARATIONS)
+
+ if(${TP_QT4_NANO_VERSION} EQUAL 0)
+ set(NOT_RELEASE 0)
+ else(${TP_QT4_NANO_VERSION} EQUAL 0)
+ set(NOT_RELEASE 1)
+ endif(${TP_QT4_NANO_VERSION} EQUAL 0)
+
+ set(desired
+ all
+ extra
+ sign-compare
+ pointer-arith
+ format-security
+ init-self
+ non-virtual-dtor)
+ set(undesired
+ missing-field-initializers
+ unused-parameter)
+ compiler_warnings(CMAKE_CXX_FLAGS_WARNINGS cxx ${NOT_RELEASE} "${desired}" "${undesired}")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_WARNINGS}")
+
+ set(desired_c
+ all
+ extra
+ declaration-after-statement
+ shadow
+ strict-prototypes
+ missing-prototypes
+ sign-compare
+ nested-externs
+ pointer-arith
+ format-security
+ init-self)
+ set(undesired_c
+ missing-field-initializers
+ unused-parameter)
+ compiler_warnings(CMAKE_C_FLAGS_WARNINGS c ${NOT_RELEASE} "${desired_c}" "${undesired_c}")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_WARNINGS}")
+
+ # Link development builds with -Wl,--no-add-needed
+ # TODO: binutils 2.21 renames the flag to --no-copy-dt-needed-entries, though it keeps the old
+ # one as a deprecated alias.
+ if(${NOT_RELEASE} EQUAL 1)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-add-needed")
+ endif(${NOT_RELEASE} EQUAL 1)
+
+ if(CMAKE_SYSTEM_NAME MATCHES Linux)
+ add_definitions(-D_BSD_SOURCE)
+ endif(CMAKE_SYSTEM_NAME MATCHES Linux)
+
+ # Compiler coverage
+ set(ENABLE_COMPILER_COVERAGE OFF CACHE BOOL "Enables compiler coverage tests through lcov. Enabling this option will build
+Telepathy-Qt4 as a static library.")
+
+ if (ENABLE_COMPILER_COVERAGE)
+ check_cxx_accepts_flag("-fprofile-arcs -ftest-coverage" CXX_FPROFILE_ARCS)
+ check_cxx_accepts_flag("-ftest-coverage" CXX_FTEST_COVERAGE)
+
+ if (CXX_FPROFILE_ARCS AND CXX_FTEST_COVERAGE)
+ find_program(LCOV lcov)
+ find_program(LCOV_GENHTML genhtml)
+ if (NOT LCOV OR NOT LCOV_GENHTML)
+ message(FATAL_ERROR "You chose to use compiler coverage tests, but lcov or genhtml could not be found in your PATH.")
+ else (NOT LCOV OR NOT LCOV_GENHTML)
+ message(STATUS "Compiler coverage tests enabled - Telepathy-Qt4 will be compiled as a static library")
+ set(COMPILER_COVERAGE_FLAGS "-fprofile-arcs -ftest-coverage")
+ endif (NOT LCOV OR NOT LCOV_GENHTML)
+ else (CXX_FPROFILE_ARCS AND CXX_FTEST_COVERAGE)
+ message(FATAL_ERROR "You chose to use compiler coverage tests, but it appears your compiler is not able to support them.")
+ endif (CXX_FPROFILE_ARCS AND CXX_FTEST_COVERAGE)
+ else (ENABLE_COMPILER_COVERAGE)
+ set(COMPILER_COVERAGE_FLAGS)
+ endif (ENABLE_COMPILER_COVERAGE)
+
+ # gcc under Windows
+ if(MINGW)
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--export-all-symbols -Wl,--disable-auto-import")
+ set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--export-all-symbols -Wl,--disable-auto-import")
+
+ # we always link against the release version of QT with mingw
+ # (even for debug builds). So we need to define QT_NO_DEBUG
+ # or else QPluginLoader rejects plugins because it thinks
+ # they're built against the wrong QT.
+ add_definitions(-DQT_NO_DEBUG)
+ endif(MINGW)
+endif(CMAKE_COMPILER_IS_GNUCXX)
+
+if(MSVC)
+ set(ESCAPE_CHAR ^)
+endif(MSVC)
+
+set(LIB_SUFFIX "" CACHE STRING "Define suffix of library directory name (32/64)" )
+set(LIB_INSTALL_DIR "lib${LIB_SUFFIX}" CACHE PATH "The subdirectory where libraries will be installed (default is ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX})" FORCE)
+set(INCLUDE_INSTALL_DIR "include" CACHE PATH "The subdirectory where header files will be installed (default is ${CMAKE_INSTALL_PREFIX}/include)" FORCE)
+set(DATA_INSTALL_DIR "share/telepathy" CACHE PATH "The subdirectory where data files will be installed (default is ${CMAKE_INSTALL_PREFIX}/share/telepathy)" FORCE)
diff --git a/qt4/cmake/modules/TelepathyDist.cmake b/qt4/cmake/modules/TelepathyDist.cmake
new file mode 100644
index 000000000..37b7e69c7
--- /dev/null
+++ b/qt4/cmake/modules/TelepathyDist.cmake
@@ -0,0 +1,110 @@
+# setup make dist
+add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+ COMMAND git archive --format=tar --prefix=${PACKAGE_NAME}-${PACKAGE_VERSION}/ HEAD |
+ gzip > ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+
+add_custom_target(create-source-working-dir
+ rm -rf ${PACKAGE_NAME}-${PACKAGE_VERSION} &&
+ gzip -df ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz &&
+ tar -xf ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar &&
+ rm ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar* &&
+ cd ${PACKAGE_NAME}-${PACKAGE_VERSION}/ &&
+ rm -rf doc && mkdir doc && cp -R ${CMAKE_BINARY_DIR}/doc/html doc/
+
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ DEPENDS ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+ COMMENT "Generating working source dir for the dist tarball")
+add_dependencies(create-source-working-dir doxygen-doc)
+
+add_custom_target(dist-hook
+ chmod u+w ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}/ChangeLog &&
+ git log --stat > ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}/ChangeLog ||
+ git log > ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}/ChangeLog
+
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ COMMENT "Updating Changelog")
+add_dependencies(dist-hook create-source-working-dir)
+
+add_custom_target(dist tar --format=ustar -chf - ${PACKAGE_NAME}-${PACKAGE_VERSION} |
+ GZIP=--best gzip -c > ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ COMMENT "Generating dist tarball")
+add_dependencies(dist dist-hook)
+
+# setup make distcheck
+add_custom_target(distcheck rm -rf build && mkdir build && cd build && cmake .. && make && make check
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}/
+ COMMENT "Testing successful tarball build")
+add_dependencies(distcheck dist)
+
+# CPack
+set(ENABLE_CPACK OFF CACHE BOOL "Enables CPack targets generation")
+if (ENABLE_CPACK)
+
+ include(InstallRequiredSystemLibraries)
+
+ SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A high-level binding for Telepathy in Qt4")
+ SET(CPACK_PACKAGE_VENDOR "Collabora Ltd.")
+ SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README")
+ SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING")
+ SET(CPACK_PACKAGE_VERSION_MAJOR ${TP_QT4_MAJOR_VERSION})
+ SET(CPACK_PACKAGE_VERSION_MINOR ${TP_QT4_MINOR_VERSION})
+ SET(CPACK_PACKAGE_VERSION_PATCH ${TP_QT4_MICRO_VERSION})
+ SET(CPACK_PACKAGE_INSTALL_DIRECTORY "TelepathyQt4")
+ SET(CPACK_PACKAGE_CONTACT "telepathy@lists.freedesktop.org")
+ set(CPACK_SOURCE_IGNORE_FILES
+ "/build/;/.bzr/;~$;/.git/;/.kdev4/;${CPACK_SOURCE_IGNORE_FILES}")
+ IF(WIN32 AND NOT UNIX)
+ # There is a bug in NSI that does not handle full unix paths properly. Make
+ # sure there is at least one set of four (4) backlasshes.
+ #SET(CPACK_PACKAGE_ICON "${CMake_SOURCE_DIR}/Utilities/Release\\\\InstallIcon.bmp")
+ #SET(CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\MyExecutable.exe")
+ SET(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY} TelepathyQt4")
+ #SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.github.com")
+ #SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.my-personal-home-page.com")
+ #SET(CPACK_NSIS_CONTACT "me@my-personal-home-page.com")
+ SET(CPACK_NSIS_MODIFY_PATH ON)
+ ELSE(WIN32 AND NOT UNIX)
+ #SET(CPACK_STRIP_FILES "bin/MyExecutable")
+ SET(CPACK_SOURCE_STRIP_FILES "")
+ ENDIF(WIN32 AND NOT UNIX)
+ #SET(CPACK_PACKAGE_EXECUTABLES "MyExecutable" "My Executable")
+
+ if (APPLE)
+ set(CPACK_SET_DESTDIR ON)
+ set(CPACK_PACKAGE_RELOCATABLE OFF)
+ endif (APPLE)
+
+ #name components
+ set(CPACK_COMPONENT_MAINLIBRARY_DISPLAY_NAME "TelepathyQt4 main components")
+ set(CPACK_COMPONENT_FARSIGHT_DISPLAY_NAME "TelepathyQt4 Farsight support")
+ set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "Development files for TelepathyQt4")
+ set(CPACK_COMPONENT_FARSIGHT_HEADERS_DISPLAY_NAME "Development files for TelepathyQt4-Farsight")
+
+ #components description
+ set(CPACK_COMPONENT_MAINLIBRARY_DESCRIPTION
+ "The main TelepathyQt4 library")
+ set(CPACK_COMPONENT_FARSIGHT_DESCRIPTION
+ "The TelepathyQt4-Farsight library")
+ set(CPACK_COMPONENT_HEADERS_DESCRIPTION
+ "Development files for TelepathyQt4")
+ set(CPACK_COMPONENT_FARSIGHT_HEADERS_DESCRIPTION
+ "Development files for TelepathyQt4-Farsight")
+
+ set(CPACK_COMPONENT_HEADERS_DEPENDS mainlibrary)
+ set(CPACK_COMPONENT_FARSIGHT_DEPENDS mainlibrary)
+ set(CPACK_COMPONENT_FARSIGHT_HEADERS_DEPENDS mainlibrary farsight)
+
+ #installation types
+ set(CPACK_ALL_INSTALL_TYPES User Developer Minimal)
+
+ set(CPACK_COMPONENT_MAINLIBRARY_INSTALL_TYPES User Developer Minimal)
+ set(CPACK_COMPONENT_FARSIGHT_INSTALL_TYPES User Developer)
+ set(CPACK_COMPONENT_HEADERS_INSTALL_TYPES Developer)
+ set(CPACK_COMPONENT_FARSIGHT_HEADERS_INSTALL_TYPES Developer)
+
+ # Leave this as the last declaration, always!!!
+ include(CPack)
+
+endif (ENABLE_CPACK)
diff --git a/qt4/cmake/modules/TpQt4Macros.cmake b/qt4/cmake/modules/TpQt4Macros.cmake
new file mode 100644
index 000000000..20bb399ae
--- /dev/null
+++ b/qt4/cmake/modules/TpQt4Macros.cmake
@@ -0,0 +1,435 @@
+# - Common macros for Tp-Qt4
+
+# Copyright (c) 2010, Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+
+#
+# These macros/functions are not exported - they are meant for internal usage into Telepathy-Qt4's build system.
+#
+# Preamble: How dynamic generators are handled with the CMake build system.
+# Telepathy-Qt4 strongly relies upon lots of files generated at build time through some python programs, found
+# in tools/. To avoid developers the struggle of handling those manually, a set of convenience macros have been
+# created to handle them with the correct dependencies. Each of those macros takes a target name as a first argument
+# and creates a target with that exact name. In a similar fashion, in the last argument you can specify a list
+# of targets the generated target will depend on. This way, you can handle transparently dependencies between
+# generated files, while the dirty stuff is done for you in the background.
+#
+# macro TPQT4_EXTRACT_DEPENDS (tpqt4_other tpqt4_depends)
+# Internal macro used to extract arguments from ARGN
+#
+# function TPQT4_CREATE_MOC_COMMAND_TARGET_DEPS(inputfile outputfile moc_flags moc_options target_dependencies ...)
+# This function behaves exactly like qt4_create_moc_command, but creates a custom target for the
+# moc file generation, allowing to specify a list of targets the generated moc target will depend on.
+# Just like qt4_create_moc_command, it is an internal macro and it's not meant to be used explicitely.
+#
+# function TPQT4_GENERATE_MOC_I(inputfile outputfile)
+# This function behaves exactly like qt4_generate_moc, but it generates moc files with the -i option,
+# which disables the generation of an #include directive. This macro has to be used always when building
+# Tp-Qt4 internals due to the internal header files restrictions.
+#
+# function TPQT4_GENERATE_MOC_I_TARGET_DEPS(inputfile outputfile target_dependencies ...)
+# This function acts as an overload to QT4_GENERATE_MOC_I: it does exactly the same thing, but creates a
+# custom target for the moc file generation, and adds target_dependencies to it as dependencies.
+#
+# function TPQT4_GENERATE_MOCS(sourcefile ...)
+# Generates mocs from a list of header files. You usually want to use this function when building tests
+# or examples. Please remember the list of the header files passed to this function MUST be added to the
+# target's sources.
+#
+# function TPQT4_CLIENT_GENERATOR(spec group pretty_include namespace [arguments] [DEPENDS dependencies ...])
+# This function takes care of invoking qt4-client-gen.py with the correct arguments, which generates
+# headers out of specs. spec is the name of the spec headers will be generated from, group represents
+# the spec's group, pretty_include is the name of the capitalized header (for example ClientGenerator),
+# namespace is the C++ namespace the generated header will belong to. This function also accepts
+# as an optional last argument a list of additional command line arguments which will be passed to
+# qt4-client-gen.py upon execution. After issuing DEPENDS in the last argument you can pass a list of targets
+# the generated target will depend on.
+#
+# function TPQT4_FUTURE_CLIENT_GENERATOR(spec namespace [arguments] [DEPENDS dependencies ...])
+# Same as tpqt4_client_generator, but for future interfaces
+#
+# function TPQT4_GENERATE_MANAGER_FILE(MANAGER_FILE OUTPUT_FILENAME DEPEND_FILENAME)
+# This function takes care of invoking manager-file.py with the correct arguments. The first argument is the
+# path to the manager-file.py file which should be used, the second is the output filename of the manager,
+# and the third is the path to the file which depends on the generated manager file.
+#
+# function TPQT4_XINCLUDATOR (TARGET_NAME INPUT_FILE OUTPUT_FILE [additional_arguments ...] [DEPENDS dependencies ...])
+# This function takes care of invoking xincludator.py with the correct arguments. TARGET_NAME is the name of
+# the generated target (see preamble), INPUT_FILE is the input spec file, OUTPUT_FILE is the filename
+# the generated file will be saved to. This function also accepts as an optional last argument a list of
+# additional command line arguments which will be passed to xincludator upon execution.
+# After issuing DEPENDS in the last argument you can pass a list of targets the generated target will depend on.
+#
+# function TPQT4_CONSTANTS_GEN (TARGET_NAME SPEC_XML OUTPUT_FILE [additional_arguments ...] [DEPENDS dependencies ...])
+# This function takes care of invoking qt4-constants-gen.py with the correct arguments. TARGET_NAME is the name of
+# the generated target (see preamble), SPEC_XML is the spec input file, OUTPUT_FILE is the filename
+# the generated file will be saved to. This function also accepts as an optional last argument a list of
+# additional command line arguments which will be passed to qt4-constants-gen.py upon execution.
+# After issuing DEPENDS in the last argument you can pass a list of targets the generated target will depend on.
+#
+# function TPQT4_TYPES_GEN (TARGET_NAME SPEC_XML OUTFILE_DECL OUTFILE_IMPL NAMESPACE
+# REAL_INCLUDE PRETTY_INCLUDE [additional_arguments ...] [DEPENDS dependencies ...])
+# This function takes care of invoking qt4-types-gen.py with the correct arguments. TARGET_NAME is the name of
+# the generated target (see preamble), SPEC_XML is the input spec file, OUTFILE_DECL is the filename
+# the header of the generated file will be saved to, OUTFILE_IMPL is the filename the implementation of the
+# generated file will be saved to, NAMESPACE is the C++ namespace the generated header will belong to,
+# REAL_INCLUDE is the real include file you want to use, PRETTY_INCLUDE is the name of the capitalized header
+# (for example ClientGenerator).
+# This function also accepts as an optional last argument a list of additional command line arguments
+# which will be passed to qt4-constants-gen.py upon execution.
+# After issuing DEPENDS in the last argument you can pass a list of targets the generated target will depend on.
+#
+# macro TPQT4_ADD_GENERIC_UNIT_TEST (fancyName name [libraries ...])
+# This macro takes care of building and adding a generic unit test to the automatic CTest suite. The requirement
+# for using this macro is to have the unit test contained in a single source file named ${name}.cpp. fancyName will
+# be used as the test and target's name, and you can specify as a third and optional argument a set of additional
+# libraries the target will link to.
+#
+# macro TPQT4_ADD_DBUS_UNIT_TEST (fancyName name [libraries ...])
+# This macro takes care of building and adding an unit test requiring DBus emulation to the automatic
+# CTest suite. The requirement for using this macro is to have the unit test contained in a single
+# source file named ${name}.cpp. fancyName will be used as the test and target's name, and you can specify as a third
+# and optional argument a set of additional libraries the target will link to. Please remember that you need to
+# set up the DBus environment by calling TPQT4_SETUP_DBUS_TEST_ENVIRONMENT BEFORE you call this macro.
+#
+# macro _TPQT4_ADD_CHECK_TARGETS (fancyName name command [args])
+# This is an internal macro which is meant to be used by TPQT4_ADD_DBUS_UNIT_TEST and TPQT4_ADD_GENERIC_UNIT_TEST.
+# It takes care of generating a check target for each test method available (currently normal execution, valgrind and
+# callgrind). This macro accepts the same arguments as the add test macros, but accepts a command and a list of
+# arguments for running the test instead of the link libraries. However, you are not meant to call this macro from
+# your CMakeLists.txt files.
+#
+# function TPQT4_SETUP_DBUS_TEST_ENVIRONMENT ()
+# This function MUST be called before calling TPQT4_ADD_DBUS_UNIT_TEST. It takes care of preparing the test
+# environment for DBus tests and generating the needed files.
+#
+
+MACRO (TPQT4_EXTRACT_DEPENDS _tpqt4_other _tpqt4_depends)
+ SET(${_tpqt4_other})
+ SET(${_tpqt4_depends})
+ SET(_TPQT4_DOING_DEPENDS FALSE)
+ FOREACH(_currentArg ${ARGN})
+ IF ("${_currentArg}" STREQUAL "DEPENDS")
+ SET(_TPQT4_DOING_DEPENDS TRUE)
+ ELSE ("${_currentArg}" STREQUAL "DEPENDS")
+ IF(_TPQT4_DOING_DEPENDS)
+ LIST(APPEND ${_tpqt4_depends} "${_currentArg}")
+ ELSE(_TPQT4_DOING_DEPENDS)
+ LIST(APPEND ${_tpqt4_other} "${_currentArg}")
+ ENDIF(_TPQT4_DOING_DEPENDS)
+ ENDIF ("${_currentArg}" STREQUAL "DEPENDS")
+ ENDFOREACH(_currentArg)
+ENDMACRO (TPQT4_EXTRACT_DEPENDS)
+
+# helper function to set up a moc rule
+FUNCTION (TPQT4_CREATE_MOC_COMMAND_TARGET_DEPS infile outfile moc_flags moc_options)
+ # For Windows, create a parameters file to work around command line length limit
+ GET_FILENAME_COMPONENT(_moc_outfile_name "${outfile}" NAME)
+ IF (WIN32)
+ # Pass the parameters in a file. Set the working directory to
+ # be that containing the parameters file and reference it by
+ # just the file name. This is necessary because the moc tool on
+ # MinGW builds does not seem to handle spaces in the path to the
+ # file given with the @ syntax.
+ GET_FILENAME_COMPONENT(_moc_outfile_dir "${outfile}" PATH)
+ IF(_moc_outfile_dir)
+ SET(_moc_working_dir WORKING_DIRECTORY ${_moc_outfile_dir})
+ ENDIF(_moc_outfile_dir)
+ SET (_moc_parameters_file ${outfile}_parameters)
+ SET (_moc_parameters ${moc_flags} ${moc_options} -o "${outfile}" "${infile}")
+ FILE (REMOVE ${_moc_parameters_file})
+ FOREACH(arg ${_moc_parameters})
+ FILE (APPEND ${_moc_parameters_file} "${arg}\n")
+ ENDFOREACH(arg)
+ ADD_CUSTOM_COMMAND(OUTPUT ${outfile}
+ COMMAND ${QT_MOC_EXECUTABLE} @${_moc_outfile_name}_parameters
+ DEPENDS ${infile}
+ ${_moc_working_dir}
+ VERBATIM)
+ ELSE (WIN32)
+ ADD_CUSTOM_COMMAND(OUTPUT ${outfile}
+ COMMAND ${QT_MOC_EXECUTABLE}
+ ARGS ${moc_flags} ${moc_options} -o ${outfile} ${infile}
+ DEPENDS ${infile})
+ ENDIF (WIN32)
+
+ add_custom_target(moc-${_moc_outfile_name} DEPENDS ${outfile})
+ add_dependencies(moc-${_moc_outfile_name} ${ARGN})
+ENDFUNCTION (TPQT4_CREATE_MOC_COMMAND_TARGET_DEPS)
+
+# add the -i option to QT4_GENERATE_MOC
+function(TPQT4_GENERATE_MOC_I infile outfile)
+ qt4_get_moc_flags(moc_flags)
+ get_filename_component(abs_infile ${infile} ABSOLUTE)
+ qt4_create_moc_command(${abs_infile} ${outfile} "${moc_flags}" "-i")
+ set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC TRUE) # dont run automoc on this file
+endfunction(TPQT4_GENERATE_MOC_I)
+
+# same as tpqt4_generate_moc_i, but lets the caller specify a list of targets which the mocs should depend on
+function(TPQT4_GENERATE_MOC_I_TARGET_DEPS infile outfile)
+ qt4_get_moc_flags(moc_flags)
+ get_filename_component(abs_infile ${infile} ABSOLUTE)
+ tpqt4_create_moc_command_target_deps(${abs_infile} ${outfile} "${moc_flags}" "-i" ${ARGN})
+ set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC TRUE) # dont run automoc on this file
+endfunction(TPQT4_GENERATE_MOC_I_TARGET_DEPS)
+
+# generates mocs for the passed list. The list should be added to the target's sources
+function(tpqt4_generate_mocs)
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen" )
+ foreach(moc_src ${ARGN})
+ string(REPLACE ".h" ".moc.hpp" generated_file ${moc_src})
+ tpqt4_generate_moc_i(${CMAKE_CURRENT_SOURCE_DIR}/${moc_src} ${CMAKE_CURRENT_BINARY_DIR}/_gen/${generated_file})
+ macro_add_file_dependencies(${moc_src} ${CMAKE_CURRENT_BINARY_DIR}/_gen/${generated_file})
+ endforeach(moc_src ${ARGN})
+endfunction(tpqt4_generate_mocs)
+
+function(tpqt4_client_generator spec group pretty_include namespace)
+ tpqt4_extract_depends(client_generator_args client_generator_depends ${ARGN})
+ set(ARGS
+ ${CMAKE_SOURCE_DIR}/tools/qt4-client-gen.py
+ --group=${group}
+ --namespace=${namespace}
+ --typesnamespace=Tp
+ --headerfile=${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.h
+ --implfile=${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}-body.hpp
+ --realinclude=TelepathyQt4/${spec}.h
+ --prettyinclude=TelepathyQt4/${pretty_include}
+ --specxml=${CMAKE_CURRENT_BINARY_DIR}/_gen/stable-spec.xml
+ --ifacexml=${CMAKE_CURRENT_BINARY_DIR}/_gen/spec-${spec}.xml
+ --extraincludes=${TYPES_INCLUDE}
+ --must-define=IN_TELEPATHY_QT4_HEADER
+ --visibility=TELEPATHY_QT4_EXPORT
+ ${client_generator_args})
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}-body.hpp
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${ARGS}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/libqt4codegen.py
+ ${CMAKE_SOURCE_DIR}/tools/qt4-client-gen.py)
+ add_custom_target(generate_cli-${spec}-body DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}-body.hpp)
+ add_dependencies(all-generated-sources generate_cli-${spec}-body)
+
+ if (client_generator_depends)
+ add_dependencies(generate_cli-${spec}-body ${client_generator_depends})
+ endif (client_generator_depends)
+
+ tpqt4_generate_moc_i_target_deps(${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.moc.hpp
+ "generate_cli-${spec}-body")
+ list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.moc.hpp)
+endfunction(tpqt4_client_generator spec group pretty_include namespace)
+
+function(tpqt4_future_client_generator spec namespace)
+ tpqt4_extract_depends(future_client_generator_args future_client_generator_depends ${ARGN})
+ set(ARGS
+ ${CMAKE_SOURCE_DIR}/tools/qt4-client-gen.py
+ --namespace=${namespace}
+ --typesnamespace=TpFuture
+ --headerfile=${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.h
+ --implfile=${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}-body.hpp
+ --realinclude=TelepathyQt4/future-internal.h
+ --prettyinclude=TelepathyQt4/future-internal.h
+ --specxml=${CMAKE_CURRENT_BINARY_DIR}/_gen/future-spec.xml
+ --ifacexml=${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.xml
+ --extraincludes=${TYPES_INCLUDE}
+ --extraincludes='<TelepathyQt4/Types>'
+ --extraincludes='<TelepathyQt4/future-internal.h>'
+ --visibility=TELEPATHY_QT4_NO_EXPORT
+ ${future_client_generator_args})
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}-body.hpp
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${ARGS}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/libqt4codegen.py
+ ${CMAKE_SOURCE_DIR}/tools/qt4-client-gen.py)
+ add_custom_target(generate_future-${spec}-body DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}-body.hpp)
+ add_dependencies(all-generated-sources generate_future-${spec}-body)
+
+ if (future_client_generator_depends)
+ add_dependencies(generate_future-${spec}-body ${future_client_generator_depends})
+ endif (future_client_generator_depends)
+
+ tpqt4_generate_moc_i_target_deps(${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.moc.hpp
+ "generate_future-${spec}-body")
+endfunction(tpqt4_future_client_generator spec namespace)
+
+# This function is used for generating CM in various examples
+function(tpqt4_generate_manager_file MANAGER_FILE OUTPUT_FILENAME DEPEND_FILENAME)
+ # make_directory is required, otherwise the command won't work!!
+ make_directory(${CMAKE_CURRENT_BINARY_DIR}/_gen)
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/param-spec-struct.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/${OUTPUT_FILENAME}
+
+ COMMAND ${PYTHON_EXECUTABLE}
+
+ ARGS ${CMAKE_SOURCE_DIR}/tools/manager-file.py
+ ${MANAGER_FILE}
+ _gen
+
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/manager-file.py)
+
+ set_source_files_properties(${DEPEND_FILENAME}
+ PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_gen/param-spec-struct.h)
+endfunction(tpqt4_generate_manager_file MANAGER_FILE)
+
+function(tpqt4_xincludator _TARGET_NAME _INPUT_FILE _OUTPUT_FILE)
+ tpqt4_extract_depends(xincludator_gen_args xincludator_gen_depends ${ARGN})
+ # Gather all .xml files in TelepathyQt4 and spec/ and make this target depend on those
+ file(GLOB depends_xml_files ${CMAKE_SOURCE_DIR}/TelepathyQt4/*.xml ${CMAKE_SOURCE_DIR}/spec/*.xml)
+
+ add_custom_command(OUTPUT ${_OUTPUT_FILE}
+
+ COMMAND ${PYTHON_EXECUTABLE}
+
+ ARGS ${CMAKE_SOURCE_DIR}/tools/xincludator.py
+ ${_INPUT_FILE}
+ ${xincludator_gen_args}
+ > ${_OUTPUT_FILE}
+
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/xincludator.py
+ ${_INPUT_FILE} ${depends_xml_files})
+ add_custom_target(${_TARGET_NAME} DEPENDS ${_OUTPUT_FILE})
+
+ if (xincludator_gen_depends)
+ add_dependencies(${_TARGET_NAME} ${xincludator_gen_depends})
+ endif (xincludator_gen_depends)
+endfunction(tpqt4_xincludator _TARGET_NAME _INPUT_FILE _OUTPUT_FILE)
+
+function(tpqt4_constants_gen _TARGET_NAME _SPEC_XML _OUTFILE)
+ tpqt4_extract_depends(constants_gen_args constants_gen_depends ${ARGN})
+ # Gather all .xml files in TelepathyQt4 and spec/ and make this target depend on those
+ file(GLOB depends_xml_files ${CMAKE_SOURCE_DIR}/TelepathyQt4/*.xml ${CMAKE_SOURCE_DIR}/spec/*.xml)
+
+ add_custom_command(OUTPUT ${_OUTFILE}
+
+ COMMAND ${PYTHON_EXECUTABLE}
+
+ ARGS ${CMAKE_SOURCE_DIR}/tools/qt4-constants-gen.py
+ ${constants_gen_args}
+ --specxml=${_SPEC_XML}
+ > ${_OUTFILE}
+
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/libqt4codegen.py
+ ${CMAKE_SOURCE_DIR}/tools/qt4-constants-gen.py
+ ${_SPEC_XML} ${depends_xml_files})
+ add_custom_target(${_TARGET_NAME} DEPENDS ${_OUTFILE})
+ add_dependencies(all-generated-sources ${_TARGET_NAME})
+
+ if (constants_gen_depends)
+ add_dependencies(${_TARGET_NAME} ${constants_gen_depends})
+ endif (constants_gen_depends)
+endfunction (tpqt4_constants_gen _TARGET_NAME _SPEC_XML _OUTFILE)
+
+function(tpqt4_types_gen _TARGET_NAME _SPEC_XML _OUTFILE_DECL _OUTFILE_IMPL _NAMESPACE _REALINCLUDE _PRETTYINCLUDE)
+ tpqt4_extract_depends(types_gen_args types_gen_depends ${ARGN})
+ # Gather all .xml files in TelepathyQt4 and spec/ and make this target depend on those
+ file(GLOB depends_xml_files ${CMAKE_SOURCE_DIR}/TelepathyQt4/*.xml ${CMAKE_SOURCE_DIR}/spec/*.xml)
+
+ add_custom_command(OUTPUT ${_OUTFILE_DECL} ${_OUTFILE_IMPL}
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/qt4-types-gen.py
+ --namespace=${_NAMESPACE}
+ --declfile=${_OUTFILE_DECL}
+ --implfile=${_OUTFILE_IMPL}
+ --realinclude=${_REALINCLUDE}
+ --prettyinclude=${_PRETTYINCLUDE}
+ ${types_gen_args}
+ --specxml=${_SPEC_XML}
+
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/libqt4codegen.py
+ ${CMAKE_SOURCE_DIR}/tools/qt4-types-gen.py
+ ${_SPEC_XML} ${depends_xml_files})
+ add_custom_target(${_TARGET_NAME} DEPENDS ${_OUTFILE_IMPL})
+ add_dependencies(all-generated-sources ${_TARGET_NAME})
+
+ if (types_gen_depends)
+ add_dependencies(${_TARGET_NAME} ${types_gen_depends})
+ endif (types_gen_depends)
+endfunction(tpqt4_types_gen _TARGET_NAME _SPEC_XML _OUTFILE_DECL _OUTFILE_IMPL _NAMESPACE _REALINCLUDE _PRETTYINCLUDE)
+
+macro(tpqt4_add_generic_unit_test _fancyName _name)
+ tpqt4_generate_moc_i(${_name}.cpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/${_name}.cpp.moc.hpp)
+ add_executable(test-${_name} ${_name}.cpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/${_name}.cpp.moc.hpp)
+ target_link_libraries(test-${_name} ${QT_QTDBUS_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} telepathy-qt4 tp-qt4-tests ${ARGN})
+ add_test(${_fancyName} ${SH} ${CMAKE_CURRENT_BINARY_DIR}/runGenericTest.sh ${CMAKE_CURRENT_BINARY_DIR}/test-${_name})
+ list(APPEND _telepathy_qt4_test_cases test-${_name})
+
+ # Valgrind and Callgrind targets
+ _tpqt4_add_check_targets(${_fancyName} ${_name} ${CMAKE_CURRENT_BINARY_DIR}/runGenericTest.sh ${CMAKE_CURRENT_BINARY_DIR}/test-${_name})
+endmacro(tpqt4_add_generic_unit_test _fancyName _name)
+
+macro(tpqt4_add_dbus_unit_test _fancyName _name)
+ tpqt4_generate_moc_i(${_name}.cpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/${_name}.cpp.moc.hpp)
+ add_executable(test-${_name} ${_name}.cpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/${_name}.cpp.moc.hpp)
+ target_link_libraries(test-${_name} ${QT_QTDBUS_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} telepathy-qt4 tp-qt4-tests ${ARGN})
+ set(with_session_bus ${CMAKE_CURRENT_BINARY_DIR}/runDbusTest.sh)
+ add_test(${_fancyName} ${SH} ${with_session_bus} ${CMAKE_CURRENT_BINARY_DIR}/test-${_name})
+ list(APPEND _telepathy_qt4_test_cases test-${_name})
+
+ # Valgrind and Callgrind targets
+ _tpqt4_add_check_targets(${_fancyName} ${_name} ${with_session_bus} ${CMAKE_CURRENT_BINARY_DIR}/test-${_name})
+endmacro(tpqt4_add_dbus_unit_test _fancyName _name)
+
+macro(_tpqt4_add_check_targets _fancyName _name _runnerScript)
+ set_tests_properties(${_fancyName}
+ PROPERTIES
+ FAIL_REGULAR_EXPRESSION "^FAIL!")
+
+ # Standard check target
+ add_custom_target(check-${_fancyName} ${SH} ${_runnerScript} ${ARGN})
+ add_dependencies(check-${_fancyName} test-${_name})
+
+ # Lcov target
+ add_dependencies(lcov-check test-${_name})
+
+ # Valgrind target
+ add_custom_target(check-valgrind-${_fancyName})
+ add_dependencies(check-valgrind-${_fancyName} test-${_name})
+
+ add_custom_command(
+ TARGET check-valgrind-${_fancyName}
+ COMMAND G_SLICE=always-malloc ${SH} ${_runnerScript} /usr/bin/valgrind
+ --tool=memcheck
+ --leak-check=full
+ --leak-resolution=high
+ --child-silent-after-fork=yes
+ --num-callers=20
+ --gen-suppressions=all
+ --log-file=${CMAKE_CURRENT_BINARY_DIR}/test-${_fancyName}.memcheck.log
+ --suppressions=${CMAKE_SOURCE_DIR}/tools/tp-qt4-tests.supp
+ --suppressions=${CMAKE_SOURCE_DIR}/tools/telepathy-glib.supp
+ ${ARGN}
+ WORKING_DIRECTORY
+ ${CMAKE_CURRENT_BINARY_DIR}
+ COMMENT "Running valgrind on test \"${_fancyName}\"")
+ add_dependencies(check-valgrind check-valgrind-${_fancyName})
+
+ # Callgrind target
+ add_custom_target(check-callgrind-${_fancyName})
+ add_dependencies(check-callgrind-${_fancyName} test-${_name})
+ add_custom_command(
+ TARGET check-callgrind-${_fancyName}
+ COMMAND ${SH} ${_runnerScript} /usr/bin/valgrind
+ --tool=callgrind
+ --dump-instr=yes
+ --log-file=${CMAKE_CURRENT_BINARY_DIR}/test-${_fancyName}.callgrind.log
+ --callgrind-out-file=${CMAKE_CURRENT_BINARY_DIR}/test-${_fancyName}.callgrind.out
+ ${ARGN}
+ WORKING_DIRECTORY
+ ${CMAKE_CURRENT_BINARY_DIR}
+ COMMENT
+ "Running callgrind on test \"${_fancyName}\"")
+ add_dependencies(check-callgrind check-callgrind-${_fancyName})
+endmacro(_tpqt4_add_check_targets _fancyName _name)
+
+function(tpqt4_setup_dbus_test_environment)
+ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/runDbusTest.sh "
+${test_environment}
+sh ${CMAKE_SOURCE_DIR}/tools/with-session-bus.sh \\
+ --config-file=${CMAKE_BINARY_DIR}/tests/dbus-1/session.conf -- $@
+")
+endfunction(tpqt4_setup_dbus_test_environment)
diff --git a/qt4/cmake_uninstall.cmake.in b/qt4/cmake_uninstall.cmake.in
new file mode 100644
index 000000000..df95fb9d8
--- /dev/null
+++ b/qt4/cmake_uninstall.cmake.in
@@ -0,0 +1,21 @@
+IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+ MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
+ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+
+FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
+STRING(REGEX REPLACE "\n" ";" files "${files}")
+FOREACH(file ${files})
+ MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
+ IF(EXISTS "$ENV{DESTDIR}${file}")
+ EXEC_PROGRAM(
+ "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
+ OUTPUT_VARIABLE rm_out
+ RETURN_VALUE rm_retval
+ )
+ IF(NOT "${rm_retval}" STREQUAL 0)
+ MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
+ ENDIF(NOT "${rm_retval}" STREQUAL 0)
+ ELSE(EXISTS "$ENV{DESTDIR}${file}")
+ MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
+ ENDIF(EXISTS "$ENV{DESTDIR}${file}")
+ENDFOREACH(file)
diff --git a/qt4/config-version.h.in b/qt4/config-version.h.in
new file mode 100644
index 000000000..28446ec55
--- /dev/null
+++ b/qt4/config-version.h.in
@@ -0,0 +1 @@
+#define PACKAGE_VERSION "@PACKAGE_VERSION@"
diff --git a/qt4/config.h.in b/qt4/config.h.in
new file mode 100644
index 000000000..de3d86ee5
--- /dev/null
+++ b/qt4/config.h.in
@@ -0,0 +1 @@
+#define PACKAGE_NAME "@PACKAGE_NAME@"
diff --git a/qt4/doxygen-footer.html b/qt4/doxygen-footer.html
new file mode 100644
index 000000000..87dc2a981
--- /dev/null
+++ b/qt4/doxygen-footer.html
@@ -0,0 +1,7 @@
+<p /><address><hr /><div align="center">
+<table width="100%" cellspacing="0" border="0"><tr class="address">
+<td width="30%">Copyright &copy; 2008-2011 Collabora Ltd. and Nokia Corporation</td>
+<td width="30%" align="right"><div align="right">Telepathy-Qt4 $projectnumber</div></td>
+</tr></table></div></address>
+</body>
+</html>
diff --git a/qt4/doxygen-header.html b/qt4/doxygen-header.html
new file mode 100644
index 000000000..b6480f644
--- /dev/null
+++ b/qt4/doxygen-header.html
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>$title</title>
+ <link href="doxygen.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+<table border="0" cellpadding="0" cellspacing="0" width="100%">
+<tr>
+<td width="1">&nbsp;&nbsp;</td>
+<td class="postheader" valign="center">
+<a href="index.html">
+<font color="#004faf">Home</font></a>&nbsp;&middot;
+<a href="classes.html">
+<font color="#004faf">All Classes</font></a>&nbsp;&middot;
+<a href="namespaces.html">
+<font color="#004faf">All Namespaces</font></a>&nbsp;&middot;
+<a href="modules.html">
+<font color="#004faf">Modules</font></a>&nbsp;&middot;
+<a href="functions.html">
+<font color="#004faf">Functions</font></a>&nbsp;&middot;
+<a href="files.html">
+<font color="#004faf">Files</font></a>
+</td>
+</tr>
+</table>
diff --git a/qt4/doxygen.cfg.in b/qt4/doxygen.cfg.in
new file mode 100644
index 000000000..1b01e9765
--- /dev/null
+++ b/qt4/doxygen.cfg.in
@@ -0,0 +1,1474 @@
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = ${PROJECT_NAME}
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = ${PACKAGE_VERSION}
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = ${abs_top_builddir}/doc
+
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
+# and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The \$name class" \
+ "The \$name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = YES
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH = ${abs_top_srcdir}
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH = $abs_top_srcdir} ${abs_top_builddir}
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = YES
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = YES
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = YES
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = NO
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = NO
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 0
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = NO
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = NO
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page. This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "\$file:\$line: \$text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE = doxygen.log
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = ${abs_top_srcdir}/TelepathyQt4 ${abs_top_builddir}/TelepathyQt4
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.cpp \
+ *.cc \
+ *.cxx \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.dox
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS = */.svn/* \
+ */.git/* \
+ */cmake/* \
+ *.moc.* \
+ */tests/* \
+ *-internal.* \
+ future*
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH = ${abs_top_srcdir}/examples
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = YES
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = ${GENERATE_HTML}
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER = @abs_top_srcdir@/doxygen-header.html
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER = @abs_top_srcdir@/doxygen-footer.html
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET = @abs_top_srcdir@/doxygen.css
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = ${GENERATE_CHM}
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE = ../${PROJECT}.chm
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION = ${HHC_PATH}
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = ${GENERATE_CHI}
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = YES
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to FRAME, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature. Other possible values
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
+# and Class Hiererachy pages using a tree view instead of an ordered list;
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
+# disables this behavior completely. For backwards compatibility with previous
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
+# respectively.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = ${GENERATE_LATEX}
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = ${PAPER_SIZE}
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = YES
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = ${GENERATE_RTF}
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = ${GENERATE_MAN}
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .1
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = ${GENERATE_XML}
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH = ${abs_top_srcdir} ${abs_top_builddir}
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS = *
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+# FIXME: for some reason, doxygen doesn't seem to emit a doxygen= attribute at all
+# in the <a> element if there is no location, in which case there is nothing for
+# installdox to rewrite.
+TAGFILES = ${QT_TAGS_FILE}=/you/forgot/to/run/installdox
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE = ${abs_top_builddir}/doc/html/${PACKAGE_NAME}.tags
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = ${PERL_PATH}
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = NO
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = ${HAVE_DOT}
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = NO
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH = ${DOT_PATH}
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is enabled by default, which results in a transparent
+# background. Warning: Depending on the platform used, enabling this option
+# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
+# become hard to read).
+
+DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+SEARCHENGINE = NO
+
+GENERATE_QHP = ${GENERATE_QHP}
+QHP_NAMESPACE = "org.freedesktop.Telepathy.Qt4"
+QHP_VIRTUAL_FOLDER = "${PROJECT_NAME}-${PACKAGE_VERSION}"
+QCH_FILE = ${abs_top_builddir}/doc/help/telepathy-qt4.qch
+QHG_LOCATION = ${QHELPGENERATOR_EXECUTABLE}
+
+### TelepathyQt4 Settings
+ALIASES = \
+ "intern=\par<b>Internal use only.</b>" \
+ "reimp=\par<b>Reimplemented from superclass.</b>" \
+ "obsolete=@deprecated" \
+ "feature=\xrefitem features \"Feature(s)\" \"Features\"" \
+ "maintainer=\xrefitem maintainers \"Maintainer(s)\" \"Maintainers\"" \
+ "unmaintained=\xrefitem unmaintained \"Unmaintained\" \"Unmaintained\"" \
+ "requirement=\xrefitem requirements \"Requirement(s)\" \"Requirements\"" \
+ "faq=\xrefitem FAQ \"F.A.Q.\" \"F.A.Q.\"" \
+ "authors=\xrefitem authors \"Author(s)\" \"Authors\"" \
+ "maintainers=\xrefitem maintainers \"Maintainer(s)\" \"Maintainers\"" \
+ "glossary=\xrefitem glossary \"TelepathyQt4 Glossary\" \"TelepathyQt4 Glossary\"" \
+ "acronym=\b "\
+ "licenses=\xrefitem licenses \"License(s)\" \"Licenses\"" \
+ "short=@brief "\
+ "FIXME=\xrefitem fixme \"Fixme\" \"Fixme\"" \
+ "bc=\xrefitem bc \"Binary Compatible\" \"Binary Compatible\"" \
+ "telepathy=<a href=\"http://telepathy.freedesktop.org\">Telepathy</a>" \
+ "telepathy_spec=<a href=\"http://telepathy.freedesktop.org/spec/\">Telepathy specification</a>" \
+ "dbus=<a href=\"http://dbus.freedesktop.org\">D-Bus</a>" \
+ "artistic=<a href=\"http://www.opensource.org/licenses/artistic-license.php\">Artistic</a>" \
+ "bsd=<a href=\"http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5\">BSD</a>" \
+ "x11=<a href=\"http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3\">X11</a>" \
+ "gpl=<a href=\"http://www.fsf.org/licensing/licenses/gpl.html#SEC1\">GPL</a>" \
+ "lgpl=<a href=\"http://www.fsf.org/licensing/licenses/lgpl.html#SEC1\">LGPL</a>" \
+ "qpl=<a href=\"http://www.trolltech.com/products/qt/licenses\">QPL</a>"
+
+PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS \
+ TELEPATHY_QT4_EXPORT="" \
+ TELEPATHY_QT4_NO_EXPORT="" \
+ Q_SLOTS="slots" \
+ Q_SIGNALS="signals"
diff --git a/qt4/doxygen.css b/qt4/doxygen.css
new file mode 100644
index 000000000..33ac66df9
--- /dev/null
+++ b/qt4/doxygen.css
@@ -0,0 +1,458 @@
+body, table, div, p, dl {
+ font-family: Lucida Grande, Verdana, Geneva, Arial, sans-serif;
+ font-size: 12px;
+}
+
+/* @group Heading Levels */
+
+h1 {
+ text-align: center;
+ font-size: 150%;
+}
+
+h2 {
+ font-size: 110%;
+ font-weight: bold;
+}
+
+h3 {
+ font-size: 100%;
+}
+
+h3.version {
+ font-size: 100%;
+ text-align: center;
+}
+
+/* @end */
+
+caption {
+ font-weight: bold;
+}
+
+div.title{
+ font-size: 150%;
+ text-align: center;
+ margin: 2px;
+ padding: 2px;
+}
+
+div.qindex, div.navtab{
+ background-color: #e8eef2;
+ border: 1px solid #84b0c7;
+ text-align: center;
+ margin: 2px;
+ padding: 2px;
+}
+
+div.qindex, div.navpath {
+ width: 100%;
+ line-height: 140%;
+}
+
+div.navtab {
+ margin-right: 15px;
+}
+
+/* @group Link Styling */
+
+a {
+ color: #153788;
+ font-weight: normal;
+ text-decoration: none;
+}
+
+.contents a:visited {
+ color: #1b77c5;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a.qindex {
+ font-weight: bold;
+}
+
+a.qindexHL {
+ font-weight: bold;
+ background-color: #6666cc;
+ color: #ffffff;
+ border: 1px double #9295C2;
+}
+
+.contents a.qindexHL:visited {
+ color: #ffffff;
+}
+
+a.el {
+}
+
+a.elRef {
+}
+
+a.code {
+}
+
+a.codeRef {
+}
+
+/* @end */
+
+dl.el {
+ margin-left: -1cm;
+}
+
+.fragment {
+ font-family: monospace, fixed;
+ font-size: 100%;
+}
+
+pre.fragment {
+ border: 1px solid #CCCCCC;
+ background-color: #f5f5f5;
+ padding: 4px 6px;
+ margin: 4px 8px 4px 2px;
+}
+
+div.ah {
+ background-color: black;
+ font-weight: bold;
+ color: #ffffff;
+ margin-bottom: 3px;
+ margin-top: 3px
+}
+
+div.groupHeader {
+ margin-left: 16px;
+ margin-top: 12px;
+ margin-bottom: 6px;
+ font-weight: bold;
+}
+
+div.groupText {
+ margin-left: 16px;
+ font-style: italic;
+}
+
+body {
+ background: white;
+ color: black;
+ margin-right: 20px;
+ margin-left: 20px;
+}
+
+td.indexkey {
+ background-color: #e8eef2;
+ font-weight: bold;
+ border: 1px solid #CCCCCC;
+ margin: 2px 0px 2px 0;
+ padding: 2px 10px;
+}
+
+td.indexvalue {
+ background-color: #e8eef2;
+ border: 1px solid #CCCCCC;
+ padding: 2px 10px;
+ margin: 2px 0px;
+}
+
+tr.memlist {
+ background-color: #f0f0f0;
+}
+
+p.formulaDsp {
+ text-align: center;
+}
+
+img.formulaDsp {
+
+}
+
+img.formulaInl {
+ vertical-align: middle;
+}
+
+/* @group Code Colorization */
+
+span.keyword {
+ color: #008000
+}
+
+span.keywordtype {
+ color: #604020
+}
+
+span.keywordflow {
+ color: #e08000
+}
+
+span.comment {
+ color: #800000
+}
+
+span.preprocessor {
+ color: #806020
+}
+
+span.stringliteral {
+ color: #002080
+}
+
+span.charliteral {
+ color: #008080
+}
+
+span.vhdldigit {
+ color: #ff00ff
+}
+
+span.vhdlchar {
+ color: #000000
+}
+
+span.vhdlkeyword {
+ color: #700070
+}
+
+span.vhdllogic {
+ color: #ff0000
+}
+
+/* @end */
+
+.search {
+ color: #003399;
+ font-weight: bold;
+}
+
+form.search {
+ margin-bottom: 0px;
+ margin-top: 0px;
+}
+
+input.search {
+ font-size: 75%;
+ color: #000080;
+ font-weight: normal;
+ background-color: #e8eef2;
+}
+
+td.tiny {
+ font-size: 75%;
+}
+
+.dirtab {
+ padding: 4px;
+ border-collapse: collapse;
+ border: 1px solid #84b0c7;
+}
+
+th.dirtab {
+ background: #e8eef2;
+ font-weight: bold;
+}
+
+hr {
+ height: 0;
+ border: none;
+ border-top: 1px solid #666;
+}
+
+/* @group Member Descriptions */
+
+.mdescLeft, .mdescRight,
+.memItemLeft, .memItemRight,
+.memTemplItemLeft, .memTemplItemRight, .memTemplParams {
+ background-color: #FAFAFA;
+ border: none;
+ margin: 4px;
+ padding: 1px 0 0 8px;
+}
+
+.mdescLeft, .mdescRight {
+ padding: 0px 8px 4px 8px;
+ color: #555;
+}
+
+.memItemLeft, .memItemRight, .memTemplParams {
+ border-top: 1px solid #ccc;
+}
+
+.memTemplParams {
+ color: #606060;
+}
+
+/* @end */
+
+/* @group Member Details */
+
+/* Styles for detailed member documentation */
+
+.memtemplate {
+ font-size: 80%;
+ color: #606060;
+ font-weight: normal;
+ margin-left: 3px;
+}
+
+.memnav {
+ background-color: #e8eef2;
+ border: 1px solid #84b0c7;
+ text-align: center;
+ margin: 2px;
+ margin-right: 15px;
+ padding: 2px;
+}
+
+.memitem {
+ padding: 0;
+}
+
+.memname {
+ white-space: nowrap;
+ font-weight: bold;
+}
+
+.memproto {
+ padding: 0;
+ background-color: #d5e1e8;
+ font-weight: bold;
+ border: 1px solid #84b0c7;
+}
+
+.memdoc {
+ padding: 2px 5px;
+ border-top-width: 0;
+}
+
+.paramkey {
+ text-align: right;
+}
+
+.paramtype {
+ white-space: nowrap;
+}
+
+.paramname {
+ color: #602020;
+ white-space: nowrap;
+}
+.paramname em {
+ font-style: normal;
+}
+
+/* @end */
+
+/* @group Directory (tree) */
+
+/* for the tree view */
+
+.ftvtree {
+ font-family: sans-serif;
+ margin: 0.5em;
+}
+
+/* these are for tree view when used as main index */
+
+.directory {
+ font-size: 9pt;
+ font-weight: bold;
+}
+
+.directory h3 {
+ margin: 0px;
+ margin-top: 1em;
+ font-size: 11pt;
+}
+
+/*
+The following two styles can be used to replace the root node title
+with an image of your choice. Simply uncomment the next two styles,
+specify the name of your image and be sure to set 'height' to the
+proper pixel height of your image.
+*/
+
+/*
+.directory h3.swap {
+ height: 61px;
+ background-repeat: no-repeat;
+ background-image: url("yourimage.gif");
+}
+.directory h3.swap span {
+ display: none;
+}
+*/
+
+.directory > h3 {
+ margin-top: 0;
+}
+
+.directory p {
+ margin: 0px;
+ white-space: nowrap;
+}
+
+.directory div {
+ display: none;
+ margin: 0px;
+}
+
+.directory img {
+ vertical-align: -30%;
+}
+
+/* these are for tree view when not used as main index */
+
+.directory-alt {
+ font-size: 100%;
+ font-weight: bold;
+}
+
+.directory-alt h3 {
+ margin: 0px;
+ margin-top: 1em;
+ font-size: 11pt;
+}
+
+.directory-alt > h3 {
+ margin-top: 0;
+}
+
+.directory-alt p {
+ margin: 0px;
+ white-space: nowrap;
+}
+
+.directory-alt div {
+ display: none;
+ margin: 0px;
+}
+
+.directory-alt img {
+ vertical-align: -30%;
+}
+
+/* @end */
+
+address {
+ font-style: normal;
+ color: #333;
+}
+
+div.rationale:before {
+ content: "Rationale:";
+ display: block;
+ font-weight: bold;
+ font-size: 0.85em;
+}
+
+div.rationale {
+ font-style: italic;
+ border-left: 0.25em solid #808080;
+ padding-left: 0.5em;
+}
+
+div.rationale p {
+ font-size: 0.85em;
+}
diff --git a/qt4/examples/CMakeLists.txt b/qt4/examples/CMakeLists.txt
new file mode 100644
index 000000000..c409e5a6c
--- /dev/null
+++ b/qt4/examples/CMakeLists.txt
@@ -0,0 +1,11 @@
+# Set the required flags found in TelepathyDefaults
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DEPRECATED_DECLARATIONS_FLAGS}")
+set(LD_FLAGS "${LD_FLAGS} ${DEPRECATED_DECLARATIONS_FLAGS}")
+
+add_subdirectory(accounts)
+add_subdirectory(contact-messenger)
+add_subdirectory(extensions)
+add_subdirectory(file-transfer)
+add_subdirectory(protocols)
+add_subdirectory(roster)
+add_subdirectory(stream-tubes)
diff --git a/qt4/examples/accounts/CMakeLists.txt b/qt4/examples/accounts/CMakeLists.txt
new file mode 100644
index 000000000..eec0df86b
--- /dev/null
+++ b/qt4/examples/accounts/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(accounts_SRCS
+ main.cpp
+ account-item.cpp
+ account-item.h
+ accounts-window.cpp
+ accounts-window.h)
+
+set(accounts_MOC_SRCS
+ account-item.h
+ accounts-window.h)
+
+tpqt4_generate_mocs(${accounts_MOC_SRCS})
+
+add_executable(accounts ${accounts_SRCS} ${accounts_MOC_SRCS})
+target_link_libraries(accounts
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/accounts/account-item.cpp b/qt4/examples/accounts/account-item.cpp
new file mode 100644
index 000000000..0ad213733
--- /dev/null
+++ b/qt4/examples/accounts/account-item.cpp
@@ -0,0 +1,176 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "account-item.h"
+#include "_gen/account-item.moc.hpp"
+
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDebug>
+#include <QComboBox>
+#include <QTableWidget>
+
+AccountItem::AccountItem(Tp::AccountPtr acc, QTableWidget *table, int row, QObject *parent)
+ : QObject(parent),
+ mAcc(acc),
+ mTable(table),
+ mRow(row)
+{
+ init();
+}
+
+AccountItem::~AccountItem()
+{
+}
+
+void AccountItem::setupGui()
+{
+ mTable->setItem(mRow, ColumnValid, new QTableWidgetItem(mAcc->isValid() ?
+ QLatin1String("true") : QLatin1String("false")));
+ mTable->setItem(mRow, ColumnEnabled, new QTableWidgetItem(mAcc->isEnabled() ?
+ QLatin1String("true") : QLatin1String("false")));
+ mTable->setItem(mRow, ColumnConnectionManager, new QTableWidgetItem(mAcc->cmName()));
+ mTable->setItem(mRow, ColumnProtocol, new QTableWidgetItem(mAcc->protocolName()));
+ mTable->setItem(mRow, ColumnDisplayName, new QTableWidgetItem(mAcc->displayName()));
+ mTable->setItem(mRow, ColumnNickname, new QTableWidgetItem(mAcc->nickname()));
+ mTable->setItem(mRow, ColumnConnectsAutomatically, new QTableWidgetItem(mAcc->connectsAutomatically() ?
+ QLatin1String("true") : QLatin1String("false")));
+ mTable->setItem(mRow, ColumnAutomaticPresence, new QTableWidgetItem(mAcc->automaticPresence().status()));
+ mTable->setItem(mRow, ColumnCurrentPresence, new QTableWidgetItem(mAcc->currentPresence().status()));
+ mTable->setItem(mRow, ColumnRequestedPresence, new QTableWidgetItem(mAcc->requestedPresence().status()));
+ mTable->setItem(mRow, ColumnChangingPresence, new QTableWidgetItem(mAcc->isChangingPresence() ?
+ QLatin1String("true") : QLatin1String("false")));
+ mTable->setItem(mRow, ColumnConnectionStatus, new QTableWidgetItem(QString::number(mAcc->connectionStatus())));
+ mTable->setItem(mRow, ColumnConnection, new QTableWidgetItem(
+ mAcc->connection().isNull() ? QLatin1String("") : mAcc->connection()->objectPath()));
+}
+
+void AccountItem::init()
+{
+ setupGui();
+
+ Tp::Account *acc = mAcc.data();
+ connect(acc,
+ SIGNAL(validityChanged(bool)),
+ SLOT(onValidityChanged(bool)));
+ connect(acc,
+ SIGNAL(stateChanged(bool)),
+ SLOT(onStateChanged(bool)));
+ connect(acc,
+ SIGNAL(displayNameChanged(const QString &)),
+ SLOT(onDisplayNameChanged(const QString &)));
+ connect(acc,
+ SIGNAL(nicknameChanged(const QString &)),
+ SLOT(onNicknameChanged(const QString &)));
+ connect(acc,
+ SIGNAL(connectsAutomaticallyPropertyChanged(bool)),
+ SLOT(onConnectsAutomaticallyPropertyChanged(bool)));
+ connect(acc,
+ SIGNAL(changingPresence(bool)),
+ SLOT(onChangingPresenceChanged(bool)));
+ connect(acc,
+ SIGNAL(automaticPresenceChanged(const Tp::SimplePresence &)),
+ SLOT(onAutomaticPresenceChanged(const Tp::SimplePresence &)));
+ connect(acc,
+ SIGNAL(currentPresenceChanged(const Tp::SimplePresence &)),
+ SLOT(onCurrentPresenceChanged(const Tp::SimplePresence &)));
+ connect(acc,
+ SIGNAL(requestedPresenceChanged(const Tp::SimplePresence &)),
+ SLOT(onRequestedPresenceChanged(const Tp::SimplePresence &)));
+ connect(acc,
+ SIGNAL(statusChanged(Tp::ConnectionStatus, Tp::ConnectionStatusReason,
+ const QString &, const QVariantMap &)),
+ SLOT(onStatusChanged(Tp::ConnectionStatus, Tp::ConnectionStatusReason,
+ const QString &, const QVariantMap &)));
+ connect(acc,
+ SIGNAL(haveConnectionChanged(bool)),
+ SLOT(onHaveConnectionChanged(bool)));
+}
+
+void AccountItem::onValidityChanged(bool valid)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnValid);
+ item->setText((valid ? QLatin1String("true") : QLatin1String("false")));
+}
+
+void AccountItem::onStateChanged(bool enabled)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnEnabled);
+ item->setText((enabled ? QLatin1String("true") : QLatin1String("false")));
+}
+
+void AccountItem::onDisplayNameChanged(const QString &name)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnDisplayName);
+ item->setText(name);
+}
+
+void AccountItem::onNicknameChanged(const QString &name)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnNickname);
+ item->setText(name);
+}
+
+void AccountItem::onConnectsAutomaticallyPropertyChanged(bool value)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnConnectsAutomatically);
+ item->setText((value ? QLatin1String("true") : QLatin1String("false")));
+}
+
+void AccountItem::onChangingPresenceChanged(bool value)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnChangingPresence);
+ item->setText((value ? QLatin1String("true") : QLatin1String("false")));
+}
+
+void AccountItem::onAutomaticPresenceChanged(const Tp::SimplePresence &presence)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnAutomaticPresence);
+ item->setText(presence.status);
+}
+
+void AccountItem::onCurrentPresenceChanged(const Tp::SimplePresence &presence)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnCurrentPresence);
+ item->setText(presence.status);
+}
+
+void AccountItem::onRequestedPresenceChanged(const Tp::SimplePresence &presence)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnRequestedPresence);
+ item->setText(presence.status);
+}
+
+void AccountItem::onStatusChanged(Tp::ConnectionStatus status,
+ Tp::ConnectionStatusReason reason, const QString &error,
+ const QVariantMap &errorDetails)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnConnectionStatus);
+ item->setText(QString::number(status));
+}
+
+void AccountItem::onHaveConnectionChanged(bool haveConnection)
+{
+ QTableWidgetItem *item = mTable->item(mRow, ColumnConnection);
+ item->setText(mAcc->connection().isNull() ?
+ QLatin1String("") : mAcc->connection()->objectPath());
+}
diff --git a/qt4/examples/accounts/account-item.h b/qt4/examples/accounts/account-item.h
new file mode 100644
index 000000000..d4584b212
--- /dev/null
+++ b/qt4/examples/accounts/account-item.h
@@ -0,0 +1,89 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_accounts_account_item_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_accounts_account_item_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Types>
+
+#include <QString>
+
+namespace Tp {
+class AccountManager;
+class PendingOperation;
+}
+
+class QTableWidget;
+
+class AccountItem : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum Columns {
+ ColumnValid = 0,
+ ColumnEnabled,
+ ColumnConnectionManager,
+ ColumnProtocol,
+ ColumnDisplayName,
+ ColumnNickname,
+ ColumnConnectsAutomatically,
+ ColumnChangingPresence,
+ ColumnAutomaticPresence,
+ ColumnCurrentPresence,
+ ColumnRequestedPresence,
+ ColumnConnectionStatus,
+ ColumnConnection,
+ NumColumns
+ };
+ Q_ENUMS(Columns)
+
+ AccountItem(Tp::AccountPtr acc, QTableWidget *table, int row, QObject *parent = 0);
+ virtual ~AccountItem();
+
+ int row() const { return mRow; }
+
+private Q_SLOTS:
+ void onValidityChanged(bool);
+ void onStateChanged(bool);
+ void onDisplayNameChanged(const QString &);
+ void onNicknameChanged(const QString &);
+ void onConnectsAutomaticallyPropertyChanged(bool);
+ void onChangingPresenceChanged(bool);
+ void onAutomaticPresenceChanged(const Tp::SimplePresence &);
+ void onCurrentPresenceChanged(const Tp::SimplePresence &);
+ void onRequestedPresenceChanged(const Tp::SimplePresence &);
+ void onStatusChanged(Tp::ConnectionStatus, Tp::ConnectionStatusReason,
+ const QString &error, const QVariantMap &errorDetails);
+ void onHaveConnectionChanged(bool);
+
+private:
+ void init();
+ void setupGui();
+
+ Tp::AccountPtr mAcc;
+ QTableWidget *mTable;
+ int mRow;
+};
+
+#endif
diff --git a/qt4/examples/accounts/accounts-window.cpp b/qt4/examples/accounts/accounts-window.cpp
new file mode 100644
index 000000000..fc79b8344
--- /dev/null
+++ b/qt4/examples/accounts/accounts-window.cpp
@@ -0,0 +1,100 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "accounts-window.h"
+#include "_gen/accounts-window.moc.hpp"
+
+#include "account-item.h"
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+
+#include <QCheckBox>
+#include <QDebug>
+#include <QHBoxLayout>
+#include <QItemEditorCreatorBase>
+#include <QItemEditorFactory>
+#include <QTableWidget>
+
+AccountsWindow::AccountsWindow(QWidget *parent)
+ : QMainWindow(parent)
+{
+ setupGui();
+
+ mAM = Tp::AccountManager::create(Tp::AccountFactory::create(QDBusConnection::sessionBus(),
+ Tp::Account::FeatureCore));
+ connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onAMReady(Tp::PendingOperation *)));
+ connect(mAM.data(),
+ SIGNAL(newAccount(const Tp::AccountPtr &)),
+ SLOT(onNewAccount(const Tp::AccountPtr &)));
+}
+
+AccountsWindow::~AccountsWindow()
+{
+}
+
+void AccountsWindow::setupGui()
+{
+ mTable = new QTableWidget;
+
+ mTable->setColumnCount(AccountItem::NumColumns);
+ QStringList headerLabels;
+ headerLabels <<
+ QLatin1String("Valid") <<
+ QLatin1String("Enabled") <<
+ QLatin1String("Connection Manager") <<
+ QLatin1String("Protocol Name") <<
+ QLatin1String("Display Name") <<
+ QLatin1String("Nickname") <<
+ QLatin1String("Connects Automatically") <<
+ QLatin1String("Changing Presence") <<
+ QLatin1String("Automatic Presence") <<
+ QLatin1String("Current Presence") <<
+ QLatin1String("Requested Presence") <<
+ QLatin1String("Connection Status") <<
+ QLatin1String("Connection");
+ mTable->setHorizontalHeaderLabels(headerLabels);
+
+ setCentralWidget(mTable);
+}
+
+void AccountsWindow::onAMReady(Tp::PendingOperation *op)
+{
+ mTable->setRowCount(mAM->allAccounts().count());
+
+ int row = 0;
+ foreach (const Tp::AccountPtr &acc, mAM->allAccounts()) {
+ (void) new AccountItem(acc, mTable, row++, this);
+ }
+}
+
+void AccountsWindow::onNewAccount(const Tp::AccountPtr &acc)
+{
+ int row = mTable->rowCount();
+ mTable->insertRow(row);
+ (void) new AccountItem(acc, mTable, row, this);
+}
diff --git a/qt4/examples/accounts/accounts-window.h b/qt4/examples/accounts/accounts-window.h
new file mode 100644
index 000000000..28520d170
--- /dev/null
+++ b/qt4/examples/accounts/accounts-window.h
@@ -0,0 +1,55 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_accounts_accounts_window_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_accounts_accounts_window_h_HEADER_GUARD_
+
+#include <QMainWindow>
+
+#include <TelepathyQt4/Types>
+
+namespace Tp {
+class PendingOperation;
+}
+
+class QTableWidget;
+class QTableWidgetItem;
+
+class AccountsWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ AccountsWindow(QWidget *parent = 0);
+ virtual ~AccountsWindow();
+
+private Q_SLOTS:
+ void onAMReady(Tp::PendingOperation *);
+ void onNewAccount(const Tp::AccountPtr &);
+
+private:
+ void setupGui();
+
+ Tp::AccountManagerPtr mAM;
+ QTableWidget *mTable;
+};
+
+#endif
diff --git a/qt4/examples/accounts/main.cpp b/qt4/examples/accounts/main.cpp
new file mode 100644
index 000000000..a21b62c1e
--- /dev/null
+++ b/qt4/examples/accounts/main.cpp
@@ -0,0 +1,21 @@
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#include <QtGui>
+
+#include "accounts-window.h"
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ Tp::registerTypes();
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+
+ AccountsWindow w;
+ w.show();
+
+ return app.exec();
+}
diff --git a/qt4/examples/contact-messenger/CMakeLists.txt b/qt4/examples/contact-messenger/CMakeLists.txt
new file mode 100644
index 000000000..da1d25062
--- /dev/null
+++ b/qt4/examples/contact-messenger/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(messenger-sender_SRCS
+ sender.cpp)
+
+set(messenger-sender_MOC_SRCS
+ sender.h)
+
+tpqt4_generate_mocs(${messenger-sender_MOC_SRCS})
+
+add_executable(messenger-sender ${messenger-sender_SRCS} ${messenger-sender_MOC_SRCS})
+target_link_libraries(messenger-sender
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/contact-messenger/sender.cpp b/qt4/examples/contact-messenger/sender.cpp
new file mode 100644
index 000000000..0964ef0bc
--- /dev/null
+++ b/qt4/examples/contact-messenger/sender.cpp
@@ -0,0 +1,82 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sender.h"
+#include "_gen/sender.moc.hpp"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/ContactMessenger>
+#include <TelepathyQt4/PendingSendMessage>
+#include <TelepathyQt4/Types>
+
+#include <QCoreApplication>
+
+Sender::Sender(const QString &accountPath,
+ const QString &contactIdentifier, const QString &message)
+{
+ Tp::AccountPtr acc = Tp::Account::create(TP_QT4_ACCOUNT_MANAGER_BUS_NAME,
+ accountPath);
+ messenger = Tp::ContactMessenger::create(acc, contactIdentifier);
+ connect(messenger->sendMessage(message),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onSendMessageFinished(Tp::PendingOperation*)));
+}
+
+Sender::~Sender()
+{
+}
+
+void Sender::onSendMessageFinished(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ qDebug() << "Error sending message:" << op->errorName() << "-" << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ Tp::PendingSendMessage *psm = qobject_cast<Tp::PendingSendMessage *>(op);
+ qDebug() << "Message sent, token is" << psm->sentMessageToken();
+ QCoreApplication::exit(0);
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ Tp::registerTypes();
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+
+ if (argc < 4) {
+ qDebug() << "Usage: contact-messenger account_path contact_id message";
+ return -1;
+ }
+
+ Sender *sender = new Sender(QLatin1String(argv[1]), QLatin1String(argv[2]),
+ QLatin1String(argv[3]));
+
+ int ret = app.exec();
+ delete sender;
+ return ret;
+}
diff --git a/qt4/examples/contact-messenger/sender.h b/qt4/examples/contact-messenger/sender.h
new file mode 100644
index 000000000..6e363a253
--- /dev/null
+++ b/qt4/examples/contact-messenger/sender.h
@@ -0,0 +1,51 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2011 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_contact_messenger_sender_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_contact_messenger_sender_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Types>
+
+#include <QObject>
+#include <QString>
+
+namespace Tp
+{
+class PendingOperation;
+}
+
+class Sender : public QObject
+{
+ Q_OBJECT
+
+public:
+ Sender(const QString &accountPath, const QString &contactIdentifier, const QString &message);
+ ~Sender();
+
+private Q_SLOTS:
+ void onSendMessageFinished(Tp::PendingOperation *op);
+
+private:
+ Tp::ContactMessengerPtr messenger;
+};
+
+#endif
diff --git a/qt4/examples/extensions/CMakeLists.txt b/qt4/examples/extensions/CMakeLists.txt
new file mode 100644
index 000000000..2e9ffb2fb
--- /dev/null
+++ b/qt4/examples/extensions/CMakeLists.txt
@@ -0,0 +1,76 @@
+# This directory is an example of how to build extensions to the spec.
+# Typically this would be in a top-level extensions/ directory.
+
+# In this example we build an optional interface for Telepathy Connections,
+# so we specify Tp::Client::ConnectionInterface as the main interface for the
+# generated proxies with "--mainiface=Tp::Client::ConnectionInterface'. The
+# generated proxies will have a convenience constructors for associating the
+# proxy with the same remote object an instance of the main interface class
+# is associated with. We could instead have made an optional interface for any
+# other class, or by leaving that option out entirely we could have made an
+# extension that will work on QDBusAbstractInterface or any subclass of it.
+#
+# For stand-alone interfaces (for which the interface itself should be considered
+# the main interface) --mainiface should be specified as fully namespaced name
+# of the interface class itself.
+
+set(example_extensions_SRCS
+ cli-connection.cpp
+ cli-connection.h
+ types.cpp)
+
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/_gen)
+set(generated_all_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml)
+tpqt4_xincludator(example-extensions-includator ${CMAKE_CURRENT_SOURCE_DIR}/all.xml ${generated_all_xml})
+tpqt4_constants_gen(example-extensions-constants ${generated_all_xml} ${CMAKE_CURRENT_BINARY_DIR}/_gen/constants.h
+ --namespace=Example
+ --str-constant-prefix=EXAMPLE_
+ DEPENDS example-extensions-includator)
+
+if(MSVC)
+ set(TYPES_INCLUDE ^<TelepathyQt4/Types^>)
+else(MSVC)
+ set(TYPES_INCLUDE '<TelepathyQt4/Types>')
+endif(MSVC)
+
+tpqt4_types_gen(example-extensions-typesgen ${generated_all_xml}
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/types-body.hpp
+ Example types.h types.h
+ --extraincludes=${TYPES_INCLUDE}
+ DEPENDS example-extensions-constants)
+
+set(connection_generated_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/connection.xml)
+tpqt4_xincludator(example-extensions-connection-includator ${CMAKE_CURRENT_SOURCE_DIR}/connection.xml ${connection_generated_xml})
+
+if(MSVC)
+ set(ESCAPED_QUOTES \"\"\")
+ set(connection_include ^<TelepathyQt4/Connection^>)
+else(MSVC)
+ set(ESCAPED_QUOTES \\\")
+ set(connection_include '<TelepathyQt4/Connection>')
+endif(MSVC)
+
+add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection-body.hpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.h
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/qt4-client-gen.py
+ --namespace=Example::Client
+ --typesnamespace=Example
+ --headerfile=${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.h
+ --implfile=${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection-body.hpp
+ --realinclude=${CMAKE_CURRENT_SRC_DIR}/cli-connection.h
+ --specxml=${generated_all_xml}
+ --ifacexml=${connection_generated_xml}
+ --extraincludes=${connection_include},${ESCAPED_QUOTES}types.h${ESCAPED_QUOTES}
+ --mainiface=Tp::Client::ConnectionInterface)
+add_custom_target(example-extensions-connection-generation)
+add_dependencies(example-extensions-connection-generation example-extensions-connection-includator example-extensions-typesgen)
+list(APPEND example_extensions_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection-body.hpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.h)
+tpqt4_generate_moc_i(${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.moc.hpp)
+list(APPEND example_extensions_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.moc.hpp)
+
+add_library(example_extensions STATIC ${example_extensions_SRCS})
+add_dependencies(example_extensions example-extensions-connection-generation)
+target_link_libraries(example_extensions
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/extensions/Connection_Interface_Hats.xml b/qt4/examples/extensions/Connection_Interface_Hats.xml
new file mode 100644
index 000000000..56a733d82
--- /dev/null
+++ b/qt4/examples/extensions/Connection_Interface_Hats.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Hats"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2007 Collabora Ltd.</tp:copyright>
+ <tp:copyright> Copyright (C) 2007 Nokia Corporation</tp:copyright>
+ <tp:license>
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved.
+ </tp:license>
+ <interface name="com.example.Telepathy.Connection.Interface.Hats"
+ tp:causes-havoc='a silly example'>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This interface is an example of how Telepathy can be extended.
+ For the purposes of this example, we pretend we're an organisation
+ example.com that's adding a proprietary extension to Telepathy,
+ so the extension is not in the main Telepathy namespace.
+ </tp:docstring>
+
+ <tp:struct name="Contact_Hat" array-name="Contact_Hat_List">
+ <tp:docstring>A data structure representing a contact and their
+ hat.</tp:docstring>
+ <tp:member type="u" name="Contact" tp:type="Contact_Handle">
+ <tp:docstring>The contact wearing the hat.</tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Color">
+ <tp:docstring>The color of the hat</tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Style" tp:type="Hat_Style">
+ <tp:docstring>The style of the hat</tp:docstring>
+ </tp:member>
+ <tp:member type="a{sv}" name="Properties" tp:type="String_Variant_Map">
+ <tp:docstring>Optional key-value pairs describing extended
+ properties of the hat.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:enum name="Hat_Style" type="u">
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>There is no hat. color MUST be the empty
+ string and properties MUST be an empty mapping.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Other" value="1">
+ <tp:docstring>An unspecified type of hat.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Fedora" value="2">
+ <tp:docstring>A fedora, which MAY be red.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Knitted" value="3">
+ <tp:docstring>A knitted hat, with or without a bobble.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Bowler" value="4">
+ <tp:docstring>A bowler hat, as worn by stereotypical English
+ businessmen.</tp:docstring>
+ <tp:enumvalue suffix="Helmet" value="5">
+ <tp:docstring>A hat with protective qualities.</tp:docstring>
+ </tp:enumvalue>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <signal name="HatsChanged">
+ <tp:docstring>
+ Emitted when the contact's hat has changed.
+ </tp:docstring>
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle representing the contact's ID on the server
+ </tp:docstring>
+ </arg>
+ <arg name="Color" type="s">
+ <tp:docstring>
+ The color of the contact's hat.
+ </tp:docstring>
+ </arg>
+ <arg name="Style" type="u" tp:type="Hat_Style">
+ <tp:docstring>
+ The style of the contact's hat.
+ </tp:docstring>
+ </arg>
+ <arg name="Properties" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring>
+ There's always an a{sv}. Perhaps there's some special religious
+ reason.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="GetHats">
+ <tp:docstring>
+ Request a list of the hats worn by the given contacts.
+ </tp:docstring>
+ <arg direction="in" name="Contact" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The handles of the contacts whose hats are requested
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Hats" type="a(usua{sv})"
+ tp:type="Contact_Hat[]">
+ <tp:docstring>
+ A list of contacts and their hats.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="SetHat">
+ <tp:docstring>
+ Indicate that the hat currently being worn by the local user
+ has changed.
+ </tp:docstring>
+ <arg name="Color" type="s" direction="in">
+ <tp:docstring>
+ The color of the contact's hat.
+ </tp:docstring>
+ </arg>
+ <arg name="Style" type="u" tp:type="Hat_Style" direction="in">
+ <tp:docstring>
+ The style of the contact's hat.
+ </tp:docstring>
+ </arg>
+ <arg name="Properties" type="a{sv}" tp:type="String_Variant_Map"
+ direction="in">
+ <tp:docstring>
+ There's always an a{sv}...
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="RequestHatPrice">
+ <tp:docstring>
+ Request the prices of a contact's current hat.
+ </tp:docstring>
+ <arg direction="in" name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the contact whose hat we're interested in purchasing.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Price" type="(ius)" tp:type="Currency_Amount">
+ <tp:docstring>
+ The price of the hat.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This hat is not for sale. Get your own style, you trend-follower!
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/examples/extensions/all.xml b/qt4/examples/extensions/all.xml
new file mode 100644
index 000000000..f47ea3561
--- /dev/null
+++ b/qt4/examples/extensions/all.xml
@@ -0,0 +1,18 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Extensions for 'extended' examples</tp:title>
+
+<xi:include href="connection.xml"/>
+
+<tp:generic-types>
+ <tp:external-type name="Contact_Handle" type="u"
+ from="Telepathy specification"/>
+ <tp:external-type name="String_Variant_Map" type="a{sv}"
+ from="Telepathy specification"/>
+ <tp:external-type name="Currency_Amount" type="(ius)"
+ from="Telepathy specification"/>
+</tp:generic-types>
+
+</tp:spec>
diff --git a/qt4/examples/extensions/cli-connection.cpp b/qt4/examples/extensions/cli-connection.cpp
new file mode 100644
index 000000000..89401602e
--- /dev/null
+++ b/qt4/examples/extensions/cli-connection.cpp
@@ -0,0 +1,2 @@
+#include "cli-connection.h"
+#include "_gen/cli-connection.moc.hpp"
diff --git a/qt4/examples/extensions/cli-connection.h b/qt4/examples/extensions/cli-connection.h
new file mode 100644
index 000000000..fc07d2506
--- /dev/null
+++ b/qt4/examples/extensions/cli-connection.h
@@ -0,0 +1,6 @@
+#ifndef _Example_Client_Connection_HEADER_GUARD_
+#define _Example_Client_Connection_HEADER_GUARD_
+
+#include "_gen/cli-connection.h"
+
+#endif
diff --git a/qt4/examples/extensions/connection.xml b/qt4/examples/extensions/connection.xml
new file mode 100644
index 000000000..242fb1f5f
--- /dev/null
+++ b/qt4/examples/extensions/connection.xml
@@ -0,0 +1,9 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Connection extensions for 'extended' example</tp:title>
+
+<xi:include href="Connection_Interface_Hats.xml"/>
+
+</tp:spec>
diff --git a/qt4/examples/extensions/types.cpp b/qt4/examples/extensions/types.cpp
new file mode 100644
index 000000000..49bc41d00
--- /dev/null
+++ b/qt4/examples/extensions/types.cpp
@@ -0,0 +1 @@
+#include "_gen/types-body.hpp"
diff --git a/qt4/examples/extensions/types.h b/qt4/examples/extensions/types.h
new file mode 100644
index 000000000..fb4ad9cc3
--- /dev/null
+++ b/qt4/examples/extensions/types.h
@@ -0,0 +1,6 @@
+#ifndef _Example_Types_HEADER_GUARD_
+#define _Example_Types_HEADER_GUARD_
+
+#include "_gen/types.h"
+
+#endif
diff --git a/qt4/examples/file-transfer/CMakeLists.txt b/qt4/examples/file-transfer/CMakeLists.txt
new file mode 100644
index 000000000..21d3edb86
--- /dev/null
+++ b/qt4/examples/file-transfer/CMakeLists.txt
@@ -0,0 +1,39 @@
+set(ft-receiver_SRCS
+ file-receiver.cpp
+ file-receiver-handler.cpp
+ pending-file-receive.cpp
+ pending-file-transfer.cpp)
+
+set(ft-receiver_MOC_SRCS
+ file-receiver.h
+ file-receiver-handler.h
+ pending-file-receive.h
+ pending-file-transfer.h)
+
+tpqt4_generate_mocs(${ft-receiver_MOC_SRCS})
+
+add_executable(ft-receiver ${ft-receiver_SRCS} ${ft-receiver_MOC_SRCS})
+target_link_libraries(ft-receiver
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
+
+set(ft-sender_SRCS
+ file-sender.cpp
+ pending-file-send.cpp
+ pending-file-transfer.cpp)
+
+set(ft-sender_MOC_SRCS
+ file-sender.h
+ pending-file-send.h
+ pending-file-transfer.h)
+
+tpqt4_generate_mocs(${ft-sender_MOC_SRCS})
+
+add_executable(ft-sender ${ft-sender_SRCS} ${ft-sender_MOC_SRCS})
+target_link_libraries(ft-sender
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/file-transfer/file-receiver-handler.cpp b/qt4/examples/file-transfer/file-receiver-handler.cpp
new file mode 100644
index 000000000..15e5ca937
--- /dev/null
+++ b/qt4/examples/file-transfer/file-receiver-handler.cpp
@@ -0,0 +1,96 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "file-receiver-handler.h"
+#include "_gen/file-receiver-handler.moc.hpp"
+
+#include "pending-file-receive.h"
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/MethodInvocationContext>
+#include <TelepathyQt4/IncomingFileTransferChannel>
+
+#include <QDateTime>
+#include <QDebug>
+
+FileReceiverHandler::FileReceiverHandler()
+ : QObject(),
+ AbstractClientHandler(ChannelClassSpecList() << ChannelClassSpec::incomingFileTransfer(),
+ AbstractClientHandler::Capabilities(), false)
+{
+}
+
+FileReceiverHandler::~FileReceiverHandler()
+{
+}
+
+bool FileReceiverHandler::bypassApproval() const
+{
+ return false;
+}
+
+void FileReceiverHandler::handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo)
+{
+ // We should always receive one channel to handle,
+ // otherwise either MC or tp-qt4 itself is bogus, so let's assert in case they are
+ Q_ASSERT(channels.size() == 1);
+ ChannelPtr chan = channels.first();
+
+ if (!chan->isValid()) {
+ qWarning() << "Channel received to handle is invalid, ignoring channel";
+ context->setFinishedWithError(TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Channel received to handle is invalid"));
+ return;
+ }
+
+ // We should always receive incoming channels of type FileTransfer, as set by our filter,
+ // otherwise either MC or tp-qt4 itself is bogus, so let's assert in case they are
+ Q_ASSERT(chan->channelType() == TP_QT4_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+ Q_ASSERT(!chan->isRequested());
+
+ IncomingFileTransferChannelPtr transferChannel = IncomingFileTransferChannelPtr::qObjectCast(chan);
+ Q_ASSERT(transferChannel);
+
+ context->setFinished();
+
+ PendingFileReceive *receiveOperation = new PendingFileReceive(transferChannel,
+ SharedPtr<RefCounted>::dynamicCast(AbstractClientPtr(this)));
+ connect(receiveOperation,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onReceiveFinished(Tp::PendingOperation*)));
+}
+
+void FileReceiverHandler::onReceiveFinished(PendingOperation *op)
+{
+ PendingFileReceive *receiveOperation = qobject_cast<PendingFileReceive*>(op);
+ qDebug() << "Closing channel";
+ receiveOperation->channel()->requestClose();
+}
diff --git a/qt4/examples/file-transfer/file-receiver-handler.h b/qt4/examples/file-transfer/file-receiver-handler.h
new file mode 100644
index 000000000..d34d5610b
--- /dev/null
+++ b/qt4/examples/file-transfer/file-receiver-handler.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_file_transfer_file_receiver_handler_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_file_transfer_file_receiver_handler_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/Types>
+
+using namespace Tp;
+
+class FileReceiverHandler : public QObject, public AbstractClientHandler
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FileReceiverHandler)
+
+public:
+ static SharedPtr<FileReceiverHandler> create()
+ {
+ return SharedPtr<FileReceiverHandler>(new FileReceiverHandler());
+ }
+
+ ~FileReceiverHandler();
+
+ bool bypassApproval() const;
+
+ void handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const HandlerInfo &handlerInfo);
+
+private Q_SLOTS:
+ void onReceiveFinished(Tp::PendingOperation *op);
+
+private:
+ FileReceiverHandler();
+
+ QSet<PendingOperation*> mReceiveOps;
+};
+
+#endif
diff --git a/qt4/examples/file-transfer/file-receiver.cpp b/qt4/examples/file-transfer/file-receiver.cpp
new file mode 100644
index 000000000..97c9d412f
--- /dev/null
+++ b/qt4/examples/file-transfer/file-receiver.cpp
@@ -0,0 +1,78 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "file-receiver.h"
+
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/IncomingFileTransferChannel>
+
+#include <QDebug>
+
+FileReceiver::FileReceiver(QObject *parent)
+ : QObject(parent)
+{
+ QDBusConnection bus(QDBusConnection::sessionBus());
+
+ AccountFactoryPtr accountFactory = AccountFactory::create(bus);
+ ConnectionFactoryPtr connectionFactory = ConnectionFactory::create(bus);
+ ChannelFactoryPtr channelFactory = ChannelFactory::create(bus);
+ channelFactory->addCommonFeatures(Channel::FeatureCore);
+ channelFactory->addFeaturesForIncomingFileTransfers(IncomingFileTransferChannel::FeatureCore);
+
+ mCR = ClientRegistrar::create(accountFactory, connectionFactory,
+ channelFactory);
+
+ qDebug() << "Registering incoming file transfer handler";
+ mHandler = FileReceiverHandler::create();
+ QString handlerName(QLatin1String("TpQt4ExampleFileReceiverHandler"));
+ if (!mCR->registerClient(AbstractClientPtr::dynamicCast(mHandler), handlerName)) {
+ qWarning() << "Unable to register incoming file transfer handler, aborting";
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ qDebug() << "Awaiting file transfers";
+}
+
+FileReceiver::~FileReceiver()
+{
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ Tp::registerTypes();
+ Tp::enableDebug(false);
+ Tp::enableWarnings(true);
+
+ new FileReceiver(&app);
+
+ return app.exec();
+}
+
+#include "_gen/file-receiver.moc.hpp"
diff --git a/qt4/examples/file-transfer/file-receiver.h b/qt4/examples/file-transfer/file-receiver.h
new file mode 100644
index 000000000..e1b567b1a
--- /dev/null
+++ b/qt4/examples/file-transfer/file-receiver.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_file_transfer_file_receiver_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_file_transfer_file_receiver_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#include "file-receiver-handler.h"
+
+using namespace Tp;
+
+class FileReceiver : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FileReceiver)
+
+public:
+ FileReceiver(QObject *parent);
+ ~FileReceiver();
+
+private:
+ ClientRegistrarPtr mCR;
+ SharedPtr<FileReceiverHandler> mHandler;
+};
+
+#endif
diff --git a/qt4/examples/file-transfer/file-sender.cpp b/qt4/examples/file-transfer/file-sender.cpp
new file mode 100644
index 000000000..888a723de
--- /dev/null
+++ b/qt4/examples/file-transfer/file-sender.cpp
@@ -0,0 +1,273 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "file-sender.h"
+
+#include "pending-file-send.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactCapabilities>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingChannelRequest>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDebug>
+
+FileSender::FileSender(const QString &accountName, const QString &receiver,
+ const QString &filePath, QObject *parent)
+ : QObject(parent),
+ mAccountName(accountName),
+ mReceiver(receiver),
+ mFilePath(filePath),
+ mTransferRequested(false)
+{
+ qDebug() << "Retrieving account from AccountManager";
+
+ QDBusConnection bus(QDBusConnection::sessionBus());
+
+ // Let's not prepare any account feature as we only care about one account, thus no need to
+ // prepare features for all accounts
+ AccountFactoryPtr accountFactory = AccountFactory::create(bus);
+ // We only care about CONNECTED connections, so let's specify that in a Connection Factory
+ ConnectionFactoryPtr connectionFactory = ConnectionFactory::create(bus,
+ Connection::FeatureCore | Connection::FeatureConnected);
+ ChannelFactoryPtr channelFactory = ChannelFactory::create(bus);
+ channelFactory->addCommonFeatures(Channel::FeatureCore);
+ channelFactory->addFeaturesForOutgoingFileTransfers(OutgoingFileTransferChannel::FeatureCore);
+ ContactFactoryPtr contactFactory = ContactFactory::create();
+
+ mAM = AccountManager::create(bus, accountFactory, connectionFactory,
+ channelFactory, contactFactory);
+ connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAMReady(Tp::PendingOperation*)));
+}
+
+FileSender::~FileSender()
+{
+}
+
+void FileSender::onAMReady(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "AccountManager cannot become ready -" <<
+ op->errorName() << '-' << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ Q_ASSERT(pr != NULL);
+ qDebug() << "AccountManager ready";
+
+ mAccount = mAM->accountForPath(
+ TP_QT4_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + mAccountName);
+ if (!mAccount) {
+ qWarning() << "The account given does not exist";
+ QCoreApplication::exit(1);
+ }
+ Q_ASSERT(!mAccount->isReady());
+ connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onAccountReady(Tp::PendingOperation*)));
+}
+
+void FileSender::onAccountReady(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Account cannot become ready -" <<
+ op->errorName() << '-' << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ PendingReady *pr = qobject_cast<PendingReady*>(op);
+ Q_ASSERT(pr != NULL);
+ qDebug() << "Account ready";
+
+ qDebug() << "Checking if account is online...";
+ connect(mAccount.data(),
+ SIGNAL(connectionChanged(Tp::ConnectionPtr)),
+ SLOT(onAccountConnectionChanged(Tp::ConnectionPtr)));
+ onAccountConnectionChanged(mAccount->connection());
+}
+
+void FileSender::onAccountConnectionChanged(const ConnectionPtr &conn)
+{
+ if (!conn) {
+ qDebug() << "The account given has no connection. "
+ "Please set it online to continue";
+ return;
+ }
+
+ Q_ASSERT(conn->isValid());
+ Q_ASSERT(conn->status() == ConnectionStatusConnected);
+
+ qDebug() << "Account online, got a connected connection!";
+ mConnection = conn;
+
+ qDebug() << "Creating contact object for receiver" << mReceiver;
+ connect(mConnection->contactManager()->contactsForIdentifiers(QStringList() << mReceiver,
+ Contact::FeatureCapabilities),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onContactRetrieved(Tp::PendingOperation *)));
+}
+
+void FileSender::onContactRetrieved(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Unable to create contact object for receiver" <<
+ mReceiver << "-" << op->errorName() << "-" << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ PendingContacts *pc = qobject_cast<PendingContacts*>(op);
+ Q_ASSERT(pc->contacts().size() == 1);
+ mContact = pc->contacts().first();
+
+ qDebug() << "Checking contact capabilities...";
+ connect(mContact.data(),
+ SIGNAL(capabilitiesChanged(Tp::ContactCapabilities)),
+ SLOT(onContactCapabilitiesChanged()));
+
+#if 0
+ qDebug() << "Contact capabilities:";
+ Q_FOREACH (const RequestableChannelClassSpec &spec, mContact->capabilities().allClassSpecs()) {
+ qDebug() << " fixed:" << spec.fixedProperties();
+ qDebug() << " allowed:" << spec.allowedProperties();
+ }
+#endif
+
+ if (mContact->capabilities().fileTransfers()) {
+ onContactCapabilitiesChanged();
+ } else {
+ qDebug() << "The receiver needs to be online and support file transfers to continue";
+ }
+}
+
+void FileSender::onContactCapabilitiesChanged()
+{
+ if (mTransferRequested) {
+ return;
+ }
+
+ if (mContact->capabilities().fileTransfers()) {
+ qDebug() << "The remote contact is capable of receiving file transfers. "
+ "Requesting file transfer channel";
+
+ mTransferRequested = true;
+ FileTransferChannelCreationProperties ftProps(mFilePath,
+ QLatin1String("application/octet-stream"));
+ connect(mAccount->createAndHandleFileTransfer(mContact, ftProps),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onTransferRequestFinished(Tp::PendingOperation*)));
+ }
+}
+
+void FileSender::onTransferRequestFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Unable to request stream tube channel -" <<
+ op->errorName() << ": " << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ qDebug() << "File transfer channel request finished successfully!";
+
+ PendingChannel *pc = qobject_cast<PendingChannel*>(op);
+ Q_ASSERT(pc);
+
+ ChannelPtr chan = pc->channel();
+ if (!chan->isValid()) {
+ qWarning() << "Channel received to handle is invalid, aborting file transfer";
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ // We should always receive outgoing channels of type FileTransfer, as requested,
+ // otherwise either MC or tp-qt4 itself is bogus, so let's assert in case they are
+ Q_ASSERT(chan->channelType() == TP_QT4_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+ Q_ASSERT(chan->isRequested());
+
+ OutgoingFileTransferChannelPtr transferChannel = OutgoingFileTransferChannelPtr::qObjectCast(chan);
+ Q_ASSERT(transferChannel);
+
+ // We just passed the URI when requesting the channel, so it has to be set
+ Q_ASSERT(!transferChannel->uri().isEmpty());
+
+ PendingFileSend *sendOperation = new PendingFileSend(transferChannel, SharedPtr<RefCounted>());
+ connect(sendOperation,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onSendFinished(Tp::PendingOperation*)));
+}
+
+void FileSender::onSendFinished(PendingOperation *op)
+{
+ PendingFileSend *sendOperation = qobject_cast<PendingFileSend*>(op);
+ qDebug() << "Closing channel";
+ sendOperation->channel()->requestClose();
+
+ QCoreApplication::exit(0);
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ if (argc != 4) {
+ qDebug() << "usage:" << argv[0] << "<account name, as in mc-tool list> <receiver contact ID> <file>";
+ return 1;
+ }
+
+ QString filePath = QLatin1String(argv[3]);
+ QFileInfo fileInfo(filePath);
+ if (!fileInfo.exists()) {
+ qWarning() << "File" << filePath << "does not exist";
+ return 1;
+ }
+
+ Tp::registerTypes();
+ Tp::enableDebug(false);
+ Tp::enableWarnings(true);
+
+ new FileSender(QLatin1String(argv[1]), QLatin1String(argv[2]), filePath, &app);
+
+ return app.exec();
+}
+
+#include "_gen/file-sender.moc.hpp"
diff --git a/qt4/examples/file-transfer/file-sender.h b/qt4/examples/file-transfer/file-sender.h
new file mode 100644
index 000000000..78bf74471
--- /dev/null
+++ b/qt4/examples/file-transfer/file-sender.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_file_transfer_file_sender_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_file_transfer_file_sender_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+using namespace Tp;
+
+namespace Tp
+{
+class PendingOperation;
+}
+
+class FileSender : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(FileSender)
+
+public:
+ FileSender(const QString &accountName, const QString &receiverID,
+ const QString &filePath, QObject *parent);
+ ~FileSender();
+
+private Q_SLOTS:
+ void onAMReady(Tp::PendingOperation *op);
+ void onAccountReady(Tp::PendingOperation *op);
+ void onAccountConnectionChanged(const Tp::ConnectionPtr &conn);
+ void onContactRetrieved(Tp::PendingOperation *op);
+ void onContactCapabilitiesChanged();
+ void onTransferRequestFinished(Tp::PendingOperation *op);
+ void onSendFinished(Tp::PendingOperation *op);
+
+private:
+ QString mAccountName;
+ QString mReceiver;
+ QString mFilePath;
+ bool mTransferRequested;
+
+ AccountManagerPtr mAM;
+ AccountPtr mAccount;
+ ConnectionPtr mConnection;
+ ContactPtr mContact;
+};
+
+#endif
diff --git a/qt4/examples/file-transfer/pending-file-receive.cpp b/qt4/examples/file-transfer/pending-file-receive.cpp
new file mode 100644
index 000000000..75e047703
--- /dev/null
+++ b/qt4/examples/file-transfer/pending-file-receive.cpp
@@ -0,0 +1,71 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pending-file-receive.h"
+#include "_gen/pending-file-receive.moc.hpp"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/IncomingFileTransferChannel>
+
+#include <QDebug>
+#include <QFile>
+
+PendingFileReceive::PendingFileReceive(const IncomingFileTransferChannelPtr &chan,
+ const SharedPtr<RefCounted> &object)
+ : PendingFileTransfer(FileTransferChannelPtr::qObjectCast(chan), object),
+ mReceivingFile(false)
+{
+ // connect/call onTransferStateChanged here as now we are constructed, otherwise doing it in the base
+ // class would only invoke the base class slot
+ connect(chan.data(),
+ SIGNAL(stateChanged(Tp::FileTransferState,Tp::FileTransferStateChangeReason)),
+ SLOT(onTransferStateChanged(Tp::FileTransferState,Tp::FileTransferStateChangeReason)));
+ onTransferStateChanged(chan->state(), chan->stateReason());
+}
+
+PendingFileReceive::~PendingFileReceive()
+{
+ mFile.close();
+}
+
+void PendingFileReceive::onTransferStateChanged(FileTransferState state,
+ FileTransferStateChangeReason stateReason)
+{
+ PendingFileTransfer::onTransferStateChanged(state, stateReason);
+
+ if (state == FileTransferStatePending) {
+ Q_ASSERT(!mReceivingFile);
+ mReceivingFile = true;
+
+ IncomingFileTransferChannelPtr chan =
+ IncomingFileTransferChannelPtr::qObjectCast(channel());
+ Q_ASSERT(chan);
+
+ QString fileName(QLatin1String("TpQt4ExampleFTReceiver_") + chan->fileName());
+ fileName.replace(QLatin1String("/"), QLatin1String("_"));
+ mFile.setFileName(fileName);
+
+ qDebug() << "Receiving" << chan->fileName() << "from" <<
+ chan->targetId() << ", saving as" << fileName;
+ chan->acceptFile(0, &mFile);
+ }
+}
diff --git a/qt4/examples/file-transfer/pending-file-receive.h b/qt4/examples/file-transfer/pending-file-receive.h
new file mode 100644
index 000000000..198e8d2f6
--- /dev/null
+++ b/qt4/examples/file-transfer/pending-file-receive.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_file_transfer_pending_file_receive_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_file_transfer_pending_file_receive_h_HEADER_GUARD_
+
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/IncomingFileTransferChannel>
+#include <TelepathyQt4/Types>
+
+#include "pending-file-transfer.h"
+
+using namespace Tp;
+
+class PendingFileReceive : public PendingFileTransfer
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingFileReceive)
+
+public:
+ PendingFileReceive(const IncomingFileTransferChannelPtr &chan, const SharedPtr<RefCounted> &object);
+ ~PendingFileReceive();
+
+private Q_SLOTS:
+ void onTransferStateChanged(Tp::FileTransferState state,
+ Tp::FileTransferStateChangeReason stateReason);
+
+private:
+ bool mReceivingFile;
+ QFile mFile;
+};
+
+#endif
diff --git a/qt4/examples/file-transfer/pending-file-send.cpp b/qt4/examples/file-transfer/pending-file-send.cpp
new file mode 100644
index 000000000..36b1b59d4
--- /dev/null
+++ b/qt4/examples/file-transfer/pending-file-send.cpp
@@ -0,0 +1,76 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pending-file-send.h"
+#include "_gen/pending-file-send.moc.hpp"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+
+#include <QDebug>
+#include <QFile>
+#include <QUrl>
+
+PendingFileSend::PendingFileSend(const OutgoingFileTransferChannelPtr &chan,
+ const SharedPtr<RefCounted> &object)
+ : PendingFileTransfer(FileTransferChannelPtr::qObjectCast(chan), object),
+ mSendingFile(false)
+{
+ // connect/call onTransferStateChanged here as now we are constructed, otherwise doing it in the base
+ // class would only invoke the base class slot
+ connect(chan.data(),
+ SIGNAL(stateChanged(Tp::FileTransferState,Tp::FileTransferStateChangeReason)),
+ SLOT(onTransferStateChanged(Tp::FileTransferState,Tp::FileTransferStateChangeReason)));
+ onTransferStateChanged(chan->state(), chan->stateReason());
+}
+
+PendingFileSend::~PendingFileSend()
+{
+ mFile.close();
+}
+
+void PendingFileSend::onTransferStateChanged(FileTransferState state,
+ FileTransferStateChangeReason stateReason)
+{
+ PendingFileTransfer::onTransferStateChanged(state, stateReason);
+
+ if (state == FileTransferStateAccepted) {
+ Q_ASSERT(!mSendingFile);
+ mSendingFile = true;
+
+ OutgoingFileTransferChannelPtr chan =
+ OutgoingFileTransferChannelPtr::qObjectCast(channel());
+ Q_ASSERT(chan);
+
+ QString uri = chan->uri();
+ mFile.setFileName(QUrl(uri).toLocalFile());
+ if (!mFile.open(QIODevice::ReadOnly)) {
+ qWarning() << "Unable to open" << uri << "for reading, aborting transfer";
+ setFinishedWithError(TP_QT4_ERROR_INVALID_ARGUMENT,
+ QLatin1String("Unable to open file for reading"));
+ return;
+ }
+
+ qDebug() << "Sending" << uri << "to" << chan->targetId();
+ chan->provideFile(&mFile);
+ }
+}
diff --git a/qt4/examples/file-transfer/pending-file-send.h b/qt4/examples/file-transfer/pending-file-send.h
new file mode 100644
index 000000000..a5dc1224a
--- /dev/null
+++ b/qt4/examples/file-transfer/pending-file-send.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_file_transfer_pending_file_send_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_file_transfer_pending_file_send_h_HEADER_GUARD_
+
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+#include <TelepathyQt4/Types>
+
+#include "pending-file-transfer.h"
+
+using namespace Tp;
+
+class PendingFileSend : public PendingFileTransfer
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingFileSend)
+
+public:
+ PendingFileSend(const OutgoingFileTransferChannelPtr &chan, const SharedPtr<RefCounted> &object);
+ ~PendingFileSend();
+
+private Q_SLOTS:
+ void onTransferStateChanged(Tp::FileTransferState state,
+ Tp::FileTransferStateChangeReason stateReason);
+
+private:
+ bool mSendingFile;
+ QFile mFile;
+};
+
+#endif
diff --git a/qt4/examples/file-transfer/pending-file-transfer.cpp b/qt4/examples/file-transfer/pending-file-transfer.cpp
new file mode 100644
index 000000000..a8a9ec529
--- /dev/null
+++ b/qt4/examples/file-transfer/pending-file-transfer.cpp
@@ -0,0 +1,93 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pending-file-transfer.h"
+#include "_gen/pending-file-transfer.moc.hpp"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+
+#include <QDebug>
+#include <QFile>
+#include <QUrl>
+
+PendingFileTransfer::PendingFileTransfer(const FileTransferChannelPtr &chan,
+ const SharedPtr<RefCounted> &object)
+ : PendingOperation(object),
+ mChannel(chan)
+{
+ connect(chan.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(onChannelInvalidated(Tp::DBusProxy*,QString,QString)));
+ connect(chan.data(),
+ SIGNAL(transferredBytesChanged(qulonglong)),
+ SLOT(onTransferredBytesChanged(qulonglong)));
+}
+
+PendingFileTransfer::~PendingFileTransfer()
+{
+}
+
+void PendingFileTransfer::onChannelInvalidated(DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ Q_UNUSED(proxy);
+
+ qWarning() << "Error sending file, channel invalidated -" <<
+ errorName << "-" << errorMessage;
+ setFinishedWithError(errorName, errorMessage);
+}
+
+void PendingFileTransfer::onTransferStateChanged(FileTransferState state,
+ FileTransferStateChangeReason stateReason)
+{
+ qDebug() << "File transfer channel state changed to" << state <<
+ "with reason" << stateReason;
+ switch (state) {
+ case FileTransferStatePending:
+ case FileTransferStateOpen:
+ break;
+
+ case FileTransferStateAccepted:
+ qDebug() << "Transfer accepted!";
+ break;
+
+ case FileTransferStateCompleted:
+ qDebug() << "Transfer completed!";
+ setFinished();
+ break;
+
+ case FileTransferStateCancelled:
+ qDebug() << "Transfer cancelled";
+ setFinished();
+ return;
+
+ default:
+ Q_ASSERT(false);
+ }
+}
+
+void PendingFileTransfer::onTransferredBytesChanged(qulonglong count)
+{
+ qDebug().nospace() << "Transferred bytes " << count << " - " <<
+ ((int) (((double) count / mChannel->size()) * 100)) << "% done";
+}
diff --git a/qt4/examples/file-transfer/pending-file-transfer.h b/qt4/examples/file-transfer/pending-file-transfer.h
new file mode 100644
index 000000000..34393223f
--- /dev/null
+++ b/qt4/examples/file-transfer/pending-file-transfer.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_file_transfer_pending_file_transfer_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_file_transfer_pending_file_transfer_h_HEADER_GUARD_
+
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+#include <TelepathyQt4/Types>
+
+using namespace Tp;
+
+class PendingFileTransfer : public PendingOperation
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(PendingFileTransfer)
+
+public:
+ PendingFileTransfer(const FileTransferChannelPtr &chan,
+ const SharedPtr<RefCounted> &object);
+ ~PendingFileTransfer();
+
+ FileTransferChannelPtr channel() const { return mChannel; }
+
+protected Q_SLOTS:
+ void onTransferStateChanged(Tp::FileTransferState state,
+ Tp::FileTransferStateChangeReason stateReason);
+
+private Q_SLOTS:
+ void onChannelInvalidated(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage);
+ void onTransferredBytesChanged(qulonglong count);
+
+private:
+ FileTransferChannelPtr mChannel;
+};
+
+#endif
diff --git a/qt4/examples/protocols/CMakeLists.txt b/qt4/examples/protocols/CMakeLists.txt
new file mode 100644
index 000000000..a3c348ad9
--- /dev/null
+++ b/qt4/examples/protocols/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(protocols_SRCS
+ main.cpp
+ cm-wrapper.cpp
+ protocols.cpp)
+
+set(protocols_MOC_SRCS
+ cm-wrapper.h
+ protocols.h)
+
+tpqt4_generate_mocs(${protocols_MOC_SRCS})
+
+add_executable(protocols ${protocols_SRCS} ${protocols_MOC_SRCS})
+target_link_libraries(protocols
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/protocols/cm-wrapper.cpp b/qt4/examples/protocols/cm-wrapper.cpp
new file mode 100644
index 000000000..b8122b53a
--- /dev/null
+++ b/qt4/examples/protocols/cm-wrapper.cpp
@@ -0,0 +1,65 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "cm-wrapper.h"
+#include "_gen/cm-wrapper.moc.hpp"
+
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDebug>
+
+CMWrapper::CMWrapper(const QString &cmName, QObject *parent)
+ : QObject(parent),
+ mCM(ConnectionManager::create(cmName))
+{
+ connect(mCM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onCMReady(Tp::PendingOperation *)));
+}
+
+CMWrapper::~CMWrapper()
+{
+}
+
+ConnectionManagerPtr CMWrapper::cm() const
+{
+ return mCM;
+}
+
+void CMWrapper::onCMReady(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "CM" << mCM->name() << "cannot become ready -" <<
+ op->errorName() << ": " << op->errorMessage();
+ return;
+ }
+
+ qDebug() << "CM" << mCM->name() << "ready!";
+ qDebug() << "Supported protocols:";
+ foreach (const QString &protocol, mCM->supportedProtocols()) {
+ qDebug() << "\t" << protocol;
+ }
+
+ emit finished();
+}
diff --git a/qt4/examples/protocols/cm-wrapper.h b/qt4/examples/protocols/cm-wrapper.h
new file mode 100644
index 000000000..a95778fed
--- /dev/null
+++ b/qt4/examples/protocols/cm-wrapper.h
@@ -0,0 +1,59 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_protocols_cm_wrapper_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_protocols_cm_wrapper_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Types>
+
+#include <QObject>
+#include <QString>
+
+using namespace Tp;
+
+namespace Tp
+{
+class ConnectionManager;
+class PendingOperation;
+}
+
+class CMWrapper : public QObject
+{
+ Q_OBJECT
+
+public:
+ CMWrapper(const QString &cmName, QObject *parent = 0);
+ ~CMWrapper();
+
+ ConnectionManagerPtr cm() const;
+
+Q_SIGNALS:
+ void finished();
+
+private Q_SLOTS:
+ void onCMReady(Tp::PendingOperation *op);
+
+private:
+ ConnectionManagerPtr mCM;
+};
+
+#endif
diff --git a/qt4/examples/protocols/main.cpp b/qt4/examples/protocols/main.cpp
new file mode 100644
index 000000000..eed72c236
--- /dev/null
+++ b/qt4/examples/protocols/main.cpp
@@ -0,0 +1,21 @@
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Types>
+
+#include <QDebug>
+#include <QtCore>
+
+#include "protocols.h"
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ Tp::registerTypes();
+ Tp::enableDebug(false);
+ Tp::enableWarnings(false);
+
+ Protocols protocols;
+
+ return app.exec();
+}
diff --git a/qt4/examples/protocols/protocols.cpp b/qt4/examples/protocols/protocols.cpp
new file mode 100644
index 000000000..1ccffe224
--- /dev/null
+++ b/qt4/examples/protocols/protocols.cpp
@@ -0,0 +1,73 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "protocols.h"
+#include "_gen/protocols.moc.hpp"
+
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/PendingStringList>
+
+#include <QCoreApplication>
+#include <QDebug>
+
+Protocols::Protocols()
+ : cmWrappersFinished(0)
+{
+ qDebug() << "Listing names";
+ connect(ConnectionManager::listNames(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onListNamesFinished(Tp::PendingOperation *)));
+}
+
+Protocols::~Protocols()
+{
+}
+
+void Protocols::onListNamesFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Error listing connection manager names -" <<
+ op->errorName() << ": " << op->errorMessage();
+ return;
+ }
+
+ PendingStringList *ps = qobject_cast<PendingStringList *>(op);
+
+ qDebug() << "Supported CMs:" << ps->result();
+
+ foreach (const QString &cmName, ps->result()) {
+ CMWrapper *cmWrapper = new CMWrapper(cmName, this);
+ mCMWrappers.append(cmWrapper);
+ connect(cmWrapper,
+ SIGNAL(finished()),
+ SLOT(onCMWrapperFinished()));
+ }
+}
+
+void Protocols::onCMWrapperFinished()
+{
+ if (++cmWrappersFinished == mCMWrappers.size()) {
+ QCoreApplication::quit();
+ }
+}
+
diff --git a/qt4/examples/protocols/protocols.h b/qt4/examples/protocols/protocols.h
new file mode 100644
index 000000000..896fe17a0
--- /dev/null
+++ b/qt4/examples/protocols/protocols.h
@@ -0,0 +1,57 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2010 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_protocols_protocols_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_protocols_protocols_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Types>
+
+#include "cm-wrapper.h"
+
+#include <QList>
+#include <QObject>
+
+using namespace Tp;
+
+namespace Tp
+{
+class PendingOperation;
+}
+
+class Protocols : public QObject
+{
+ Q_OBJECT
+
+public:
+ Protocols();
+ ~Protocols();
+
+private Q_SLOTS:
+ void onListNamesFinished(Tp::PendingOperation *op);
+ void onCMWrapperFinished();
+
+private:
+ QList<CMWrapper *> mCMWrappers;
+ int cmWrappersFinished;
+};
+
+#endif
diff --git a/qt4/examples/roster/CMakeLists.txt b/qt4/examples/roster/CMakeLists.txt
new file mode 100644
index 000000000..026959f9a
--- /dev/null
+++ b/qt4/examples/roster/CMakeLists.txt
@@ -0,0 +1,34 @@
+set(roster_SRCS
+ main.cpp
+ roster-window.cpp
+ roster-item.cpp
+ roster-widget.cpp)
+
+set(roster_MOC_SRCS
+ roster-window.h
+ roster-item.h
+ roster-widget.h)
+
+tpqt4_generate_mocs(${roster_MOC_SRCS})
+
+add_executable(roster ${roster_SRCS} ${roster_MOC_SRCS})
+target_link_libraries(roster
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
+
+set(telepathy_qt4_examples_roster_SRCS
+ roster-item.cpp
+ roster-widget.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/roster-item.moc.hpp
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/roster-widget.moc.hpp)
+
+add_library(telepathy-qt4-examples-roster ${telepathy_qt4_examples_roster_SRCS})
+target_link_libraries(telepathy-qt4-examples-roster
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTXML_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/roster/main.cpp b/qt4/examples/roster/main.cpp
new file mode 100644
index 000000000..0ae8158dd
--- /dev/null
+++ b/qt4/examples/roster/main.cpp
@@ -0,0 +1,48 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Types>
+
+#include <QDebug>
+#include <QtGui>
+
+#include "roster-window.h"
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ if (argc < 2) {
+ qDebug() << "usage:" << argv[0] << "<account name, as in mc-tool list>";
+ return 1;
+ }
+
+ Tp::registerTypes();
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+
+ QString accountPath = QLatin1String(argv[1]);
+ RosterWindow w(accountPath);
+ w.show();
+
+ return app.exec();
+}
diff --git a/qt4/examples/roster/roster-item.cpp b/qt4/examples/roster/roster-item.cpp
new file mode 100644
index 000000000..25110f509
--- /dev/null
+++ b/qt4/examples/roster/roster-item.cpp
@@ -0,0 +1,78 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roster-item.h"
+#include "_gen/roster-item.moc.hpp"
+
+#include <TelepathyQt4/Presence>
+
+using namespace Tp;
+
+RosterItem::RosterItem(const ContactPtr &contact, QListWidget *parent)
+ : QObject(parent),
+ QListWidgetItem(parent),
+ mContact(contact)
+{
+ onContactChanged();
+
+ connect(contact.data(),
+ SIGNAL(aliasChanged(QString)),
+ SLOT(onContactChanged()));
+ connect(contact.data(),
+ SIGNAL(presenceChanged(Tp::Presence)),
+ SLOT(onContactChanged()));
+ connect(contact.data(),
+ SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState)),
+ SLOT(onContactChanged()));
+ connect(contact.data(),
+ SIGNAL(publishStateChanged(Tp::Contact::PresenceState,QString)),
+ SLOT(onContactChanged()));
+ connect(contact.data(),
+ SIGNAL(blockStatusChanged(bool)),
+ SLOT(onContactChanged()));
+}
+
+RosterItem::~RosterItem()
+{
+}
+
+void RosterItem::onContactChanged()
+{
+ QString status = mContact->presence().status();
+ // I've asked to see the contact presence
+ if (mContact->subscriptionState() == Contact::PresenceStateAsk) {
+ setText(QString(QLatin1String("%1 (%2) (awaiting approval)")).arg(mContact->id()).arg(status));
+ // The contact asked to see my presence
+ } else if (mContact->publishState() == Contact::PresenceStateAsk) {
+ setText(QString(QLatin1String("%1 (%2) (pending approval)")).arg(mContact->id()).arg(status));
+ } else if (mContact->subscriptionState() == Contact::PresenceStateNo &&
+ mContact->publishState() == Contact::PresenceStateNo) {
+ setText(QString(QLatin1String("%1 (unknown)")).arg(mContact->id()));
+ } else {
+ setText(QString(QLatin1String("%1 (%2)")).arg(mContact->id()).arg(status));
+ }
+
+ if (mContact->isBlocked()) {
+ setText(QString(QLatin1String("%1 (blocked)")).arg(text()));
+ }
+
+ emit changed();
+}
diff --git a/qt4/examples/roster/roster-item.h b/qt4/examples/roster/roster-item.h
new file mode 100644
index 000000000..aeaf30aa1
--- /dev/null
+++ b/qt4/examples/roster/roster-item.h
@@ -0,0 +1,52 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_roster_roster_item_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_roster_roster_item_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Contact>
+
+#include <QListWidgetItem>
+#include <QString>
+
+class RosterItem : public QObject, public QListWidgetItem
+{
+ Q_OBJECT
+
+public:
+ RosterItem(const Tp::ContactPtr &contact,
+ QListWidget *parent = 0);
+ ~RosterItem();
+
+ Tp::ContactPtr contact() const { return mContact; }
+
+Q_SIGNALS:
+ void changed();
+
+private Q_SLOTS:
+ void onContactChanged();
+
+private:
+ Tp::ContactPtr mContact;
+};
+
+#endif
diff --git a/qt4/examples/roster/roster-widget.cpp b/qt4/examples/roster/roster-widget.cpp
new file mode 100644
index 000000000..21b7526f5
--- /dev/null
+++ b/qt4/examples/roster/roster-widget.cpp
@@ -0,0 +1,380 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roster-widget.h"
+#include "_gen/roster-widget.moc.hpp"
+
+#include "roster-item.h"
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingConnection>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+
+#include <QAction>
+#include <QDebug>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QListWidget>
+#include <QListWidgetItem>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+using namespace Tp;
+
+RosterWidget::RosterWidget(QWidget *parent)
+ : QWidget(parent)
+{
+ setWindowTitle(QLatin1String("Roster"));
+
+ createActions();
+ setupGui();
+}
+
+RosterWidget::~RosterWidget()
+{
+}
+
+void RosterWidget::setConnection(const ConnectionPtr &conn)
+{
+ if (mConn) {
+ unsetConnection();
+ }
+
+ mConn = conn;
+ connect(conn->contactManager().data(),
+ SIGNAL(presencePublicationRequested(const Tp::Contacts &)),
+ SLOT(onPresencePublicationRequested(const Tp::Contacts &)));
+ // TODO listen to allKnownContactsChanged
+
+ connect(conn->contactManager().data(),
+ SIGNAL(stateChanged(Tp::ContactListState)),
+ SLOT(onContactManagerStateChanged(Tp::ContactListState)));
+ onContactManagerStateChanged(conn->contactManager()->state());
+}
+
+void RosterWidget::unsetConnection()
+{
+ while (mList->count() > 0) {
+ RosterItem *item = (RosterItem *) mList->item(0);
+ mList->takeItem(0);
+ delete item;
+ }
+ mConn.reset();
+ updateActions();
+ mAddBtn->setEnabled(false);
+}
+
+void RosterWidget::createActions()
+{
+ mAuthAction = new QAction(QLatin1String("Authorize Contact"), this);
+ mAuthAction->setEnabled(false);
+ connect(mAuthAction,
+ SIGNAL(triggered(bool)),
+ SLOT(onAuthActionTriggered(bool)));
+ mDenyAction = new QAction(QLatin1String("Deny Contact"), this);
+ mDenyAction->setEnabled(false);
+ connect(mDenyAction,
+ SIGNAL(triggered(bool)),
+ SLOT(onDenyActionTriggered(bool)));
+ mRemoveAction = new QAction(QLatin1String("Remove Contact"), this);
+ mRemoveAction->setEnabled(false);
+ connect(mRemoveAction,
+ SIGNAL(triggered(bool)),
+ SLOT(onRemoveActionTriggered(bool)));
+ mBlockAction = new QAction(QLatin1String("Block Contact"), this);
+ mBlockAction->setEnabled(false);
+ mBlockAction->setCheckable(true);
+ connect(mBlockAction,
+ SIGNAL(triggered(bool)),
+ SLOT(onBlockActionTriggered(bool)));
+}
+
+void RosterWidget::setupGui()
+{
+ QVBoxLayout *vbox = new QVBoxLayout;
+
+ mList = new QListWidget;
+ connect(mList,
+ SIGNAL(itemSelectionChanged()),
+ SLOT(onItemSelectionChanged()));
+ vbox->addWidget(mList);
+
+ mList->setContextMenuPolicy(Qt::ActionsContextMenu);
+ mList->addAction(mAuthAction);
+ mList->addAction(mDenyAction);
+ mList->addAction(mRemoveAction);
+ mList->addAction(mBlockAction);
+
+ QHBoxLayout *hbox = new QHBoxLayout;
+
+ mAddBtn = new QPushButton(QLatin1String("+"));
+ mAddBtn->setEnabled(false);
+ connect(mAddBtn,
+ SIGNAL(clicked(bool)),
+ SLOT(onAddButtonClicked()));
+ hbox->addWidget(mAddBtn);
+ hbox->addStretch(1);
+
+ vbox->addLayout(hbox);
+
+ setLayout(vbox);
+
+ mAddDlg = new QDialog(this);
+ mAddDlg->setWindowTitle(QLatin1String("Add Contact"));
+ QVBoxLayout *addDlgVBox = new QVBoxLayout;
+
+ QHBoxLayout *addDlgEntryHBox = new QHBoxLayout;
+ QLabel *label = new QLabel(QLatin1String("Username"));
+ addDlgEntryHBox->addWidget(label);
+ mAddDlgEdt = new QLineEdit();
+ addDlgEntryHBox->addWidget(mAddDlgEdt);
+ addDlgVBox->addLayout(addDlgEntryHBox);
+
+ QDialogButtonBox *addDlgBtnBox = new QDialogButtonBox(
+ QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
+ connect(addDlgBtnBox, SIGNAL(accepted()), mAddDlg, SLOT(accept()));
+ connect(addDlgBtnBox, SIGNAL(rejected()), mAddDlg, SLOT(reject()));
+ addDlgVBox->addWidget(addDlgBtnBox);
+
+ mAddDlg->setLayout(addDlgVBox);
+}
+
+RosterItem *RosterWidget::createItemForContact(const ContactPtr &contact,
+ bool &exists)
+{
+ RosterItem *item;
+ exists = false;
+ for (int i = 0; i < mList->count(); ++i) {
+ item = dynamic_cast<RosterItem*>(mList->item(i));
+ if (item->contact() == contact) {
+ exists = true;
+ return item;
+ }
+ }
+
+ return new RosterItem(contact, mList);
+}
+
+void RosterWidget::onContactManagerStateChanged(ContactListState state)
+{
+ if (state == ContactListStateSuccess) {
+ qDebug() << "Loading contacts";
+ RosterItem *item;
+ bool exists;
+ foreach (const ContactPtr &contact, mConn->contactManager()->allKnownContacts()) {
+ exists = false;
+ item = createItemForContact(contact, exists);
+ if (!exists) {
+ connect(item, SIGNAL(changed()), SLOT(updateActions()));
+ }
+ }
+
+ mAddBtn->setEnabled(true);
+ }
+}
+
+void RosterWidget::onPresencePublicationRequested(const Contacts &contacts)
+{
+ qDebug() << "Presence publication requested";
+ RosterItem *item;
+ bool exists;
+ foreach (const ContactPtr &contact, contacts) {
+ exists = false;
+ item = createItemForContact(contact, exists);
+ if (!exists) {
+ connect(item, SIGNAL(changed()), SLOT(updateActions()));
+ }
+ }
+}
+
+void RosterWidget::onItemSelectionChanged()
+{
+ updateActions();
+}
+
+void RosterWidget::onAddButtonClicked()
+{
+ mAddDlgEdt->clear();
+ int ret = mAddDlg->exec();
+ if (ret == QDialog::Rejected) {
+ return;
+ }
+
+ QString username = mAddDlgEdt->text();
+ PendingContacts *pcontacts = mConn->contactManager()->contactsForIdentifiers(
+ QStringList() << username);
+ connect(pcontacts,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onContactRetrieved(Tp::PendingOperation *)));
+}
+
+void RosterWidget::onAuthActionTriggered(bool checked)
+{
+ Q_UNUSED(checked);
+
+ QList<QListWidgetItem *> selectedItems = mList->selectedItems();
+ if (selectedItems.isEmpty()) {
+ return;
+ }
+
+ Q_ASSERT(selectedItems.size() == 1);
+ RosterItem *item = dynamic_cast<RosterItem*>(selectedItems.first());
+ if (item->contact()->publishState() != Contact::PresenceStateYes) {
+ item->contact()->authorizePresencePublication();
+ }
+}
+
+void RosterWidget::onDenyActionTriggered(bool checked)
+{
+ Q_UNUSED(checked);
+
+ QList<QListWidgetItem *> selectedItems = mList->selectedItems();
+ if (selectedItems.isEmpty()) {
+ return;
+ }
+
+ Q_ASSERT(selectedItems.size() == 1);
+ RosterItem *item = dynamic_cast<RosterItem*>(selectedItems.first());
+ if (item->contact()->publishState() != Contact::PresenceStateNo) {
+ // The contact can't see my presence
+ item->contact()->removePresencePublication();
+ }
+}
+
+void RosterWidget::onRemoveActionTriggered(bool checked)
+{
+ Q_UNUSED(checked);
+
+ QList<QListWidgetItem *> selectedItems = mList->selectedItems();
+ if (selectedItems.isEmpty()) {
+ return;
+ }
+
+ Q_ASSERT(selectedItems.size() == 1);
+ RosterItem *item = dynamic_cast<RosterItem*>(selectedItems.first());
+ if (item->contact()->subscriptionState() != Contact::PresenceStateNo) {
+ // The contact can't see my presence and I can't see his/her presence
+ item->contact()->removePresencePublication();
+ item->contact()->removePresenceSubscription();
+ }
+}
+
+void RosterWidget::onBlockActionTriggered(bool checked)
+{
+ QList<QListWidgetItem *> selectedItems = mList->selectedItems();
+ if (selectedItems.isEmpty()) {
+ return;
+ }
+
+ Q_ASSERT(selectedItems.size() == 1);
+ RosterItem *item = dynamic_cast<RosterItem*>(selectedItems.first());
+ if (checked) {
+ item->contact()->block();
+ } else {
+ item->contact()->unblock();
+ }
+}
+
+void RosterWidget::onContactRetrieved(Tp::PendingOperation *op)
+{
+ PendingContacts *pcontacts = qobject_cast<PendingContacts *>(op);
+ QList<ContactPtr> contacts = pcontacts->contacts();
+ Q_ASSERT(pcontacts->identifiers().size() == 1);
+ QString username = pcontacts->identifiers().first();
+ if (contacts.size() != 1 || !contacts.first()) {
+ QMessageBox msgBox;
+ msgBox.setText(QString(QLatin1String("Unable to add contact \"%1\"")).arg(username));
+ msgBox.exec();
+ return;
+ }
+
+ ContactPtr contact = contacts.first();
+ qDebug() << "Request presence subscription for contact" << username;
+ bool exists = false;
+ RosterItem *item = createItemForContact(contact, exists);
+ if (!exists) {
+ connect(item, SIGNAL(changed()), SLOT(updateActions()));
+ }
+ contact->requestPresenceSubscription();
+}
+
+void RosterWidget::updateActions()
+{
+ QList<QListWidgetItem *> selectedItems = mList->selectedItems();
+ if (selectedItems.isEmpty()) {
+ mAuthAction->setEnabled(false);
+ mDenyAction->setEnabled(false);
+ mRemoveAction->setEnabled(false);
+ mBlockAction->setEnabled(false);
+ updateActions(0);
+ return;
+ }
+ Q_ASSERT(selectedItems.size() == 1);
+
+ RosterItem *item = dynamic_cast<RosterItem*>(selectedItems.first());
+ ContactPtr contact = item->contact();
+
+ ContactManagerPtr manager = contact->manager();
+ qDebug() << "Contact" << contact->id() << "selected";
+ qDebug() << " subscription state:" << contact->subscriptionState();
+ qDebug() << " publish state :" << contact->publishState();
+ qDebug() << " blocked :" << contact->isBlocked();
+
+ if (manager->canAuthorizePresencePublication() &&
+ contact->publishState() == Contact::PresenceStateAsk) {
+ mAuthAction->setEnabled(true);
+ } else {
+ mAuthAction->setEnabled(false);
+ }
+
+ if (manager->canRemovePresencePublication() &&
+ contact->publishState() != Contact::PresenceStateNo) {
+ mDenyAction->setEnabled(true);
+ } else {
+ mDenyAction->setEnabled(false);
+ }
+
+ if (manager->canRemovePresenceSubscription() &&
+ contact->subscriptionState() != Contact::PresenceStateNo) {
+ mRemoveAction->setEnabled(true);
+ } else {
+ mRemoveAction->setEnabled(false);
+ }
+
+ if (manager->canBlockContacts() &&
+ contact->publishState() == Contact::PresenceStateYes) {
+ mBlockAction->setEnabled(true);
+ } else {
+ mBlockAction->setEnabled(false);
+ }
+
+ mBlockAction->setChecked(contact->isBlocked());
+
+ updateActions(item);
+}
diff --git a/qt4/examples/roster/roster-widget.h b/qt4/examples/roster/roster-widget.h
new file mode 100644
index 000000000..d485ec50f
--- /dev/null
+++ b/qt4/examples/roster/roster-widget.h
@@ -0,0 +1,91 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_roster_roster_widget_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_roster_roster_widget_h_HEADER_GUARD_
+
+#include <QWidget>
+
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Connection>
+
+namespace Tp {
+class Connection;
+class PendingOperation;
+}
+
+class QAction;
+class QDialog;
+class QLineEdit;
+class QListWidget;
+class QListWidgetItem;
+class QPushButton;
+
+class RosterItem;
+
+class RosterWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ RosterWidget(QWidget *parent = 0);
+ virtual ~RosterWidget();
+
+ Tp::ConnectionPtr connection() const { return mConn; }
+ void setConnection(const Tp::ConnectionPtr &conn);
+ void unsetConnection();
+
+ QListWidget *listWidget() const { return mList; }
+
+protected:
+ virtual RosterItem *createItemForContact(
+ const Tp::ContactPtr &contact,
+ bool &exists);
+ virtual void updateActions(RosterItem *item) { }
+
+private Q_SLOTS:
+ void onContactManagerStateChanged(Tp::ContactListState state);
+ void onPresencePublicationRequested(const Tp::Contacts &);
+ void onItemSelectionChanged();
+ void onAddButtonClicked();
+ void onAuthActionTriggered(bool);
+ void onDenyActionTriggered(bool);
+ void onRemoveActionTriggered(bool);
+ void onBlockActionTriggered(bool);
+ void onContactRetrieved(Tp::PendingOperation *op);
+ void updateActions();
+
+private:
+ void createActions();
+ void setupGui();
+
+ Tp::ConnectionPtr mConn;
+ QAction *mAuthAction;
+ QAction *mRemoveAction;
+ QAction *mDenyAction;
+ QAction *mBlockAction;
+ QListWidget *mList;
+ QPushButton *mAddBtn;
+ QDialog *mAddDlg;
+ QLineEdit *mAddDlgEdt;
+};
+
+#endif
diff --git a/qt4/examples/roster/roster-window.cpp b/qt4/examples/roster/roster-window.cpp
new file mode 100644
index 000000000..a219bd830
--- /dev/null
+++ b/qt4/examples/roster/roster-window.cpp
@@ -0,0 +1,102 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roster-window.h"
+#include "_gen/roster-window.moc.hpp"
+
+#include "roster-widget.h"
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+
+#include <QDebug>
+
+using namespace Tp;
+
+RosterWindow::RosterWindow(const QString &accountName, QWidget *parent)
+ : QMainWindow(parent)
+{
+ setWindowTitle(QLatin1String("Roster"));
+
+ setupGui();
+
+ ChannelFactoryPtr channelFactory = ChannelFactory::create(
+ QDBusConnection::sessionBus());
+ ConnectionFactoryPtr connectionFactory = ConnectionFactory::create(
+ QDBusConnection::sessionBus(), Connection::FeatureConnected |
+ Connection::FeatureRoster | Connection::FeatureRosterGroups);
+ ContactFactoryPtr contactFactory = ContactFactory::create(
+ Contact::FeatureAlias | Contact::FeatureSimplePresence);
+
+ mAccount = Account::create(TP_QT4_ACCOUNT_MANAGER_BUS_NAME,
+ TP_QT4_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountName,
+ connectionFactory, channelFactory, contactFactory);
+ connect(mAccount->becomeReady(Account::FeatureCore),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onAccountReady(Tp::PendingOperation *)));
+
+ resize(240, 320);
+}
+
+RosterWindow::~RosterWindow()
+{
+}
+
+void RosterWindow::setupGui()
+{
+ mRoster = new RosterWidget();
+ setCentralWidget(mRoster);
+}
+
+void RosterWindow::onAccountReady(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Account cannot become ready - " <<
+ op->errorName() << '-' << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ qDebug() << "Account ready";
+ connect(mAccount.data(),
+ SIGNAL(connectionChanged(Tp::ConnectionPtr)),
+ SLOT(onAccountConnectionChanged(Tp::ConnectionPtr)));
+
+ if (mAccount->connection().isNull()) {
+ qDebug() << "The account given has no Connection. Please set it online to continue.";
+ }
+
+ onAccountConnectionChanged(mAccount->connection());
+}
+
+void RosterWindow::onAccountConnectionChanged(const ConnectionPtr &conn)
+{
+ if (conn) {
+ mRoster->setConnection(conn);
+ } else {
+ mRoster->unsetConnection();
+ }
+}
diff --git a/qt4/examples/roster/roster-window.h b/qt4/examples/roster/roster-window.h
new file mode 100644
index 000000000..f3a5b343d
--- /dev/null
+++ b/qt4/examples/roster/roster-window.h
@@ -0,0 +1,55 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_roster_roster_window_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_roster_roster_window_h_HEADER_GUARD_
+
+#include <QMainWindow>
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/Types>
+
+namespace Tp {
+class PendingOperation;
+}
+
+class RosterWidget;
+
+class RosterWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ RosterWindow(const QString &accountName, QWidget *parent = 0);
+ virtual ~RosterWindow();
+
+private Q_SLOTS:
+ void onAccountReady(Tp::PendingOperation *op);
+ void onAccountConnectionChanged(const Tp::ConnectionPtr &conn);
+
+private:
+ void setupGui();
+
+ Tp::AccountPtr mAccount;
+ RosterWidget *mRoster;
+};
+
+#endif
diff --git a/qt4/examples/stream-tubes/CMakeLists.txt b/qt4/examples/stream-tubes/CMakeLists.txt
new file mode 100644
index 000000000..3c6a69186
--- /dev/null
+++ b/qt4/examples/stream-tubes/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(tubereceiver_SRCS
+ tube-receiver.cpp)
+
+set(tubereceiver_MOC_SRCS
+ tube-receiver.h)
+
+tpqt4_generate_mocs(${tubereceiver_MOC_SRCS})
+
+add_executable(tubereceiver ${tubereceiver_SRCS} ${tubereceiver_MOC_SRCS})
+target_link_libraries(tubereceiver
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
+
+set(tubeinitiator_SRCS
+ tube-initiator.cpp)
+
+set(tubeinitiator_MOC_SRCS
+ tube-initiator.h)
+
+tpqt4_generate_mocs(${tubeinitiator_MOC_SRCS})
+
+add_executable(tubeinitiator ${tubeinitiator_SRCS} ${tubeinitiator_MOC_SRCS})
+target_link_libraries(tubeinitiator
+ ${QT_QTDBUS_LIBRARY}
+ ${QT_QTGUI_LIBRARY}
+ ${QT_QTCORE_LIBRARY}
+ telepathy-qt4)
diff --git a/qt4/examples/stream-tubes/tube-initiator.cpp b/qt4/examples/stream-tubes/tube-initiator.cpp
new file mode 100644
index 000000000..dd5aa4da5
--- /dev/null
+++ b/qt4/examples/stream-tubes/tube-initiator.cpp
@@ -0,0 +1,274 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tube-initiator.h"
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactCapabilities>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/PendingChannelRequest>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Presence>
+#include <TelepathyQt4/StreamTubeChannel>
+#include <TelepathyQt4/StreamTubeServer>
+
+#include <QDebug>
+#include <QTcpServer>
+#include <QTcpSocket>
+
+TubeInitiator::TubeInitiator(const QString &accountName, const QString &receiver, QObject *parent)
+ : QObject(parent),
+ mReceiver(receiver),
+ mTubeRequested(false)
+{
+ mServer = new QTcpServer(this);
+ connect(mServer, SIGNAL(newConnection()), this, SLOT(onTcpServerNewConnection()));
+ mServer->listen();
+
+ AccountFactoryPtr accountFactory = AccountFactory::create(
+ QDBusConnection::sessionBus(), Account::FeatureCore);
+ // We only care about CONNECTED connections, so let's specify that in a Connection Factory
+ ConnectionFactoryPtr connectionFactory = ConnectionFactory::create(
+ QDBusConnection::sessionBus(), Connection::FeatureCore | Connection::FeatureConnected);
+ ChannelFactoryPtr channelFactory = ChannelFactory::create(QDBusConnection::sessionBus());
+ ContactFactoryPtr contactFactory = ContactFactory::create(Contact::FeatureAlias);
+
+ mTubeServer = StreamTubeServer::create(
+ QStringList() << QLatin1String("tp-qt4-stube-example"), /* one peer-to-peer service */
+ QStringList(), /* no Room tube services */
+ QString(), /* autogenerated Client name */
+ true, /* do monitor connections */
+ accountFactory, connectionFactory, channelFactory, contactFactory);
+
+ connect(mTubeServer.data(),
+ SIGNAL(newTcpConnection(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,Tp::OutgoingStreamTubeChannelPtr)),
+ SLOT(onTubeNewConnection(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr)));
+ connect(mTubeServer.data(),
+ SIGNAL(tcpConnectionClosed(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,QString,QString,Tp::OutgoingStreamTubeChannelPtr)),
+ SLOT(onTubeConnectionClosed(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,QString,QString)));
+
+ mTubeServer->exportTcpSocket(mServer);
+ Q_ASSERT(mTubeServer->isRegistered());
+
+ connect(accountFactory->proxy(
+ TP_QT4_ACCOUNT_MANAGER_BUS_NAME,
+ TP_QT4_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountName,
+ connectionFactory,
+ mTubeServer->registrar()->channelFactory(),
+ mTubeServer->registrar()->contactFactory()),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onAccountReady(Tp::PendingOperation *)));
+}
+
+TubeInitiator::~TubeInitiator()
+{
+}
+
+void TubeInitiator::onAccountReady(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Account cannot become ready - " <<
+ op->errorName() << '-' << op->errorMessage();
+ QCoreApplication::exit(1);
+ return;
+ }
+
+ PendingReady *ready = qobject_cast<PendingReady *>(op);
+ Q_ASSERT(ready != NULL);
+
+ mAccount = AccountPtr::qObjectCast(ready->proxy());
+
+ qDebug() << "Account ready";
+ connect(mAccount.data(),
+ SIGNAL(connectionChanged(Tp::ConnectionPtr)),
+ SLOT(onAccountConnectionChanged(Tp::ConnectionPtr)));
+
+ if (mAccount->connection().isNull()) {
+ qDebug() << "The account given has no Connection. Please set it online to continue.";
+ } else {
+ onAccountConnectionChanged(mAccount->connection());
+ }
+}
+
+void TubeInitiator::onAccountConnectionChanged(const ConnectionPtr &conn)
+{
+ if (!conn) {
+ return;
+ }
+
+ // Connection::FeatureConnected being in the Connection Factory means that we shouldn't get
+ // pre-Connected Connections
+ Q_ASSERT(conn->isValid());
+ Q_ASSERT(conn->status() == ConnectionStatusConnected);
+
+ qDebug() << "Got a Connected Connection!";
+ mConn = conn;
+
+ qDebug() << "Creating contact object for receiver" << mReceiver;
+ connect(mConn->contactManager()->contactsForIdentifiers(QStringList() << mReceiver,
+ Features(Contact::FeatureCapabilities)),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onContactRetrieved(Tp::PendingOperation *)));
+}
+
+void TubeInitiator::onContactRetrieved(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Unable to create contact object for receiver" <<
+ mReceiver << "-" << op->errorName() << ": " << op->errorMessage();
+ return;
+ }
+
+ PendingContacts *pc = qobject_cast<PendingContacts *>(op);
+ Q_ASSERT(pc->contacts().size() == 1);
+ mContact = pc->contacts().first();
+
+ qDebug() << "Checking contact capabilities...";
+ connect(mContact.data(),
+ SIGNAL(capabilitiesChanged(Tp::ContactCapabilities)),
+ SLOT(onContactCapabilitiesChanged()));
+
+ if (mContact->capabilities().streamTubes(QLatin1String("tp-qt4-stube-example"))) {
+ onContactCapabilitiesChanged();
+ } else {
+ qDebug() << "The remote contact needs to be online and have the receiver application running to continue";
+ }
+}
+
+void TubeInitiator::onContactCapabilitiesChanged()
+{
+ if (mTubeRequested) {
+ return;
+ }
+
+ if (mContact->capabilities().streamTubes(QLatin1String("tp-qt4-stube-example"))) {
+ qDebug() << "The remote contact is capable of receiving tubes with service tp-qt4-stube-example now";
+
+ mTubeRequested = true;
+ connect(mAccount->createStreamTube(
+ mContact->id(),
+ QLatin1String("tp-qt4-stube-example")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onTubeRequestFinished(Tp::PendingOperation*)));
+ }
+}
+
+void TubeInitiator::onTubeRequestFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning() << "Unable to request stream tube channel -" <<
+ op->errorName() << ": " << op->errorMessage();
+ return;
+ }
+
+ qDebug() << "Stream tube channel request finished successfully!";
+}
+
+void TubeInitiator::onTubeNewConnection(
+ const QHostAddress &srcAddr,
+ quint16 srcPort,
+ const Tp::AccountPtr &account,
+ const Tp::ContactPtr &contact)
+{
+ qDebug() << "Source port" << srcPort << "is" << contact->alias();
+ mConnAliases.insert(qMakePair(srcAddr, srcPort), contact->alias());
+}
+
+void TubeInitiator::onTubeConnectionClosed(
+ const QHostAddress &srcAddr,
+ quint16 srcPort,
+ const Tp::AccountPtr &account,
+ const Tp::ContactPtr &contact,
+ const QString &error,
+ const QString &message)
+{
+ qDebug() << "Connection from source port" << srcPort << "closed with" << error << ':' << message;
+ mConnAliases.remove(qMakePair(srcAddr, srcPort));
+}
+
+void TubeInitiator::onTcpServerNewConnection()
+{
+ qDebug() << "Pending connection found";
+ QTcpSocket *socket = mServer->nextPendingConnection();
+ connect(socket, SIGNAL(readyRead()), this, SLOT(onDataFromSocket()));
+}
+
+void TubeInitiator::onDataFromSocket()
+{
+ QAbstractSocket *source = qobject_cast<QAbstractSocket *>(sender());
+ QString data = QLatin1String(source->readLine());
+ data.remove(QLatin1Char('\n'));
+
+ // Look up the alias of the remote sending us data based on the tube connection tracking. Of
+ // course, this is slightly overengineered; given that this example uses one-to-one tubes (not
+ // group/MUC tubes), all remote connections are always from the same contact we opened the tube
+ // to in the first place.
+ QPair<QHostAddress, quint16> peerAddr(source->peerAddress(), source->peerPort());
+ if (mConnAliases.contains(peerAddr)) {
+ qDebug() << "New data from contact" << mConnAliases.value(peerAddr) << ':' << data;
+ } else {
+ // We haven't found out the identity for this connection yet. This may happen, because the
+ // TCP server listen socket and the D-Bus connection are on separate sockets and have no
+ // mutual ordering guarantees.
+ //
+ // A GUI application would likely use a placeholder item and lazily replace it with a
+ // representation of the contact when newTcpConnection for this source address has been
+ // received. A non-interactive daemon or alike might, in turn, queue incoming data until the
+ // contact is known, or just carry on if the contact's details aren't needed (yet).
+ qDebug() << "New data from source port" << peerAddr.second << ':' << data;
+ }
+
+ if (data == QLatin1String("Hi there!!")) {
+ source->write(QByteArray("Hey back mate.\n"));
+ } else {
+ source->write(QByteArray("Sorry, I have no time for you right now.\n"));
+ }
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ if (argc != 3) {
+ qDebug() << "usage:" << argv[0] << "<account name, as in mc-tool list> <receiver contact ID>";
+ return 1;
+ }
+
+ Tp::registerTypes();
+
+ new TubeInitiator(QLatin1String(argv[1]), QLatin1String(argv[2]), &app);
+
+ return app.exec();
+}
+
+#include "_gen/tube-initiator.moc.hpp"
diff --git a/qt4/examples/stream-tubes/tube-initiator.h b/qt4/examples/stream-tubes/tube-initiator.h
new file mode 100644
index 000000000..69ab7d3f1
--- /dev/null
+++ b/qt4/examples/stream-tubes/tube-initiator.h
@@ -0,0 +1,79 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009,2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_stream_tubes_tube_initiator_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_stream_tubes_tube_initiator_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Types>
+
+#include <QHash>
+#include <QHostAddress>
+#include <QPair>
+
+class QTcpServer;
+using namespace Tp;
+
+namespace Tp
+{
+class ChannelRequestHints;
+class PendingOperation;
+}
+
+class TubeInitiator : public QObject
+{
+ Q_OBJECT
+
+public:
+ TubeInitiator(const QString &accountName, const QString &receiver, QObject *parent);
+ ~TubeInitiator();
+
+private Q_SLOTS:
+ void onAccountReady(Tp::PendingOperation *op);
+ void onAccountConnectionChanged(const Tp::ConnectionPtr &);
+ void onContactRetrieved(Tp::PendingOperation *op);
+ void onContactCapabilitiesChanged();
+ void onTubeRequestFinished(Tp::PendingOperation *op);
+ void onTubeNewConnection(const QHostAddress &sourceAddress, quint16 sourcePort,
+ const Tp::AccountPtr &account, const Tp::ContactPtr &contact);
+ void onTubeConnectionClosed(const QHostAddress &sourceAddress, quint16 sourcePort,
+ const Tp::AccountPtr &account, const Tp::ContactPtr &contact,
+ const QString &error, const QString &errorMessage);
+ void onTcpServerNewConnection();
+ void onDataFromSocket();
+
+private:
+ void createStreamTubeChannel();
+
+ QString mReceiver;
+ QTcpServer *mServer;
+
+ StreamTubeServerPtr mTubeServer;
+ AccountPtr mAccount;
+ ConnectionPtr mConn;
+ ContactPtr mContact;
+
+ bool mTubeRequested;
+ QHash<QPair<QHostAddress, quint16>, QString> mConnAliases;
+};
+
+#endif
diff --git a/qt4/examples/stream-tubes/tube-receiver.cpp b/qt4/examples/stream-tubes/tube-receiver.cpp
new file mode 100644
index 000000000..a202dd15b
--- /dev/null
+++ b/qt4/examples/stream-tubes/tube-receiver.cpp
@@ -0,0 +1,99 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009,2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tube-receiver.h"
+
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/StreamTubeClient>
+
+#include <QDebug>
+#include <QIODevice>
+#include <QStringList>
+
+TubeReceiver::TubeReceiver(QObject *parent)
+ : QObject(parent)
+{
+ mTubeClient = StreamTubeClient::create(QStringList() << QLatin1String("tp-qt4-stube-example"));
+ connect(mTubeClient.data(),
+ SIGNAL(tubeAcceptedAsUnix(QString,bool,uchar,Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeAccepted(QString)));
+ mTubeClient->setToAcceptAsUnix(false); // no SCM_CREDENTIALS required
+}
+
+TubeReceiver::~TubeReceiver()
+{
+}
+
+void TubeReceiver::onTubeAccepted(const QString &listenAddress)
+{
+ qDebug() << "Stream tube channel accepted and opened, listening at" << listenAddress;
+
+ mDevice = new QLocalSocket(this);
+ mDevice->connectToServer(listenAddress);
+
+ connect(mDevice, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)),
+ this, SLOT(onStateChanged(QLocalSocket::LocalSocketState)));
+ onStateChanged(mDevice->state());
+}
+
+void TubeReceiver::onStateChanged(QLocalSocket::LocalSocketState state)
+{
+ if (state == QLocalSocket::ConnectedState) {
+ qDebug() << "Local socket connected and ready";
+ connect(mDevice, SIGNAL(readyRead()), this, SLOT(onDataFromSocket()));
+ mDevice->write(QByteArray("Hi there!!\n"));
+
+ // Throw in some stuff
+ QTimer *timer = new QTimer(this);
+ timer->setInterval(2000);
+ connect(timer, SIGNAL(timeout()), this, SLOT(onTimerTimeout()));
+ timer->start();
+ } else {
+ qDebug() << "Socket in state " << state;
+ }
+}
+
+void TubeReceiver::onDataFromSocket()
+{
+ QIODevice *source = qobject_cast<QIODevice*>(sender());
+ QString data = QLatin1String(source->readLine());
+ data.remove(QLatin1Char('\n'));
+ qDebug() << "New data from socket: " << data;
+}
+
+void TubeReceiver::onTimerTimeout()
+{
+ mDevice->write(QByteArray("ping, I'm alive\n"));
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ Tp::registerTypes();
+
+ new TubeReceiver(&app);
+
+ return app.exec();
+}
+
+#include "_gen/tube-receiver.moc.hpp"
diff --git a/qt4/examples/stream-tubes/tube-receiver.h b/qt4/examples/stream-tubes/tube-receiver.h
new file mode 100644
index 000000000..b60180373
--- /dev/null
+++ b/qt4/examples/stream-tubes/tube-receiver.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of TelepathyQt4
+ *
+ * Copyright (C) 2009-2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2009,2011 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _TelepathyQt4_examples_stream_tubes_tube_receiver_h_HEADER_GUARD_
+#define _TelepathyQt4_examples_stream_tubes_tube_receiver_h_HEADER_GUARD_
+
+#include <TelepathyQt4/Types>
+
+#include <QLocalSocket>
+
+using namespace Tp;
+
+namespace Tp
+{
+class PendingOperation;
+}
+
+class TubeReceiver : public QObject
+{
+ Q_OBJECT
+
+public:
+ TubeReceiver(QObject *parent);
+ ~TubeReceiver();
+
+private Q_SLOTS:
+ void onTubeAccepted(const QString &listenAddress);
+ void onStateChanged(QLocalSocket::LocalSocketState newState);
+ void onTimerTimeout();
+ void onDataFromSocket();
+
+private:
+ StreamTubeClientPtr mTubeClient;
+ QLocalSocket *mDevice;
+};
+
+#endif
diff --git a/qt4/spec/Account.xml b/qt4/spec/Account.xml
new file mode 100644
index 000000000..b706e279f
--- /dev/null
+++ b/qt4/spec/Account.xml
@@ -0,0 +1,713 @@
+<?xml version="1.0" ?>
+<node name="/Account"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Account">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An Account object encapsulates the necessary details to make a
+ Telepathy connection.</p>
+
+ <p>Accounts are uniquely identified by object path. The object path
+ of an Account MUST take the form
+ <code>/org/freedesktop/Telepathy/Account/<em>cm</em>/<em>proto</em>/<em>acct</em></code>, where:</p>
+
+ <ul>
+ <li><em>cm</em> is the same <tp:type>Connection_Manager_Name</tp:type>
+ that appears in the connection manager's well-known bus name and
+ object path</li>
+ <li><em>proto</em> is the <tp:type>Protocol</tp:type> name as seen in
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ConnectionManager.ListProtocols</tp:dbus-ref>,
+ but with "-" replaced with "_"
+ (i.e. the same as in the object-path of a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>)</li>
+ <li><em>acct</em> is an arbitrary string of ASCII letters, digits
+ and underscores, starting with a letter or underscore, which
+ uniquely identifies this account</li>
+ <li>Clients SHOULD parse the object path to discover the
+ connection manager and protocol</li>
+ <li>Clients MUST NOT attempt to parse <em>acct</em></li>
+ <li>Clients MUST NOT assume that <em>acct</em> matches
+ the connection-specific part of a Connection's object-path and
+ bus name</li>
+ <li>The account manager SHOULD choose <em>acct</em> such that if
+ an account is deleted, its object path will be re-used if and only
+ if the new account is in some sense "the same"
+ (incorporating the 'account' parameter in some way is
+ recommended)</li>
+ </ul>
+
+ <tp:rationale>
+ <p>This API avoids specifying the "profiles" used in Mission Control
+ 4.x or the "presets" that have been proposed to replace them. An
+ optional interface will be provided for AM implementations
+ that want to provide presets.</p>
+
+ <p>There is deliberately no functionality here for opening channels;
+ we intend to provide that in the channel dispatcher.</p>
+
+ <p>Other missing features which would be better in their own
+ interfaces:</p>
+
+ <ul>
+ <li>dynamic parameter-providing (aka provisioning)</li>
+ <li>saved server capabilities</li>
+ <li>account conditions</li>
+ <li>account grouping</li>
+ </ul>
+ </tp:rationale>
+
+ </tp:docstring>
+ <tp:added version="0.17.2"/>
+ <tp:changed version="0.17.6">moved the Avatar property to a separate
+ interface</tp:changed>
+
+ <!-- Missing functionality compared with NMC 4.x account + profile,
+ apart from as listed above:
+
+ * vCard field, + default profile for each vCard field
+ (vCard field is per protocol and should be chosen by the
+ Telepathy <-> address-book bridge?; default profile is now
+ meaningless)
+
+ * "normalized name" (normalized handle?)
+
+ * branding icon (what's this and how does it differ from the icon?)
+
+ * configuration UI (not our problem - perhaps the UI could special-case
+ by cm,protocol,preset tuples?)
+
+ * default account domain (somewhat meaningless in general; specialized
+ account config UI can hard-code this)
+
+ * SPLIT_ACCOUNT (pseudo-capability - this is a property of the protocol,
+ not the profile, and in any case only the account config UI cares)
+
+ Missing functionality compared with Decibel accounts:
+
+ * we don't really know, they take arbitrary key/value pairs...
+ but display name, protocol, presence/message, current, autoreconnect
+ are the ones given special status by the source, and we have all of them
+ -->
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read">
+ <tp:docstring>
+ A list of the extra interfaces provided by this account.
+ </tp:docstring>
+ </property>
+
+ <method name="Remove" tp:name-for-bindings="Remove">
+ <tp:docstring>Delete the account.</tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="Removed" tp:name-for-bindings="Removed">
+ <tp:docstring>
+ This account has been removed.
+
+ <tp:rationale>
+ This is redundant with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.AccountManager">AccountRemoved</tp:dbus-ref>,
+ but it's still worth having,
+ to avoid having to bind to AccountManager.AccountRemoved to tell
+ you whether your Account is valid — ideally, an account-editing UI
+ should only care about a single Account.
+ </tp:rationale>
+ </tp:docstring>
+ </signal>
+
+ <signal name="AccountPropertyChanged"
+ tp:name-for-bindings="Account_Property_Changed">
+ <tp:docstring>
+ The values of one or more properties on this interface (that do not
+ specify that this signal does not apply to them) may have changed.
+ This does not cover properties of other interfaces, which must
+ provide their own change notification if appropriate.
+ </tp:docstring>
+
+ <arg name="Properties" type="a{sv}">
+ <tp:docstring>
+ A map from property names in this namespace (e.g.
+ <tp:member-ref>Nickname</tp:member-ref>) to
+ values. Properties whose values have not changed SHOULD be
+ omitted, but this need not be done.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="DisplayName" type="s" access="readwrite"
+ tp:name-for-bindings="Display_Name">
+ <tp:docstring>
+ The user-visible name of this account. This SHOULD be chosen by the
+ user at account creation time. The account creation user interface
+ is responsible for setting a reasonable default value in the user's
+ locale; something like "Jabber (bob@example.com)" would be sensible.
+ </tp:docstring>
+ </property>
+
+ <property name="Icon" tp:name-for-bindings="Icon"
+ type="s" access="readwrite">
+ <tp:docstring>
+ The name of an icon in the system's icon theme, such as "im-msn",
+ or the empty string to not specify an icon. If the icon is set to
+ an empty string, the account manager or any client MAY derive a
+ default icon, for instance from the protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="Valid" tp:name-for-bindings="Valid"
+ type="b" access="read">
+ <tp:docstring>
+ If true, this account is considered by the account manager to be
+ complete and usable. If false, user action is required to make it
+ usable, and it will never attempt to connect (for instance, this
+ might be caused by the absence of a required parameter).
+
+ <tp:rationale>
+ For connection managers with a plugin architecture, like
+ telepathy-haze, we have little or no control over the parameters
+ offered; for platforms with package management, we have little or
+ no control over the CMs offered. NMC 4.x would just pretend the
+ account didn't exist in these circumstances, but silent data loss
+ is bad, and UIs with CM-specific knowledge (or a user filling in
+ newly-required parameters) might be able to rescue a broken account.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Enabled" tp:name-for-bindings="Enabled"
+ type="b" access="readwrite">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This property gives the users the possibility to prevent an account
+ from being used. This flag does not change the validity of the
+ account.</p>
+
+ <p>A disabled account can never be put online.</p>
+
+ <tp:rationale>
+ <p>Use cases:</p>
+
+ <ul>
+ <li>user has two or more accounts capable of calling contact X, but
+ he doesn't want the UI to prompt him everytime about which one he
+ wants to use for the call. He can then disable all the equivalent
+ accounts but one.</li>
+
+ <li>There is some temporary server error and the user doesn't want
+ to be be bother by error messages, or change the account
+ configuration: temporarily disabling the account is quicker.</li>
+ </ul>
+ </tp:rationale>
+
+ <p>The AccountManager SHOULD allow this property to be set on invalid
+ accounts, but MUST NOT attempt to put invalid accounts online
+ even if they become Enabled.</p>
+
+ <tp:rationale>
+ <p>There doesn't seem to be any good reason not to allow this.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Nickname" tp:name-for-bindings="Nickname"
+ type="s" access="readwrite">
+ <tp:docstring>
+ The nickname to set on this account for display to other contacts,
+ as set by the user. When the account becomes connected, the
+ account manager SHOULD set this as the user's alias using <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Aliasing">SetAliases</tp:dbus-ref>
+ if appropriate.
+
+ <tp:rationale>
+ In a later specification revision, we plan to separate the concepts
+ of a contact's nickname as set by themselves, and the local
+ name for them in our contact list (a "handle" or "pet name" as
+ described in XEP-0165 and its references). The terminology change
+ from alias to nickname here is a step in that direction.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Service" tp:name-for-bindings="Service" type="s"
+ access="readwrite">
+ <tp:added version="0.19.8"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Some protocols, like XMPP and SIP, are used by various different
+ user-recognised brands, such as <i>Google Talk</i> and <i>Ovi by
+ Nokia</i>. On accounts for such services, this property SHOULD be
+ set to a string describing the service, which MUST consist only of
+ ASCII letters, numbers and hyphen/minus signs, and start with a
+ letter (matching the requirements for <tp:type>Protocol</tp:type>).
+ For the <tt>jabber</tt> protocol, one of the following service names
+ should be used if possible:</p>
+
+ <ul>
+ <li><tt>google-talk</tt> (for <a
+ href="http://www.google.com/talk/">Google's IM service</a>)</li>
+ <li><tt>ovi-chat</tt> (for <a href="http://www.ovi.com/">Ovi</a>'s IM
+ service)</li>
+ <li><tt>facebook</tt> (for <a
+ href="http://www.facebook.com/sitetour/chat.php">Facebook's IM
+ service</a>)</li>
+ <li><tt>lj-talk</tt> (for <a
+ href="http://www.livejournal.com/chat/">LiveJournal's IM
+ service</a>)</li>
+
+ </ul>
+
+ <p>The <tp:member-ref>Icon</tp:member-ref> property SHOULD be set to a
+ corresponding brand-specific icon name, if possible. In the future,
+ this property may be used as an index into additional
+ service-specific customizations. If this property is the empty string
+ (or missing), the service is determined by the protocol name (either
+ because this is a single-service protocol like <tt>msn</tt>, or
+ because this is just a generic <tt>jabber</tt> or <tt>sip</tt>
+ account without specific branding).</p>
+
+ <p>This property MAY be set, if appropriate, when calling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.AccountManager"
+ >CreateAccount</tp:dbus-ref>. Updating this property will fail on
+ externally-stored accounts whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account.Interface.Storage"
+ >StorageRestrictions</tp:dbus-ref> include
+ <code>Cannot_Set_Service</code>.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Parameters" tp:name-for-bindings="Parameters"
+ type="a{sv}" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from connection manager parameter names (as in the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ConnectionManager</tp:dbus-ref>
+ interface) to their values. This property includes
+ only those parameters that are stored for this account, and SHOULD
+ only include those parameters that the user has explicitly set.
+ </p>
+ <p>This property cannot be altered using
+ <code>org.freedesktop.DBus.Properties.Set()</code>; use
+ <tp:member-ref>UpdateParameters</tp:member-ref> instead.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="UpdateParameters" tp:name-for-bindings="Update_Parameters">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Change the value of the <tp:member-ref>Parameters</tp:member-ref>
+ property.</p>
+
+ <p>If any of the <var>Set</var> parameters’
+ <tp:type>Conn_Mgr_Param_Flags</tp:type> include
+ <code>DBus_Property</code>, the change will be applied immediately to
+ the corresponding D-Bus Property on the active
+ <tp:member-ref>Connection</tp:member-ref>, if there is one. If any of
+ the <var>Unset</var> parameters’
+ <tp:type>Conn_Mgr_Param_Flags</tp:type> include both
+ <code>DBus_Property</code> and <code>Has_Default</code>, the
+ corresponding D-Bus Property on the connection will be set to the
+ default value. Changes to other parameters will not take effect
+ until the next time the account is disconnected and reconnected. (If
+ parameters are explicitly set to their default value, or are unset
+ when previously set to their default value, the account manager MAY
+ decide that no reconnection is necessary to make the change take
+ effect.)</p>
+
+ <tp:rationale>
+ <p>In general, reconnecting is a destructive operation that shouldn't
+ happen as a side-effect. In particular, migration tools that
+ twiddle the settings of all accounts shouldn't cause an automatic
+ disconnect and reconnect.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:changed version="0.17.16">
+ parameters which are also D-Bus properties can and should be updated on
+ existing Connections
+ </tp:changed>
+
+ <tp:changed version="0.17.24">
+ return an array of the parameters that won't change until the account
+ is reconnected
+ </tp:changed>
+
+ <arg name="Set" type="a{sv}" direction="in">
+ <tp:docstring>
+ A mapping from parameter names to their values. These parameters
+ should be stored for future use.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Unset" type="as" direction="in">
+ <tp:docstring>
+ A list of the names of parameters to be removed from the set of
+ stored values, allowing the default values to be used.
+ If the given parameters were not, in fact, stored, or even if they
+ do not exist at all, the account manager MUST accept this without
+ error.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Reconnect_Required" type="as" direction="out">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If all of the updates could be applied to the active
+ <tp:member-ref>Connection</tp:member-ref> (if any),
+ the empty list, signifying that no reconnection is required for the
+ new parameters to take effect. For example, if the only parameter
+ updated is <tt>...Cellular.<tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Cellular">MessageValidityPeriod</tp:dbus-ref></tt>,
+ the new value can be applied immediately to the connection.</p>
+
+ <p>Otherwise, a list of the names of parameters with changes that
+ will not take effect until the account is reconnected. User
+ interfaces that require "instant apply" semantics MAY call
+ <tp:member-ref>Reconnect</tp:member-ref> in response to receiving a
+ non-empty list. For example, if the caller updates both
+ <tt>...Anonymity.<tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Anonymity">AnonymityMandatory</tp:dbus-ref></tt>
+ and <tt>require-encryption</tt>, the former can be applied to the
+ current connection, but the latter needs a reconnect to take
+ effect, so this method should return
+ <code>["require-encryption"]</code>.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="AutomaticPresence" type="(uss)" access="readwrite"
+ tp:type="Simple_Presence" tp:name-for-bindings="Automatic_Presence">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The presence status that this account should have if it is brought
+ online.</p>
+
+ <tp:rationale>
+ In ITOS2007 and ITOS2008 this is a global preference, not visible
+ on D-Bus (the "default presence"). "Automatic presence" better
+ describes when it is used.
+ </tp:rationale>
+
+ <p>Setting this property MUST NOT actually change the account's
+ status until the next time it is (re)connected for some reason.</p>
+
+ <p>The value of this property MUST be one that would be acceptable
+ for <tp:member-ref>RequestedPresence</tp:member-ref>,
+ with the additional restriction that the
+ <tp:type>Connection_Presence_Type</tp:type> MUST NOT be Offline.</p>
+
+ <tp:rationale>
+ <p>Otherwise, it would not be possible to use this presence to bring
+ the account online for a channel request.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ConnectAutomatically" type="b" access="readwrite"
+ tp:name-for-bindings="Connect_Automatically">
+ <tp:docstring>
+ If true, the account manager SHOULD attempt to put this account
+ online with the <tp:member-ref>AutomaticPresence</tp:member-ref>
+ whenever possible (in the base
+ Account interface this is deliberately left vague). If false,
+ it MUST NOT put the account online automatically in response to,
+ for instance, connectivity changes, but SHOULD still put the account
+ online with the <tp:member-ref>AutomaticPresence</tp:member-ref> if
+ requested by the user (for
+ instance, if the user tries to start a conversation using this
+ account).
+ </tp:docstring>
+ </property>
+
+ <property name="Connection" tp:name-for-bindings="Connection"
+ type="o" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the object path of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref> to
+ this account, or the special value <code>'/'</code> if there is no
+ connection.</p>
+
+ <p>If this object path is not '/', the Connection's well-known bus
+ name can be derived from this object path by removing the first '/'
+ and replacing subsequent '/' characters with '.'.</p>
+
+ <tp:rationale>
+ Object paths aren't nullable, so we can't use an empty string.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ConnectionStatus" type="u" tp:type="Connection_Status"
+ access="read" tp:name-for-bindings="Connection_Status">
+ <tp:docstring>
+ If the <tp:member-ref>Connection</tp:member-ref> property is non-empty,
+ the status of that connection.
+ If the Connection property is the empty string, this property may
+ either be Disconnected (indicating that the account manager is not
+ attempting to bring it online), or Connecting (indicating that the
+ account manager is attempting to connect).
+ The account manager is expected to set this by observing signals
+ from the Connection.
+
+ <tp:rationale>
+ If the AM is doing some sort of backoff/delay on reconnection
+ attempts, the account's status is conceptually "Connecting" even
+ though there is no Connection.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ConnectionStatusReason" type="u"
+ tp:type="Connection_Status_Reason" access="read"
+ tp:name-for-bindings="Connection_Status_Reason">
+ <tp:docstring>
+ The reason for the last change to
+ <tp:member-ref>ConnectionStatus</tp:member-ref>.
+ The account manager is expected to set this by observing signals
+ from the Connection.
+
+ <tp:rationale>
+ If you weren't watching the Connection at the time it failed,
+ you can't tell why - unless the AM can tell you.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ConnectionError" tp:name-for-bindings="Connection_Error"
+ access="read" type="s" tp:type="DBus_Error_Name">
+ <tp:added version="0.19.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If the last connection to this account failed with an error,
+ the D-Bus error name of that error; otherwise, the empty string.
+ The account manager is expected to set this by observing the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.ConnectionError</tp:dbus-ref> and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.StatusChanged</tp:dbus-ref>
+ signals.</p>
+
+ <p>If ConnectionError is received before the connection disconnects,
+ its first argument should be used to set this property;
+ otherwise, the Reason argument of StatusChanged should be converted
+ to a suitable D-Bus error name.</p>
+
+ <p>Whenever the Connection connects successfully, this property should
+ be reset to the empty string.</p>
+
+ <tp:rationale>
+ <p>This combines the state-recoverability of
+ <tp:member-ref>ConnectionStatusReason</tp:member-ref> with the
+ extensibility of Connection.ConnectionError.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ConnectionErrorDetails"
+ tp:name-for-bindings="Connection_Error_Details"
+ access="read" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:added version="0.19.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If the last connection to this account failed with an error,
+ a mapping representing any additional information about the last
+ disconnection; otherwise, the empty map. The keys and values are
+ the same as for the second argument of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.ConnectionError</tp:dbus-ref>.</p>
+
+ <p>Whenever the Connection connects successfully, this property should
+ be reset to the empty map.</p>
+
+ <tp:rationale>
+ <p>This combines the state-recoverability of
+ <tp:member-ref>ConnectionStatusReason</tp:member-ref> with the
+ extensibility of Connection.ConnectionError.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="CurrentPresence" type="(uss)" access="read"
+ tp:type="Simple_Presence" tp:name-for-bindings="Current_Presence">
+ <tp:docstring>
+ The actual presence. If the connection is not online, the
+ <tp:type>Connection_Presence_Type</tp:type> SHOULD be
+ Connection_Presence_Type_Offline.
+ If the connection is online but does not support the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">SimplePresence</tp:dbus-ref>
+ interface, the type SHOULD be Connection_Presence_Type_Unset.
+ The account manager is expected to set this by observing signals
+ from the Connection.
+ </tp:docstring>
+ </property>
+
+ <property name="RequestedPresence" type="(uss)" access="readwrite"
+ tp:type="Simple_Presence" tp:name-for-bindings="Requested_Presence">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The requested presence for this account. When this is changed, the
+ account manager should attempt to manipulate the connection manager to
+ make <tp:member-ref>CurrentPresence</tp:member-ref> match
+ <tp:member-ref>RequestedPresence</tp:member-ref> as closely as
+ possible. It should not be saved to any sort of persistent
+ storage.</p>
+
+ <p>When the account manager automatically connects an account,
+ it must signal this by setting the RequestedPresence to the same
+ thing as the <tp:member-ref>AutomaticPresence</tp:member-ref>.</p>
+
+ <p>The <tp:type>Connection_Presence_Type</tp:type> in this property
+ MUST NOT be Unset, Unknown or Error.</p>
+
+ <tp:rationale>
+ <p>Requesting those presence types doesn't make sense.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ChangingPresence" tp:name-for-bindings="Changing_Presence"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, a change to the presence of this account is
+ in progress.</p>
+
+ <p>Whenever <tp:member-ref>RequestedPresence</tp:member-ref> is set on
+ an account that could go online, or whenever an account with a
+ non-offline <tp:member-ref>RequestedPresence</tp:member-ref> becomes
+ able to go online (for instance because
+ <tp:member-ref>Enabled</tp:member-ref> or
+ <tp:member-ref>Valid</tp:member-ref> changes to True),
+ ChangingPresence MUST change to True, and the two property changes MUST
+ be emitted in the same
+ <tp:member-ref>AccountPropertyChanged</tp:member-ref> signal, before the
+ Set method returns.</p>
+
+ <p>When the account manager succeeds or fails in changing the presence,
+ or the connection disconnects due to an error, ChangingPresence MUST
+ change to False as part of the same
+ <tp:member-ref>AccountPropertyChanged</tp:member-ref> signal.</p>
+
+ <tp:rationale>
+ <p>This allows UIs to indicate that a presence change is in progress
+ or has finished, even if the change was initiated by a different
+ UI.</p>
+
+ <p>For instance, Maemo 5 and Empathy indicate a presence change by
+ having the presence indicator alternate between the
+ <tp:member-ref>RequestedPresence</tp:member-ref>
+ and the <tp:member-ref>CurrentPresence</tp:member-ref>; they should
+ start blinking when ChangingPresence becomes true, and stop when it
+ becomes false.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </property>
+
+ <method name="Reconnect" tp:name-for-bindings="Reconnect">
+ <tp:added version="0.17.24"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Re-connect this account. If the account is currently disconnected
+ and the requested presence is offline, or if the account
+ is not <tp:member-ref>Enabled</tp:member-ref> or not
+ <tp:member-ref>Valid</tp:member-ref>, this does nothing.</p>
+
+ <p>If the account is disconnected and the requested presence is not
+ offline, this forces an attempt to connect with the requested
+ presence immediately.</p>
+
+ <p>If the account is connecting or connected, this is equivalent to
+ remembering the current value of
+ <tp:member-ref>RequestedPresence</tp:member-ref>, setting its value
+ to (OFFLINE, "offline", ""), waiting for the change to take effect,
+ then setting its value to the value that was previously
+ remembered.</p>
+
+ <tp:rationale>
+ <p>Clients desiring "instant apply" semantics for CM parameters MAY
+ call this method to achieve that.</p>
+ </tp:rationale>
+
+ <p>In particular, if the account's
+ <tp:member-ref>Connection</tp:member-ref> is in the Connecting
+ state, calling this method causes the attempt to connect to be
+ aborted and re-tried.</p>
+
+ <tp:rationale>
+ <p>This is necessary to ensure that the new parameters are
+ picked up.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <property name="NormalizedName" type="s" access="read"
+ tp:name-for-bindings="Normalized_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The normalized user ID of the local user on this account (i.e. the
+ string returned when the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>
+ method is called on the
+ result of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">GetSelfHandle</tp:dbus-ref>
+ for an active connection).</p>
+
+ <p>It is unspecified whether this user ID is globally unique.</p>
+
+ <tp:rationale>
+ <p>As currently implemented, IRC user IDs are only unique within
+ the same IRCnet. On some saner protocols, the user ID includes a
+ DNS name which provides global uniqueness.</p>
+ </tp:rationale>
+
+ <p>If this value is not known yet (which will always be the case for
+ accounts that have never been online), it will be an empty
+ string.</p>
+
+ <p>It is possible that this value will change if the connection
+ manager's normalization algorithm changes, although this SHOULD
+ be avoided.</p>
+
+ <tp:rationale>
+ <p>It's not always completely clear what normalization algorithm
+ should be used; for instance, in Gabble, we currently use JIDs,
+ but it would also have been reasonable to use xmpp URIs.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="HasBeenOnline" tp:name-for-bindings="Has_Been_Online"
+ type="b" access="read">
+ <tp:docstring>
+ If true, this account has successfully been put online at some point
+ in the past.
+
+ <tp:rationale>
+ UIs could apply a policy that the 'account' parameter can only be
+ edited in accounts that have never been online, or that
+ ConnectAutomatically cannot be set on such accounts. The account
+ manager should not enforce such policies, but it can expose enough
+ information to UIs that the UI can decide what to do.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Account_Interface_Addressing.xml b/qt4/spec/Account_Interface_Addressing.xml
new file mode 100644
index 000000000..4b2846b68
--- /dev/null
+++ b/qt4/spec/Account_Interface_Addressing.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" ?>
+<node name="/Account_Interface_Addressing" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Ltd</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Account.Interface.Addressing">
+ <tp:requires interface="org.freedesktop.Telepathy.Account"/>
+ <tp:added version="0.21.5">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Some accounts can be used for multiple protocols; for instance, SIP
+ and Skype accounts can often be used to contact the PSTN, MSN and
+ Yahoo accounts can contact each other, and XMPP accounts can
+ potentially contact many protocols via a transport.</p>
+ <p>However, if the user does not intend to make use of this functionality,
+ user interfaces can improve clarity by not displaying it: for instance,
+ if a user prefers to call phone numbers via a particular SIP account,
+ when an address book displays a contact with a phone number, it is
+ desirable to display a "call with SIP" button for that account, but
+ avoid displaying similar buttons for any other configured SIP or
+ Skype accounts.</p>
+ <p>The purpose of this interface is to allow this "for use with" information
+ to be recorded and retrieved.</p>
+ </tp:docstring>
+
+ <property name="URISchemes" type="as" access="read"
+ tp:name-for-bindings="URI_Schemes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of fields indicating the type of URI addressing scheme
+ the the account should be used for (eg 'tel') indicating the
+ account is intended for use by applications offering a telephony
+ UI, or 'sip' or 'xmpp' for those protocols</p>
+ <p>Note that these fields signify intent, not ability: It is
+ entirely possible that an account which can be used for a
+ given URI scheme is not wanted for it by the user, and
+ therefore not flagged as such in this field.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="SetURISchemeAssociation"
+ tp:name-for-bindings="Set_URI_Scheme_Association">
+ <tp:docstring>
+ <p>Associate (or disassociate) an account with a particular
+ URI addressing scheme, (such as 'tel' for telephony)</p>
+ </tp:docstring>
+
+ <arg name="URI_Scheme" direction="in" type="s">
+ <tp:docstring>
+ <p>URI scheme to associate/disassociate the account with/from</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Association" direction="in" type="b">
+ <tp:docstring>
+ <p>True to associate this account with a given addressing scheme</p>
+ <p>False if the account should not be associated with said scheme</p>
+ </tp:docstring>
+ </arg>
+
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Account_Interface_Avatar.xml b/qt4/spec/Account_Interface_Avatar.xml
new file mode 100644
index 000000000..a8b78c8a2
--- /dev/null
+++ b/qt4/spec/Account_Interface_Avatar.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" ?>
+<node name="/Account_Interface_Avatar"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2008 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Account.Interface.Avatar">
+ <tp:requires interface="org.freedesktop.Telepathy.Account"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface extends the core Account interface to provide a
+ user-settable avatar image.</p>
+
+ <tp:rationale>
+ <p>The avatar could have been a property on the core Account interface,
+ but was moved to a separate interface because it is likely to be
+ large. This means that clients can safely use GetAll to get
+ properties on the core Account interface without flooding the
+ session bus with large images.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ <tp:added version="0.17.6"/>
+
+ <tp:struct name="Avatar">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A struct containing avatar data marked with its MIME type.</p>
+ </tp:docstring>
+ <tp:member type="ay" name="Avatar_Data"/>
+ <tp:member type="s" name="MIME_Type"/>
+ </tp:struct>
+
+ <property name="Avatar" tp:name-for-bindings="Avatar"
+ type="(ays)" tp:type="Avatar" access="readwrite">
+ <tp:docstring>
+ The avatar to set on this account for display to other contacts,
+ represented as a structure containing the bytes of the avatar,
+ and the MIME type as a string; may be set to an empty byte-array and
+ an empty string to indicate no avatar. When the account becomes
+ connected, the account manager SHOULD set this avatar using SetAvatar
+ if appropriate.
+ </tp:docstring>
+ </property>
+
+ <signal name="AvatarChanged" tp:name-for-bindings="Avatar_Changed">
+ <tp:docstring>
+ Emitted when the Avatar property changes.
+
+ <tp:rationale>The avatar itself is deliberately not included in this
+ signal, to reduce bus traffic in the (likely common) case where no
+ running application cares about the user's own avatar.</tp:rationale>
+ </tp:docstring>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Account_Interface_External_Password_Storage.xml b/qt4/spec/Account_Interface_External_Password_Storage.xml
new file mode 100644
index 000000000..5bd1bfce0
--- /dev/null
+++ b/qt4/spec/Account_Interface_External_Password_Storage.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" ?>
+<node name="/Account_Interface_External_Password_Storage"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2011 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Account.Interface.ExternalPasswordStorage.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.10">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Account"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for Accounts whose passwords are stored externally and
+ SHOULD NOT be stored by either the
+ <tp:dbus-ref namespace="ofdT">AccountManager</tp:dbus-ref> nor any
+ <tp:dbus-ref namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ handler.</p>
+
+ <p>This interface SHOULD only appear on accounts for which the
+ related Connection Manager implements
+ <tp:dbus-ref namespace="ofdT">ConnectionManager.Interface.AccountStorage.DRAFT</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <method name="ForgetPassword" tp:name-for-bindings="Forget_Password">
+ <tp:docstring>
+ Clears any saved password associated with this account.
+ </tp:docstring>
+ </method>
+
+ <property name="PasswordSaved"
+ tp:name-for-bindings="Password_Saved"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Indicates whether the account has a saved password or not.</p>
+
+ <p>Change notification for this property is provided by the
+ standard D-Bus <code>PropertiesChanged</code> signal.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
diff --git a/qt4/spec/Account_Interface_Hidden.xml b/qt4/spec/Account_Interface_Hidden.xml
new file mode 100644
index 000000000..cb0019178
--- /dev/null
+++ b/qt4/spec/Account_Interface_Hidden.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" ?>
+<node name="/Account_Interface_Hidden"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Account.Interface.Hidden.DRAFT1"
+ tp:causes-havoc="outrageous">
+ <tp:added version="0.21.10">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for flagging certain accounts as hidden, so that they do
+ not appear in the account manager's standard lists of accounts.
+ Accounts whose <tp:member-ref>Hidden</tp:member-ref> property is
+ <code>True</code> are intended for non-interactive use (by
+ non-user-visible services), and appear on the <tp:dbus-ref
+ namespace='ofdT'>AccountManager.Interface.Hidden.DRAFT1</tp:dbus-ref>
+ interface; in all other respects, they behave like any other
+ account.</p>
+
+ <tp:rationale>
+ <p>XMPP, in particular, is increasingly used for purposes other than
+ instant messaging and VoIP. For instance, extensions exist for
+ inter-device bookmark synchronization.</p>
+
+ <p>While obviously these services could re-use connections intended for
+ instant messaging, in some cases you might want to use a different
+ account. (Perhaps your bookmark sync provider is not your IM
+ provider.) This API allows such auxiliary accounts to exist in
+ Telepathy, while not being displayed in standard user interfaces for
+ IM, VoIP, and friends.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <property name="Hidden" tp:name-for-bindings="Hidden"
+ type="b" access="read" tp:immutable='aye'>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <code>True</code>, this account is intended for non-interactive
+ use, and thus should not be presented to the user. It will not appear
+ in properties and signals on the main <tp:dbus-ref
+ namespace='ofdT'>AccountManager</tp:dbus-ref> interface; instead, it
+ will show up on <tp:dbus-ref
+ namespace='ofdT'>AccountManager.Interface.Hidden.DRAFT1</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Account_Interface_Minimum_Presence.xml b/qt4/spec/Account_Interface_Minimum_Presence.xml
new file mode 100644
index 000000000..eb829b824
--- /dev/null
+++ b/qt4/spec/Account_Interface_Minimum_Presence.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" ?>
+<node name="/Account_Interface_Minimum_Presence"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Account.Interface.MinimumPresence.DRAFT2"
+ tp:causes-havoc="experimental">
+ <tp:requires interface="org.freedesktop.Telepathy.Account"/>
+ <tp:added version="0.19.12">(draft 2)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface extends the core Account interface to provide a way
+ for applications to request minimum presence on the account.</p>
+
+ <tp:rationale>
+ <p>Some applications, for example mail notifiers or address book
+ synchronisation, can make use of account's connection even while
+ the user is nominally offline.</p>
+ </tp:rationale>
+
+ <p>Each client's unique name may set a minimum desired presence on the
+ account. The combined presence is the most available presence
+ of the minimum presences set and of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">RequestedPresence</tp:dbus-ref>
+ set by the user. The account manager should attempt to manipulate
+ the connection to set the combined presence.</p>
+ </tp:docstring>
+
+ <property name="MinimumPresenceRequests"
+ tp:name-for-bindings="MinimumPresenceRequests" access="read"
+ type="a{s(uss)}" tp:type="Minimum_Presence_Request_Map">
+ <tp:docstring>
+ Active requests for minimum presence status, a map of client unique
+ name to the (non-offline) minimum presence they set.
+ </tp:docstring>
+ </property>
+
+ <method name="SetMinimumPresence" tp:name-for-bindings="Set_Minimum_Presence">
+ <tp:docstring>
+ <p>Set a minimum presence needed by the client for this account. Setting
+ (Offline, "offline", "") removes the minimum presence requirement for
+ the client's unique name.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="status" type="(uss)" tp:type="Simple_Presence">
+ <tp:docstring>
+ Requested presence status.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="MinimumPresenceRequestsChanged"
+ tp:name-for-bindings="Minimum_Presence_Requests_Changed">
+ <tp:docstring>
+ Emitted when the
+ <tp:member-ref>MinimumPresenceRequests</tp:member-ref> property
+ changes.
+ </tp:docstring>
+
+ <arg name="MinimumPresenceRequests" type="a{s(uss)}"
+ tp:type="Minimum_Presence_Request_Map">
+ <tp:docstring>
+ A new value of MinimumPresenceRequests property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:mapping name="Minimum_Presence_Request_Map">
+ <tp:docstring>
+ <p>A map of active minimum presence requests.</p>
+ </tp:docstring>
+ <tp:member type="s" name="Key" tp:type="DBus_Unique_Name">
+ <tp:docstring>
+ <p>Client unique name.</p>
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="(uss)" name="Value" tp:type="Simple_Presence">
+ <tp:docstring>
+ <p>Requested minimum presence.</p>
+
+ <tp:rationale>
+ <p>Some applications may want to monitor the currently active
+ minimum presences required. An example is an tool allowing
+ the user to inspect applications maintaining open connections and
+ close those applications.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Account_Interface_Storage.xml b/qt4/spec/Account_Interface_Storage.xml
new file mode 100644
index 000000000..4e3ba5dca
--- /dev/null
+++ b/qt4/spec/Account_Interface_Storage.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" ?>
+<node name="/Account_Interface_Storage"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Account.Interface.Storage">
+ <tp:requires interface="org.freedesktop.Telepathy.Account"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ This interface extends the core Account interface to specify details
+ regarding the storage of this account.
+ </p>
+
+ <tp:rationale>
+ <p>
+ Single-sign-on systems do not generally have directly user-editable
+ properties for Accounts, and require the user to visit a specific UI
+ to alter their account properties. User interfaces should know not to
+ expose these account properties as user-editable, and instead
+ redirect the user to the appropriate interface.
+ </p>
+ </tp:rationale>
+
+ </tp:docstring>
+ <tp:added version="0.19.8"/>
+
+ <property name="StorageProvider" tp:name-for-bindings="Storage_Provider"
+ type="s" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The name of the account storage implementation, which SHOULD start
+ with a reversed domain name in the same way as D-Bus interface names.
+ When this is the empty string the account is internally stored.
+ </p>
+ <p>
+ This property cannot change once an Account has been created.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="StorageIdentifier"
+ tp:name-for-bindings="Storage_Identifier" type="v" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Unique identification of the account within the storage backend.
+ The contents of the variant are defined by the
+ <tp:member-ref>StorageProvider</tp:member-ref>.
+ </p>
+ <p>
+ This property cannot change once an Account has been created.
+ </p>
+ <tp:rationale>
+ <p>
+ Different storage systems will have their own way of uniquely
+ identifying an account, typically an integer or a string.
+ Given that all users of this property should have direct knowledge
+ of the backend they should know what types to expect and how to
+ handle it.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="StorageSpecificInformation"
+ tp:name-for-bindings="Storage_Specific_Information" type="a{sv}"
+ access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Map containing information specific to the storage backend. The keys
+ and the types of their values are defined by the
+ <tp:member-ref>StorageProvider</tp:member-ref>, and are not
+ interpreted by the AccountManager implementation.
+ </p>
+ <p>
+ As the values in this map may change at any time (due to an external
+ application manipulating the storage provider directly), this
+ property should not be cached; it should instead be retrieved each
+ time it is needed.
+ </p>
+
+ <tp:rationale>
+ <p>
+ This can be used to provide additional hints to user interfaces
+ aware of a specific storage provider, without requiring those user
+ interfaces to use the
+ <tp:member-ref>StorageIdentifier</tp:member-ref> to query the
+ storage provider directly.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="StorageRestrictions"
+ tp:name-for-bindings="Storage_Restrictions" type="u"
+ tp:type="Storage_Restriction_Flags"
+ access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Bitfield which defines what restrictions this Storage method has.
+ </p>
+ <p>
+ This property cannot change once an Account has been created.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <tp:flags name="Storage_Restriction_Flags"
+ value-prefix="Storage_Restriction_Flag" type="u">
+ <tp:docstring>
+ Flags indicating restrictions imposed on an Account by its storage
+ method.
+ </tp:docstring>
+
+ <tp:flag suffix="Cannot_Set_Parameters" value="1">
+ <tp:docstring>
+ The account's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account"
+ >Parameters</tp:dbus-ref> property can't be changed by calling
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Account"
+ >UpdateParameters</tp:dbus-ref>.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Cannot_Set_Enabled" value="2">
+ <tp:docstring>
+ The account can't be enabled/disabled by setting the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account"
+ >Enabled</tp:dbus-ref> property.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Cannot_Set_Presence" value="4">
+ <tp:docstring>
+ The account's presence can't be changed by setting the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account"
+ >RequestedPresence</tp:dbus-ref> and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account"
+ >AutomaticPresence</tp:dbus-ref> properties.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Cannot_Set_Service" value="8">
+ <tp:docstring>
+ The account's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Service</tp:dbus-ref>
+ property cannot be changed.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Account_Manager.xml b/qt4/spec/Account_Manager.xml
new file mode 100644
index 000000000..cd82e7f53
--- /dev/null
+++ b/qt4/spec/Account_Manager.xml
@@ -0,0 +1,296 @@
+<?xml version="1.0" ?>
+<node name="/Account_Manager"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.AccountManager">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The account manager is a central service used to store account
+ details.</p>
+
+ <p>The current account manager is defined to be the process that owns
+ the well-known bus name org.freedesktop.Telepathy.AccountManager on
+ the session bus. This process must export an
+ /org/freedesktop/Telepathy/AccountManager object with the
+ AccountManager interface.</p>
+
+ <p>Until a mechanism exists for making a reasonable automatic choice
+ of AccountManager implementation, implementations SHOULD NOT
+ register as an activatable service for the AccountManager's
+ well-known bus name. Instead, it is RECOMMENDED that some component
+ of the user's session will select and activate a particular
+ implementation, and that other Telepathy-enabled programs can
+ detect whether Telepathy is in use by checking whether the
+ AccountManager's well-known name is in use at runtime.</p>
+ </tp:docstring>
+ <tp:added version="0.17.2"/>
+
+ <!-- Missing functionality compared with NMC 4.x:
+ * look up accounts by conditions (can be done client-side, less
+ efficiently, so not a blocker)
+ * global presence/... changes (can be done client-side, less efficiently -
+ we should add this)
+ * count used channels (what's this for?)
+ * get "average" status (not well-defined, UIs can do this)
+ * request channels (out of scope: Channel Dispatcher will do this)
+ * register filters (completely out of scope: Channel Dispatcher again)
+ -->
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read">
+ <tp:docstring>
+ A list of the interfaces provided by the account manager object.
+ </tp:docstring>
+ </property>
+
+ <property name="ValidAccounts" type="ao" access="read"
+ tp:name-for-bindings="Valid_Accounts">
+ <tp:docstring>
+ A list of the valid (complete, usable) <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>s. Change
+ notification is via
+ <tp:member-ref>AccountValidityChanged</tp:member-ref>.
+
+ <tp:rationale>
+ This split between valid and invalid accounts makes it easy to
+ ignore the invalid ones. The only things that should be manipulating
+ invalid accounts are account-editing UIs, which might be able to
+ rescue them.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InvalidAccounts" type="ao" access="read"
+ tp:name-for-bindings="Invalid_Accounts">
+ <tp:docstring>
+ A list of incomplete or otherwise unusable <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>s. Change
+ notification is via
+ <tp:member-ref>AccountValidityChanged</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <signal name="AccountRemoved" tp:name-for-bindings="Account_Removed">
+ <tp:docstring>
+ The given account has been removed.
+
+ <tp:rationale>
+ This is effectively change notification for the valid and invalid
+ accounts lists. On emission of this signal, the Account indicated
+ will no longer be present in either of the lists.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Account" type="o">
+ <tp:docstring>
+ An Account, which must not be used any more.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="AccountValidityChanged"
+ tp:name-for-bindings="Account_Validity_Changed">
+ <tp:docstring>
+ The validity of the given account has changed. New accounts are
+ also indicated by this signal, as an account validity change
+ (usually to True) on an account that did not previously exist.
+
+ <tp:rationale>
+ This is effectively change notification for the valid and invalid
+ accounts lists.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Account" type="o">
+ <tp:docstring>
+ An <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Valid" type="b">
+ <tp:docstring>
+ True if the account is now valid.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="SupportedAccountProperties"
+ tp:name-for-bindings="Supported_Account_Properties"
+ type="as" tp:type="DBus_Qualified_Member[]" access="read">
+ <tp:added version="0.17.24"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of the fully qualified names of properties that can be set
+ via the Properties argument to
+ <tp:member-ref>CreateAccount</tp:member-ref> when an account is
+ created.</p>
+
+ <tp:rationale>
+ <p>Examples of good properties to support here include
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Icon</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Enabled</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Nickname</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">AutomaticPresence</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">ConnectAutomatically</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">RequestedPresence</tp:dbus-ref>
+ and
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account.Interface.Avatar">Avatar</tp:dbus-ref>.
+ </p>
+
+ <p>Examples of properties that would make no sense here include
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Valid</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Connection</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">ConnectionStatus</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">ConnectionStatusReason</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">CurrentPresence</tp:dbus-ref>
+ and
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">NormalizedName</tp:dbus-ref>.
+ </p>
+ </tp:rationale>
+
+ <p>This property MUST NOT include include the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">DisplayName</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Parameters</tp:dbus-ref>
+ properties, which are set using separate arguments.</p>
+
+ <p>This property MAY include the names of properties that, after
+ account creation, will be read-only: this indicates that the property
+ can be set at account creation but not changed later.</p>
+
+ <tp:rationale>
+ <p>For example, an account manager might support migration tools that
+ use this to preserve the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">HasBeenOnline</tp:dbus-ref>
+ property, even though that property is usually read-only.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <method name="CreateAccount" tp:name-for-bindings="Create_Account">
+ <tp:docstring>
+ Request the creation of a new <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>. The
+ account manager SHOULD NOT allow invalid accounts to be created.
+ </tp:docstring>
+ <tp:changed version="0.17.24">added the Properties argument</tp:changed>
+
+ <arg name="Connection_Manager" direction="in" type="s"
+ tp:type="Connection_Manager_Name">
+ <tp:docstring>
+ The name of the connection manager, e.g. "salut".
+ </tp:docstring>
+ </arg>
+
+ <arg name="Protocol" direction="in" type="s"
+ tp:type="Protocol">
+ <tp:docstring>The protocol, e.g. "local-xmpp".</tp:docstring>
+ </arg>
+
+ <arg name="Display_Name" direction="in" type="s">
+ <tp:docstring>The initial value of the new account's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">DisplayName</tp:dbus-ref>
+ property. The account manager SHOULD modify this to make it unique if
+ an Account already exists with the same display name, for instance by
+ appending a number or the 'account' parameter. Account manager
+ implementations SHOULD accept an empty string, but account editing
+ user interfaces should avoid passing an empty string for this
+ parameter.
+
+ <tp:rationale>
+ <p>The account creation UI may ask the user for a name for the new
+ account. If the author of the UI chooses not to do this, the
+ account creation UI is better able to suggest a default display
+ name because it has protocol-specific knowledge which the account
+ manager does not.</p>
+
+ <p>The account manager always knows the complete list of accounts so
+ it can easily tell whether it should append something to the
+ display name to avoid presenting two identically-named accounts to
+ the user.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Parameters" direction="in" type="a{sv}">
+ <tp:docstring>Initial parameter values, as would be passed to
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ConnectionManager">RequestConnection</tp:dbus-ref>.</tp:docstring>
+ </arg>
+
+ <arg name="Properties" direction="in" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The values of any other properties to be set immediately on the
+ new Account.</p>
+
+ <p>Only the properties mentioned in
+ <tp:member-ref>SupportedAccountProperties</tp:member-ref> are
+ acceptable here. In particular, the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">DisplayName</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Parameters</tp:dbus-ref>
+ properties are never allowed here, since they are set using the other
+ arguments to this method.</p>
+
+ <p>Account manager implementations SHOULD support creating accounts
+ with an empty value for this argument.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Account" direction="out" type="o">
+ <tp:docstring>The new <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The Connection_Manager is not installed or does not
+ implement the given Protocol, or one of the properties in the
+ Properties argument is unsupported.</p>
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The Parameters provided omit a required parameter
+ or provide unsupported parameter, or the type of one
+ of the Parameters or Properties is inappropriate.</p>
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
+
diff --git a/qt4/spec/Account_Manager_Interface_Hidden.xml b/qt4/spec/Account_Manager_Interface_Hidden.xml
new file mode 100644
index 000000000..284eb6428
--- /dev/null
+++ b/qt4/spec/Account_Manager_Interface_Hidden.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" ?>
+<node name="/Account_Manager_Interface_Hidden"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.AccountManager.Interface.Hidden.DRAFT1"
+ tp:causes-havoc='kind of sketchy'>
+ <tp:requires interface='org.freedesktop.Telepathy.AccountManager'/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface lists accounts whose <tp:dbus-ref
+ namespace='ofdT.Account.Interface.Hidden.DRAFT1'>Hidden</tp:dbus-ref>
+ property is <code>True</code>.</p>
+ </tp:docstring>
+ <tp:added version="0.21.10">first draft</tp:added>
+
+ <property name="ValidHiddenAccounts" type="ao" access="read"
+ tp:name-for-bindings="Valid_Hidden_Accounts">
+ <tp:docstring>
+ A list of valid (complete, usable) <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>s intended
+ exclusively for noninteractive applications. These accounts are not
+ included in <tp:dbus-ref
+ namespace='ofdT'>AccountManager.ValidAccounts</tp:dbus-ref>. Change
+ notification is via
+ <tp:member-ref>HiddenAccountValidityChanged</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <property name="InvalidHiddenAccounts" type="ao" access="read"
+ tp:name-for-bindings="Invalid_Hidden_Accounts">
+ <tp:docstring>
+ A list of incomplete or otherwise unusable <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>s intended
+ exclusively for noninteractive applications. Change notification is via
+ <tp:member-ref>HiddenAccountValidityChanged</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <signal name="HiddenAccountRemoved"
+ tp:name-for-bindings="Hidden_Account_Removed">
+ <tp:docstring>
+ The given account has been removed from
+ <tp:member-ref>ValidHiddenAccounts</tp:member-ref> or
+ <tp:member-ref>InvalidHiddenAccounts</tp:member-ref>.
+ </tp:docstring>
+
+ <arg name="Account" type="o">
+ <tp:docstring>
+ An Account, which must not be used any more.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="HiddenAccountValidityChanged"
+ tp:name-for-bindings="Hidden_Account_Validity_Changed">
+ <tp:docstring>
+ The validity of the given account has changed. New magic
+ accounts are also indicated by this signal, as an account validity
+ change (usually to True) on an account that did not previously exist.
+
+ <tp:rationale>
+ This is effectively change notification for the valid and invalid
+ accounts lists.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Account" type="o">
+ <tp:docstring>
+ An <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Valid" type="b">
+ <tp:docstring>
+ True if the account is now valid.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Authentication_TLS_Certificate.xml b/qt4/spec/Authentication_TLS_Certificate.xml
new file mode 100644
index 000000000..db1d76fd7
--- /dev/null
+++ b/qt4/spec/Authentication_TLS_Certificate.xml
@@ -0,0 +1,305 @@
+<?xml version="1.0" ?>
+<node name="/Authentication_TLS_Certificate" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Limited</tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Authentication.TLSCertificate">
+ <tp:added version="0.19.13">(as stable API)</tp:added>
+
+ <tp:docstring>
+ This object represents a TLS certificate.
+ </tp:docstring>
+
+ <tp:simple-type name="Certificate_Data" array-name="Certificate_Data_List"
+ type="ay">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The raw data contained in a TLS certificate.</p>
+
+ <p>For X.509 certificates (<tp:member-ref>CertificateType</tp:member-ref>
+ = "x509"), this MUST be in DER format, as defined by the
+ <a href="http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf">X.690</a>
+ ITU standard.</p>
+
+ <p>For PGP certificates (<tp:member-ref>CertificateType</tp:member-ref>
+ = "pgp"), this MUST be a binary OpenPGP key as defined by section 11.1
+ of <a href="http://www.rfc-editor.org/rfc/4880.txt">RFC 4880</a>.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:struct name="TLS_Certificate_Rejection" array-name="TLS_Certificate_Rejection_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Struct representing one reason why a TLS certificate was rejected.</p>
+ <p>Since there can be multiple things wrong with a TLS certificate,
+ arrays of this type are used to represent lists of reasons for
+ rejection. In that case, the most important reason SHOULD be placed
+ first in the list.</p>
+ </tp:docstring>
+
+ <tp:member name="Reason" type="u"
+ tp:type="TLS_Certificate_Reject_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The value of the TLS_Certificate_Reject_Reason enumeration for
+ this certificate rejection.
+ <tp:rationale>
+ Clients that do not understand the <code>Error</code> member,
+ which may be implementation-specific, can use this property to
+ classify rejection reasons into common categories.
+ </tp:rationale>
+ </p>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Error" type="s"
+ tp:type="DBus_Error_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The DBus error name for this certificate rejection.</p>
+ <p>This MAY correspond to the value of the <code>Reason</code> member,
+ or MAY be a more specific D-Bus error name, perhaps implementation-specific.</p>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Details" type="a{sv}"
+ tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Additional information about why the certificate was rejected.
+ This MAY also include one or more of the following well-known keys:</p>
+ <p>
+ <dl>
+ <dt>user-requested (b)</dt>
+ <dd>True if the error was due to an user-requested rejection of
+ the certificate; False if there was an unrecoverable error in the
+ verification process.</dd>
+ <dt>expected-hostname (s)</dt>
+ <dd>If the rejection reason is Hostname_Mismatch, the hostname that
+ the server certificate was expected to have.</dd>
+ <dt>certificate-hostname (s)</dt>
+ <dd>If the rejection reason is Hostname_Mismatch, the hostname of
+ the certificate that was presented.
+ <tp:rationale>
+ <p>For instance, if you try to connect to gmail.com but are presented
+ with a TLS certificate issued to evil.example.org, the error details
+ for Hostname_Mismatch MAY include:</p>
+ <pre>
+ {
+ 'expected-hostname': 'gmail.com',
+ 'certificate-hostname': 'evil.example.org',
+ }
+ </pre>
+ </tp:rationale>
+ </dd>
+ <dt>debug-message (s)</dt>
+ <dd>Debugging information on the error, corresponding to the
+ message part of a D-Bus error message, which SHOULD NOT be
+ displayed to users under normal circumstances</dd>
+ </dl>
+ </p>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:enum type="u" name="TLS_Certificate_State">
+ <tp:docstring>
+ The possible states for a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Authentication">TLSCertificate</tp:dbus-ref>
+ object.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Pending" value="0">
+ <tp:docstring>
+ The certificate is currently waiting to be accepted or rejected.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Accepted" value="1">
+ <tp:docstring>
+ The certificate has been verified.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Rejected" value="2">
+ <tp:docstring>
+ The certificate has been rejected.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum type="u" name="TLS_Certificate_Reject_Reason">
+ <tp:docstring>
+ Possible reasons to reject a TLS certificate.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ The certificate has been rejected for another reason
+ not listed in this enumeration.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Untrusted" value="1">
+ <tp:docstring>
+ The certificate is not trusted.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Expired" value="2">
+ <tp:docstring>
+ The certificate is expired.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Not_Activated" value="3">
+ <tp:docstring>
+ The certificate is not active yet.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Fingerprint_Mismatch" value="4">
+ <tp:docstring>
+ The certificate provided does not have the expected
+ fingerprint.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Hostname_Mismatch" value="5">
+ <tp:docstring>
+ The hostname certified does not match the provided one.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Self_Signed" value="6">
+ <tp:docstring>
+ The certificate is self-signed.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Revoked" value="7">
+ <tp:docstring>
+ The certificate has been revoked.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Insecure" value="8">
+ <tp:docstring>
+ The certificate uses an insecure cipher algorithm, or is
+ cryptographically weak.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Limit_Exceeded" value="9">
+ <tp:docstring>
+ The length in bytes of the certificate, or the depth of the
+ certificate chain exceed the limits imposed by the crypto
+ library.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="State" type="u" access="read"
+ tp:type="TLS_Certificate_State"
+ tp:name-for-bindings="State">
+ <tp:docstring>
+ The current state of this certificate.
+ State change notifications happen by means of the
+ <tp:member-ref>Accepted</tp:member-ref> and
+ <tp:member-ref>Rejected</tp:member-ref> signals.
+ </tp:docstring>
+ </property>
+
+ <property name="Rejections" type="a(usa{sv})" access="read"
+ tp:type="TLS_Certificate_Rejection[]" tp:name-for-bindings="Rejections">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If the <tp:member-ref>State</tp:member-ref> is Rejected,
+ an array of <tp:type>TLS_Certificate_Rejection</tp:type>
+ structures containing the reason why the certificate is rejected.</p>
+ <p>If the <tp:member-ref>State</tp:member-ref> is not Rejected,
+ this property is not meaningful, and SHOULD be set to an empty
+ array.</p>
+ <p>The first rejection in the list MAY be assumed to be
+ the most important; if the array contains more than one
+ element, the CM MAY either use the values after the first,
+ or ignore them.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CertificateType" type="s" access="read"
+ tp:name-for-bindings="Certificate_Type">
+ <tp:docstring>
+ The type of this TLS certificate (e.g. 'x509' or 'pgp').
+ <p>This property is immutable</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CertificateChainData" type="aay" access="read"
+ tp:type="Certificate_Data[]" tp:name-for-bindings="Certificate_Chain_Data">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>One or more TLS certificates forming a trust chain, each encoded as
+ specified by <tp:type>Certificate_Data</tp:type>.</p>
+ <p>The first certificate in the chain MUST be the server certificate,
+ followed by the issuer's certificate, followed by the issuer's issuer
+ and so on.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="Accepted"
+ tp:name-for-bindings="Accepted">
+ <tp:docstring>
+ The <tp:member-ref>State</tp:member-ref> of this certificate has changed to Accepted.
+ </tp:docstring>
+ </signal>
+
+ <signal name="Rejected"
+ tp:name-for-bindings="Rejected">
+ <tp:docstring>
+ The <tp:member-ref>State</tp:member-ref> of this certificate has changed to Rejected.
+ </tp:docstring>
+ <arg name="Rejections" type="a(usa{sv})" tp:type="TLS_Certificate_Rejection[]">
+ <tp:docstring>
+ The new value of the <tp:member-ref>Rejections</tp:member-ref> property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <tp:docstring>
+ Accepts this certificate, i.e. marks it as verified.
+ </tp:docstring>
+ </method>
+
+ <method name="Reject" tp:name-for-bindings="Reject">
+ <tp:docstring>
+ Rejects this certificate.
+ </tp:docstring>
+ <arg direction="in" type="a(usa{sv})" name="Rejections"
+ tp:type="TLS_Certificate_Rejection[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The new value of the <tp:member-ref>Rejections</tp:member-ref> property.</p>
+ <p>This MUST NOT be an empty array.</p>
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ Raised when the method is called on an object whose <tp:member-ref>State</tp:member-ref>
+ is not <code>Pending</code>, or when the provided rejection list is empty.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Content.xml b/qt4/spec/Call_Content.xml
new file mode 100644
index 000000000..270d99b08
--- /dev/null
+++ b/qt4/spec/Call_Content.xml
@@ -0,0 +1,255 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents one Content inside a <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>. For
+ example, in an audio/video call there would be one audio content
+ and one video content. Each content has one or more <tp:dbus-ref
+ namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref> objects which
+ represent the actual transport to one or more remote contacts.
+ </tp:docstring>
+
+ <tp:enum name="Content_Removal_Reason" type="u">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ A representation of the reason for a content to be removed,
+ which may be used by simple clients, or used as a fallback
+ when the DBus_Reason is not understood. This enum will be
+ extended with future reasons as and when appropriate, so
+ clients SHOULD keep up to date with its values, but also be
+ happy to fallback to the Unknown value when an unknown value
+ is encountered.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ We just don't know. Unknown values of this enum SHOULD also be
+ treated like this.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="User_Requested" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The local user requests that this content is removed
+ from the call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Error" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There is an error with the content which means that it
+ has to be removed from the call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Unsupported" value="3">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Some aspect of the content is unsupported so has to be
+ removed from the call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <method name="Remove" tp:name-for-bindings="Remove">
+ <tp:changed version="0.21.2">previously there were no
+ arguments</tp:changed>
+ <tp:docstring>
+ Remove the content from the call.
+ </tp:docstring>
+
+ <arg direction="in" name="Reason" type="u"
+ tp:type="Content_Removal_Reason">
+ <tp:docstring>
+ A generic hangup reason.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Detailed_Removal_Reason" type="s"
+ tp:type="DBus_Error_Name">
+ <tp:docstring>
+ A more specific reason for the content removal, if one is
+ available, or an empty string.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A human-readable message for the reason of removing the
+ content, such as "Fatal streaming failure" or "no codec
+ intersection". This property can be left empty if no reason
+ is to be given.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError" />
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised when a Call doesn't support removing contents
+ (e.g. a Google Talk video call).
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="Removed" tp:name-for-bindings="Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the content is removed from the call. This
+ is the same as the <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT.ContentRemoved</tp:dbus-ref>
+ signal.</p>
+ </tp:docstring>
+ </signal>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes">
+ <tp:added version="0.19.11"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this content, such as <tp:dbus-ref
+ namespace="ofdT.Call">Content.Interface.Media.DRAFT</tp:dbus-ref> or
+ <tp:dbus-ref namespace="ofdT.Call">Content.Interface.Mute.DRAFT</tp:dbus-ref>.
+ This SHOULD NOT include the Content interface itself, and cannot
+ change once the content has been created.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Name" tp:name-for-bindings="Name" type="s" access="read"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the content.</p>
+
+ <tp:rationale>
+ The content name property should be meaningful, so should be
+ given a name which is significant to the user. The name
+ could be the "audio" or "video" string localized, or perhaps
+ include some string identifying the source, such as a webcam
+ identifier.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Type" tp:name-for-bindings="Type"
+ type="u" tp:type="Media_Stream_Type" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The media type of this content.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_Content_Disposition" type="u">
+ <tp:docstring>
+ The disposition of this content, which defines whether to
+ automatically start sending data on the streams when
+ <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> is
+ called on the channel.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The content has no specific disposition
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Initial" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The content was initially part of the call. When
+ <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref>
+ is called on the channel, all streams of this content with
+ <tp:dbus-ref
+ namespace="ofdT.Call.Stream.DRAFT">LocalSendingState</tp:dbus-ref>
+ set to <tp:type>Sending_State</tp:type>_Pending_Send will be
+ moved to <tp:type>Sending_State</tp:type>_Sending as if
+ <tp:dbus-ref
+ namespace="ofdT.Call.Stream.DRAFT">SetSending</tp:dbus-ref>
+ (True) had been called.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Disposition" tp:name-for-bindings="Disposition"
+ type="u" tp:type="Call_Content_Disposition" access="read"
+ tp:immutable="yes">
+ <tp:docstring>
+ The disposition of this content.
+ </tp:docstring>
+ </property>
+
+ <signal name="StreamsAdded" tp:name-for-bindings="Streams_Added">
+ <tp:changed version="0.21.2">plural version, renamed from
+ StreamAdded</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when streams are added to a call.</p>
+ </tp:docstring>
+ <arg name="Streams" type="ao">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref>s which were
+ added.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="StreamsRemoved" tp:name-for-bindings="Streams_Removed">
+ <tp:changed version="0.21.2">plural version, renamed from
+ StreamRemoved</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when streams are removed from a call</p>
+ </tp:docstring>
+ <arg name="Streams" type="ao">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="ofdT.Call">Stream.DRAFT</tp:dbus-ref>s which were
+ removed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Streams" tp:name-for-bindings="Streams"
+ type="ao" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of <tp:dbus-ref namespace="ofdT.Call"
+ >Stream.DRAFT</tp:dbus-ref> objects that exist in this
+ content.</p>
+
+ <tp:rationale>
+ In a conference call multiple parties can share one media
+ content (say, audio), but the streaming of that media can
+ either be shared or separate. For example, in a multicast
+ conference all contacts would share one stream, while in a
+ Muji conference there would be a stream for each
+ participant.
+ </tp:rationale>
+
+ <p>Change notification is through the
+ <tp:member-ref>StreamsAdded</tp:member-ref> and
+ <tp:member-ref>StreamsRemoved</tp:member-ref> signals.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Content_Codec_Offer.xml b/qt4/spec/Call_Content_Codec_Offer.xml
new file mode 100644
index 000000000..f88143f69
--- /dev/null
+++ b/qt4/spec/Call_Content_Codec_Offer.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Codec_Offer"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This object represents an offer of a Codec payload mapping.
+ </tp:docstring>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <arg name="Codecs" direction="in"
+ type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The local codec mapping to send to the remote contacts and
+ to use in the <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Accept the updated Codec mapping and update the local mapping.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The codecs given as the argument are invalid in some way.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Reject" tp:name-for-bindings="Reject">
+ <tp:docstring>
+ Reject the proposed update to the codecs
+ FIXME add error codes and strings here
+ </tp:docstring>
+ </method>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this codec offer. This SHOULD
+ NOT include the CodecOffer interface itself, and cannot change
+ once the content has been created.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteContactCodecs"
+ tp:name-for-bindings="Remote_Contact_Codecs"
+ type="a(usuua{ss})" tp:type="Codec[]" access="read"
+ tp:immutable="yes">
+ <tp:docstring>
+ A list of codecs the remote contact supports.
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteContact" tp:name-for-bindings="Remote_Contact"
+ type="u" tp:type="Contact_Handle" access="read" tp:immutable="yes">
+ <tp:docstring>
+ The contact handle that this codec offer applies to.
+ </tp:docstring>
+ </property>
+
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Content_Interface_Media.xml b/qt4/spec/Call_Content_Interface_Media.xml
new file mode 100644
index 000000000..038ce8c7a
--- /dev/null
+++ b/qt4/spec/Call_Content_Interface_Media.xml
@@ -0,0 +1,367 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Interface_Media"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface to use by a software implementation of media
+ streaming. The reason behind splitting the members of this
+ interface out from the main <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> interface is
+ that the software is not necessarily what controls the
+ media. An example of this is in GSM phones, where the CM just
+ tells the phone to dial a number and it does the audio routing
+ in a device specific hardware way and the CM does not need
+ to concern itself with codecs.</p>
+
+ <h4>Codec negotiation</h4>
+
+ <p>When a new <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channel
+ appears, whether it was requested or not, a <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ will either be waiting in the
+ <tp:member-ref>CodecOffer</tp:member-ref> property, or will
+ appear at some point via the
+ <tp:member-ref>NewCodecOffer</tp:member-ref> signal.</p>
+
+ <p>The <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ property on the codec offer lists the codecs which are
+ supported by the remote contact, and so will determine the
+ codecs that should be proposed by the local user's streaming
+ implementation. An empty list means all codecs can be proposed.</p>
+
+ <p>For incoming calls on protocols where codecs are proposed when
+ starting the call (for example, <a
+ href="http://xmpp.org/extensions/xep-0166.html">Jingle</a>),
+ the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ will contain information on the codecs that have already been
+ proposed by the remote contact, otherwise the codec map will
+ be the empty list.</p>
+
+ <p>The streaming implementation should look at the remote codec
+ map and the codecs known by the local user and call
+ <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT.Accept</tp:dbus-ref>
+ on the intersection of these two codec lists.</p>
+
+ <p>This means that in practice, outgoing calls will have a codec
+ offer pop up with no information in the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>,
+ so the local user will call <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>
+ with the list of all codecs supported. If this codec offer is
+ accepted, then <tp:member-ref>CodecsChanged</tp:member-ref>
+ will fire with the details of the codecs passed into
+ <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>. If
+ the call is incoming, then the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ will contain details of the remote contact's codecs and the
+ local user will call <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">Accept</tp:dbus-ref>
+ with the codecs that both sides understand. After the codec
+ set is accepted, <tp:member-ref>CodecsChanged</tp:member-ref>
+ will fire to signal this change.</p>
+
+ <h4>Protocols without codec negotiation</h4>
+
+ <p>For protocols where the codecs are not negotiable, instead of
+ popping up the initial content's <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object with an empty <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>,
+ the CM should set the supported codec values to known codec
+ values in the said object's codec map.</p>
+
+ <h4>Changing codecs mid-call</h4>
+
+ <p>To update the codec list used mid-call, the
+ <tp:member-ref>UpdateCodecs</tp:member-ref> method should be
+ called with details of the new codec list. If this is
+ accepted, then <tp:member-ref>CodecsChanged</tp:member-ref>
+ will be emitted with the new codec set.</p>
+
+ <p>If the other side decides to update his or her codec list
+ during a call, a new <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object will appear through
+ <tp:member-ref>NewCodecOffer</tp:member-ref> which should be
+ acted on as documented above.</p>
+
+ </tp:docstring>
+
+ <tp:struct name="Codec" array-name="Codec_List">
+ <tp:docstring>
+ A description of a codec.
+ </tp:docstring>
+ <tp:member name="Identifier" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Numeric identifier for the codec. This will be used as the PT in the
+ SDP or content description.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Name" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The name of the codec.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Clockrate" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The clockrate of the codec.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Channels" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Number of channels of the codec if applicable, otherwise 0.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Parameters" type="a{ss}" tp:type="String_String_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Extra parameters for this codec.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Contact_Codec_Map">
+ <tp:docstring>
+ A map from contact to the list of codecs he or she supports.
+ </tp:docstring>
+ <tp:member name="Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ A contact handle.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Codecs" type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The codecs that the contact supports.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Codec_Offering">
+ <tp:docstring>
+ A codec offer and its corresponding remote contact codec map.
+ </tp:docstring>
+ <tp:member name="Codec_Offer" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The object path to the <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref>
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Remote_Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The contact handle that this codec offer applies to.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Remote_Contact_Codecs" type="a(usuua{ss})"
+ tp:type="Codec[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> property
+ of the codec offer.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <signal name="CodecsChanged" tp:name-for-bindings="Codecs_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the codecs in use change.</p>
+
+ <p>As well as acting as change notification for the
+ <tp:member-ref>ContactCodecMap</tp:member-ref>, emission of this
+ signal implies that the <tp:member-ref>CodecOffer</tp:member-ref>
+ property has changed to <code>('/', {})</code>.</p>
+ </tp:docstring>
+ <arg name="Updated_Codecs" type="a{ua(usuua{ss})}"
+ tp:type="Contact_Codec_Map">
+ <tp:docstring>
+ A map from contact to his or her codecs. Each pair in this
+ map is added to the
+ <tp:member-ref>ContactCodecMap</tp:member-ref> property,
+ replacing any previous pair with that key.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed_Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of keys which were removed from the
+ <tp:member-ref>ContactCodecMap</tp:member-ref>, probably because
+ those contacts left the call.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="UpdateCodecs" tp:name-for-bindings="Update_Codecs">
+ <tp:docstring>
+ Update the local codec mapping. This method should only be
+ used during an existing call to update the codec mapping.
+ </tp:docstring>
+ <arg name="Codecs" direction="in"
+ type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring>
+ The codecs now supported by the local user.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ Raised when a <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object exists and is referred to in the
+ <tp:member-ref>CodecOffer</tp:member-ref> property which
+ should be used instead of calling this method, or before
+ the content's initial <tp:dbus-ref
+ namespace="ofdT.Call.Content">CodecOffer.DRAFT</tp:dbus-ref>
+ object has appeared.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <property name="ContactCodecMap" tp:name-for-bindings="Contact_Codec_Map"
+ type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read">
+ <tp:docstring>
+ <p>A map from contact handles (including the local user's own handle)
+ to the codecs supported by that contact.</p>
+
+ <p>Change notification is via the
+ <tp:member-ref>CodecsChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="NewCodecOffer" tp:name-for-bindings="New_Codec_Offer">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref> appears. The streaming
+ implementation MUST respond by calling the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >Accept</tp:dbus-ref> or <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >Reject</tp:dbus-ref> method on the codec offer object.</p>
+
+ <p>Emission of this signal indicates that the
+ <tp:member-ref>CodecOffer</tp:member-ref> property has changed to
+ <code>(Contact, Offer, Codecs)</code>.</p>
+ </tp:docstring>
+ <arg name="Contact" type="u">
+ <tp:docstring>
+ The contact the codec offer belongs to.
+ </tp:docstring>
+ </arg>
+ <arg name="Offer" type="o">
+ <tp:docstring>
+ The object path of the new codec offer. This replaces any previous
+ codec offer.
+ </tp:docstring>
+ </arg>
+ <arg name="Codecs" type="a(usuua{ss})" tp:type="Codec[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> property
+ of the codec offer.</p>
+
+ <tp:rationale>
+ Having the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT">RemoteContactCodecs</tp:dbus-ref>
+ property here saves a D-Bus round-trip - it shouldn't be
+ necessary to get the property from the CodecOffer object, in
+ practice.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CodecOffer" tp:name-for-bindings="Codec_Offer"
+ type="(oua(usuua{ss}))" tp:type="Codec_Offering" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The object path to the current
+ <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT</tp:dbus-ref> object, its
+ <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContact</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Call.Content"
+ >CodecOffer.DRAFT.RemoteContactCodecs</tp:dbus-ref> properties.
+ If the object path is "/" then there isn't an outstanding
+ codec offer, and the mapping MUST be empty.</p>
+
+ <tp:rationale>
+ Having the <tp:dbus-ref
+ namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >RemoteContact</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Call.Content.CodecOffer.DRAFT"
+ >RemoteContactCodecs</tp:dbus-ref>
+ properties here saves a D-Bus round-trip - it shouldn't be
+ necessary to get these properties from the CodecOffer object, in
+ practice.
+ </tp:rationale>
+
+ <p>Change notification is via the
+ <tp:member-ref>NewCodecOffer</tp:member-ref> (which replaces the
+ value of this property with a new codec offer), and
+ <tp:member-ref>CodecsChanged</tp:member-ref> (which implies that
+ there is no longer any active codec offer) signals.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_Content_Packetization_Type" type="u">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ A packetization method that can be used for a content.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="RTP" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Real-time Transport Protocol, as documented by RFC 3550.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Raw" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Raw media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="MSN_Webcam" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ MSN webcam. This is the video-only one-way type which was
+ used in earlier versions of WLM. Although no longer used,
+ modern WLM clients still support the MSN webcam protocol.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Packetization" tp:name-for-bindings="Packetization"
+ type="u" tp:type="Call_Content_Packetization_Type" access="read"
+ tp:immutable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The packetization method in use for this content.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Content_Interface_Mute.xml b/qt4/spec/Call_Content_Interface_Mute.xml
new file mode 100644
index 000000000..f926e03cd
--- /dev/null
+++ b/qt4/spec/Call_Content_Interface_Mute.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" ?>
+<node name="/Call_Content_Interface_Mute" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2010 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2005-2010 Collabora Ltd </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Mute.DRAFT" tp:causes-havoc="experimental">
+ <tp:added version="0.19.6">(draft version, not API-stable)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for calls which may be muted. This only makes sense
+ for channels where audio or video is streaming between members.</p>
+
+ <p>Muting a call content indicates that the user does not wish to send
+ outgoing audio or video.</p>
+
+ <p>Although it's client's responsibility to actually mute the microphone
+ or turn off the camera, using this interface the client can also
+ inform the CM and other clients of that fact.</p>
+
+ <tp:rationale>
+ For some protocols, the fact that the content is muted needs
+ to be transmitted to the peer; for others, the notification
+ to the peer is only informational (eg. XMPP), and some
+ protocols may have no notion of muting at all.
+ </tp:rationale>
+ </tp:docstring>
+
+ <signal name="MuteStateChanged" tp:name-for-bindings="Mute_State_Changed">
+ <tp:docstring>
+ Emitted to indicate that the mute state has changed for this call content.
+ This may occur as a consequence of the client calling
+ <tp:member-ref>SetMuted</tp:member-ref>, or as an indication that another
+ client has (un)muted the content.
+ </tp:docstring>
+ <arg name="MuteState" type="b">
+ <tp:docstring>
+ True if the content is now muted.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="MuteState" type="b"
+ access="read" tp:name-for-bindings="Mute_State">
+ <tp:docstring>
+ True if the content is muted.
+ </tp:docstring>
+ </property>
+
+ <method name="SetMuted" tp:name-for-bindings="Set_Muted">
+ <tp:changed version="0.21.2">renamed from SetMuted to Mute</tp:changed>
+ <tp:changed version="0.21.3">renamed back from Mute to SetMuted</tp:changed>
+ <arg direction="in" name="Muted" type="b">
+ <tp:docstring>
+ True if the client has muted the content.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ <p>Inform the CM that the call content has been muted or unmuted by
+ the client.</p>
+
+ <p>It is the client's responsibility to actually mute or unmute the
+ microphone or camera used for the content. However, the client
+ MUST call this whenever it mutes or unmutes the content.</p>
+ </tp:docstring>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Content_Interface_Video_Control.xml b/qt4/spec/Call_Content_Interface_Video_Control.xml
new file mode 100644
index 000000000..b066de42b
--- /dev/null
+++ b/qt4/spec/Call_Content_Interface_Video_Control.xml
@@ -0,0 +1,137 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node name="/Call_Content_Interface_Video_Control"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Content.Interface.VideoControl.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.10">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface that allows the connection manager to control the video
+ stream.</p>
+ <p>This interface is generally not needed. In cases where the connection
+ manager handles the network communication and the media is transferred
+ from the client to the connection manager via shared memory, it can
+ sometimes be beneficial for the connection manager to be able to
+ control certain aspects of the video stream.</p>
+ </tp:docstring>
+
+ <signal name="KeyFrameRequested" tp:name-for-bindings="Key_Frame_Requested">
+ <tp:docstring>
+ Request that the video encoder produce a new key frame as soon as
+ possible.
+ </tp:docstring>
+ </signal>
+
+ <tp:struct name="Video_Resolution"
+ array-name="Video_Resolution_Struct">
+ <tp:member type="u" name="Width">
+ <tp:docstring>
+ With of the video stream.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Height">
+ <tp:docstring>
+ Height of the video stream.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="VideoResolution" type="(uu)" tp:type="Video_Resolution"
+ access="read" tp:name-for-bindings="Video_Resolution">
+ <tp:docstring>
+ The resolution at which the streaming engine should be sending.
+
+ <p>Change notification is via the
+ <tp:member-ref>VideoResolutionChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="VideoResolutionChanged"
+ tp:name-for-bindings="Video_Resolution_Changed">
+ <tp:docstring>
+ The desired video resolution has changed.
+ </tp:docstring>
+ <arg type="(uu)" tp:type="Video_Resolution" name="NewResolution" />
+ </signal>
+
+ <property name="Bitrate" type="u" access="read"
+ tp:name-for-bindings="Bitrate">
+ <tp:docstring>
+ The bitrate the streaming engine should be sending at.
+
+ <p>Change notification is via the
+ <tp:member-ref>BitrateChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="BitrateChanged"
+ tp:name-for-bindings="Bitrate_Changed">
+ <tp:docstring>
+ The desired bitrate has changed
+ </tp:docstring>
+ <arg type="u" name="NewBitrate" />
+ </signal>
+
+ <property name="Framerate" type="u" access="read"
+ tp:name-for-bindings="Framerate">
+ <tp:docstring>
+ The framerate the streaming engine should be sending at.
+
+ <p>Change notification is via the
+ <tp:member-ref>FramerateChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="FramerateChanged"
+ tp:name-for-bindings="Framerate_Changed">
+ <tp:docstring>
+ The desired framerate has changed
+ </tp:docstring>
+ <arg type="u" name="NewFramerate" />
+ </signal>
+
+ <property name="MTU" type="u" access="read"
+ tp:name-for-bindings="MTU">
+ <tp:docstring>
+ The Maximum Transmission Unit
+
+ <p>Change notification is via the
+ <tp:member-ref>MTUChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="MTUChanged" tp:name-for-bindings="MTU_Changed">
+ <tp:docstring>
+ The Maximum Transmission Unit has changed
+ </tp:docstring>
+ <arg type="u" name="NewMTU" />
+ </signal>
+
+ <property name="ManualKeyFrames" type="b" access="read"
+ tp:name-for-bindings="Manual_Key_Frames">
+ <tp:docstring>
+ Only send key frames when manually requested
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
diff --git a/qt4/spec/Call_Stream.xml b/qt4/spec/Call_Stream.xml
new file mode 100644
index 000000000..1d7b28147
--- /dev/null
+++ b/qt4/spec/Call_Stream.xml
@@ -0,0 +1,261 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ One stream inside a <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>.
+ </tp:docstring>
+
+ <method name="SetSending" tp:name-for-bindings="Set_Sending">
+ <tp:docstring>
+ Set the stream to start or stop sending media from the local
+ user to other contacts.
+ </tp:docstring>
+
+ <arg name="Send" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If True, the
+ <tp:member-ref>LocalSendingState</tp:member-ref> should
+ change to <tp:type>Sending_State</tp:type>_Sending, if it isn't
+ already.</p>
+
+ <p>If False, the
+ <tp:member-ref>LocalSendingState</tp:member-ref> should
+ change to <tp:type>Sending_State</tp:type>_None, if it isn't
+ already.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented" />
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestReceiving" tp:name-for-bindings="Request_Receiving">
+ <tp:docstring>
+ <p>Request that a remote contact stops or starts sending on
+ this stream.</p>
+
+ <p>The <tp:member-ref>CanRequestReceiving</tp:member-ref>
+ property defines whether the protocol allows the local user to
+ request the other side start sending on this stream.</p>
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Contact from which sending is requested</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Receive" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, request that the given contact starts to send media.
+ If false, request that the given contact stops sending media.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The request contact is valid but is not involved in this
+ stream.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The protocol does not allow the local user to request the
+ other side starts sending on this stream.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="RemoteMembersChanged"
+ tp:name-for-bindings="Remote_Members_Changed">
+ <tp:changed version="0.21.2">renamed from SendersChanged to MembersChanged</tp:changed>
+ <tp:changed version="0.21.3">renamed from MembersChanged to RemoteMembersChanged</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted when <tp:member-ref>RemoteMembers</tp:member-ref> changes.
+ </tp:docstring>
+
+ <arg name="Updates" type="a{uu}" tp:type="Contact_Sending_State_Map">
+ <tp:docstring>
+ A mapping from channel-specific handles to their updated sending
+ state, whose keys include at least the members who were added,
+ and the members whose states changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The channel-specific handles that were removed from the keys
+ of the <tp:member-ref>RemoteMembers</tp:member-ref>
+ property, as a result of the contact leaving this stream
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="LocalSendingStateChanged"
+ tp:name-for-bindings="Local_Sending_State_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted when <tp:member-ref>LocalSendingState</tp:member-ref> changes.
+ </tp:docstring>
+
+ <arg name="State" type="u" tp:type="Sending_State">
+ <tp:docstring>
+ The new value of
+ <tp:member-ref>LocalSendingState</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Sending_State" type="u">
+ <tp:docstring>
+ Enum indicating whether a contact is sending media.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ The contact is not sending media and has not been asked to
+ do so.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Pending_Send" value="1">
+ <tp:docstring>
+ The contact has been asked to start sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Sending" value="2">
+ <tp:docstring>
+ The contact is sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Pending_Stop_Sending" value="3">
+ <tp:docstring>
+ The contact has been asked to stop sending media.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:mapping name="Contact_Sending_State_Map">
+ <tp:docstring>
+ A map from a contact to his or her sending state.
+ </tp:docstring>
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact handle.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Sending" type="u" tp:type="Sending_State">
+ <tp:docstring>
+ The sending state of the contact.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read" tp:immutable="yes">
+ <tp:added version="0.19.11"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this stream, such as <tp:dbus-ref
+ namespace="ofdT.Call">Stream.Interface.Media.DRAFT</tp:dbus-ref>.
+ This SHOULD NOT include the Stream interface itself, and cannot
+ change once the stream has been created.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RemoteMembers" tp:name-for-bindings="Remote_Members"
+ type="a{uu}" access="read" tp:type="Contact_Sending_State_Map">
+ <tp:changed version="0.21.2">renamed from Senders</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from remote contacts to their sending state. The
+ local user's sending state is shown in
+ <tp:member-ref>LocalSendingState</tp:member-ref>.</p>
+
+ <p><tp:type>Sending_State</tp:type>_Pending_Send indicates
+ that another contact has asked the local user to send
+ media.</p>
+
+ <p>Other contacts' handles in this map indicate whether they are
+ sending media to the contacts in this stream.
+ Sending_State_Pending_Send indicates contacts who are not sending but
+ have been asked to do so.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="LocalSendingState" tp:name-for-bindings="Local_Sending_State"
+ type="u" access="read" tp:type="Sending_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The local user's sending state. Media sent on this stream
+ should be assumed to be received, directly or indirectly, by
+ every other contact in the
+ <tp:member-ref>RemoteMembers</tp:member-ref> mapping. Change
+ notification is given via the
+ <tp:member-ref>LocalSendingStateChanged</tp:member-ref>
+ signal.</p>
+
+ <tp:rationale>
+ Implementations of the first Call draft had the self handle
+ in the <tp:member-ref>RemoteMembers</tp:member-ref> (then
+ called Members) map and this showed that it's annoying
+ having to keep track of the self handle so that it can be
+ special-cased.
+ </tp:rationale>
+
+ <p>A value of <tp:type>Sending_State</tp:type>_Pending_Send for
+ this property indicates that the other side requested the
+ local user start sending media, which can be done by calling
+ <tp:member-ref>SetSending</tp:member-ref>. When a call is
+ accepted, all initial contents with streams that have a
+ local sending state of
+ <tp:type>Sending_State</tp:type>_Pending_Send are
+ automatically set to sending. For example, on an incoming
+ call it means you need to <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref>
+ to start the actual call, on an outgoing call it might mean
+ you need to call <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">Accept</tp:dbus-ref>
+ before actually starting the call.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CanRequestReceiving" tp:name-for-bindings="Can_Request_Receiving"
+ type="b" access="read" tp:immutable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, the user can request that a remote contact starts
+ sending on this stream.</p>
+
+ <tp:rationale>Not all protocols allow the user to ask the
+ other side to start sending media.</tp:rationale>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Stream_Endpoint.xml b/qt4/spec/Call_Stream_Endpoint.xml
new file mode 100644
index 000000000..cf1783d1e
--- /dev/null
+++ b/qt4/spec/Call_Stream_Endpoint.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream_Endpoint"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This object represents an endpoint for a stream. In a one-to-one
+ call, there will be one (bidirectional) stream per content and
+ one endpoint per stream (as there is only one remote
+ contact). In a multi-user call there is a stream for each remote
+ contact and each stream has one endpoint as it refers to the one
+ physical machine on the other end of the stream.</p>
+
+ <p>The multiple endpoint use case appears when SIP call forking
+ is used. Unlike jingle call forking (which is just making
+ multiple jingle calls to different resources appear as one
+ call), SIP call forking is actually done at the server so you
+ have one stream to the remote contact and then and endpoint for
+ each SIP client to be called.</p>
+ </tp:docstring>
+
+ <property name="RemoteCredentials"
+ tp:name-for-bindings="Remote_Credentials"
+ type="(ss)" tp:type="Stream_Credentials" access="read">
+ <tp:docstring>
+ The ICE credentials used for all candidates. If each candidate
+ has different credentials, then this property SHOULD be ("",
+ ""). Per-candidate credentials are set in the
+ <tp:type>Candidate</tp:type>'s
+ <tp:type>Candidate_Info</tp:type> a{sv}.
+ </tp:docstring>
+ </property>
+
+ <signal name="RemoteCredentialsSet"
+ tp:name-for-bindings="Remote_Credentials_Set">
+ <arg name="Username" type="s">
+ <tp:docstring>
+ The username set.
+ </tp:docstring>
+ </arg>
+ <arg name="Password" type="s">
+ <tp:docstring>
+ The password set.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the remote ICE credentials for the endpoint are
+ set. If each candidate has different credentials, then this
+ signal will never be fired.
+ </tp:docstring>
+ </signal>
+
+ <property name="RemoteCandidates" tp:name-for-bindings="Remote_Candidates"
+ type="a(usua{sv})" tp:type="Candidate[]" access="read">
+ <tp:docstring>
+ A list of candidates for this endpoint.
+ </tp:docstring>
+ </property>
+
+ <signal name="RemoteCandidatesAdded"
+ tp:name-for-bindings="Remote_Candidates_Added">
+ <tp:docstring>
+ Emitted when remote candidates are added to the
+ <tp:member-ref>RemoteCandidates</tp:member-ref> property.
+ </tp:docstring>
+ <arg name="Candidates"
+ type="a(usua{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ The candidates that were added.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="CandidateSelected"
+ tp:name-for-bindings="Candidate_Selected">
+ <tp:docstring>
+ Emitted when a candidate is selected for use in the stream.
+ </tp:docstring>
+ <arg name="Candidate"
+ type="(usua{sv})" tp:type="Candidate">
+ <tp:docstring>
+ The candidate that has been selected.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="SelectedCandidate"
+ tp:name-for-bindings="Selected_Candidate"
+ type="(usua{sv})" tp:type="Candidate" access="read">
+ <tp:docstring>
+ The candidate that has been selected for use to stream packets
+ to the remote contact. Change notification is given via the
+ the <tp:member-ref>CandidateSelected</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <method name="SetSelectedCandidate"
+ tp:name-for-bindings="Set_Selected_Candidate">
+ <tp:docstring>
+ Set the value of
+ <tp:member-ref>CandidateSelected</tp:member-ref>.
+ </tp:docstring>
+ <arg name="Candidate"
+ type="(usua{sv})" tp:type="Candidate" direction="in">
+ <tp:docstring>
+ The candidate that has been selected.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="StreamState" tp:name-for-bindings="Stream_State"
+ type="u" tp:type="Media_Stream_State"
+ access="read">
+ <tp:docstring>
+ The stream state of the endpoint.
+ </tp:docstring>
+ </property>
+
+ <signal name="StreamStateChanged"
+ tp:name-for-bindings="Stream_State_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>StreamState</tp:member-ref>
+ property changes.
+ </tp:docstring>
+ <arg name="state" type="u" tp:type="Media_Stream_State">
+ <tp:docstring>
+ The new <tp:member-ref>StreamState</tp:member-ref> value.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="SetStreamState"
+ tp:name-for-bindings="Set_Stream_State">
+ <tp:docstring>
+ Change the <tp:member-ref>StreamState</tp:member-ref> of the
+ endpoint.
+ </tp:docstring>
+ <arg direction="in" name="State" type="u" tp:type="Media_Stream_State">
+ <tp:docstring>
+ The requested stream state.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="Transport" tp:name-for-bindings="Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read">
+ <tp:docstring>
+ The transport type for the stream endpoint.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Call_Stream_Interface_Media.xml b/qt4/spec/Call_Stream_Interface_Media.xml
new file mode 100644
index 000000000..54476f0fa
--- /dev/null
+++ b/qt4/spec/Call_Stream_Interface_Media.xml
@@ -0,0 +1,473 @@
+<?xml version="1.0" ?>
+<node name="/Call_Stream_Interface_Media"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Call.Stream.DRAFT"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ [FIXME]
+
+ <h4>ICE restarts</h4>
+
+ <p>If the <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Endpoint.DRAFT">RemoteCredentialsSet</tp:dbus-ref>
+ signal is fired during a call once it has been called before
+ whilst setting up the call for initial use, and the
+ credentials have changed, then there has been an ICE
+ restart. In such a case, the CM SHOULD also empty the remote
+ candidate list in the <tp:dbus-ref
+ namespace="ofdT.Call.Stream">Endpoint.DRAFT</tp:dbus-ref>.</p>
+
+ <p>If the CM does an ICE restart, then the
+ <tp:member-ref>PleaseRestartICE</tp:member-ref> signal is
+ emitted and the streaming implementation should then call
+ <tp:member-ref>SetCredentials</tp:member-ref> again.</p>
+
+ <p>For more information on ICE restarts see
+ <a href="http://tools.ietf.org/html/rfc5245#section-9.1.1.1">RFC 5245
+ section 9.1.1.1</a></p>
+ </tp:docstring>
+
+ <method name="SetCredentials" tp:name-for-bindings="Set_Credentials">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Used to set the username fragment and password for streams that have
+ global credentials.</p>
+ </tp:docstring>
+ <arg name="Username" type="s" direction="in">
+ <tp:docstring>
+ The username to use when authenticating on the stream.
+ </tp:docstring>
+ </arg>
+ <arg name="Password" type="s" direction="in">
+ <tp:docstring>
+ The password to use when authenticating on the stream.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <tp:mapping name="Candidate_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra information about the candidate. Allowed and mandatory keys
+ depend on the transport protocol used. The following keys are commenly
+ used:</p>
+
+ <dl>
+ <dt>Type (u)</dt>
+ <dd>type of candidate (host, srflx, prflx, relay)</dd>
+
+ <dt>Foundation (s)</dt>
+ <dd>the foundation of this candiate</dd>
+
+ <dt>Protocol (u) </dt>
+ <dd>Underlying protocol of the candidate (udp, tcp) </dd>
+
+ <dt>Priority (u) </dt>
+ <dd>Priority of the candidate </dd>
+
+ <dt>BaseIP (u) </dt>
+ <dd>Base IP of this candidate </dd>
+
+ <dt>Username (s) </dt>
+ <dd>Username of this candidate
+ (only if credentials are per candidate)</dd>
+
+ <dt>Password (s) </dt>
+ <dd>Password of this candidate
+ (only if credentials are per candidate)</dd>
+
+ <dt>RawUDPFallback (b) </dt>
+ <dd>Indicate whether this candidate may be used to provide a UDP
+ fallback</dd>
+ </dl>
+ </tp:docstring>
+ <tp:member name="Key" type="s">
+ <tp:docstring>One of the well-known keys documented here, or an
+ implementation-specific key.</tp:docstring>
+ </tp:member>
+ <tp:member name="Value" type="v">
+ <tp:docstring>The value corresponding to that key.</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:enum type="u" name="Stream_Component">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Media streams can use more than one UDP socket: one for RTP (data)
+ and one for RTCP (control). Most of the time, they are adjacent
+ to each other, but some protocols (xmpp) signal each port separately.
+ </tp:docstring>
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ The stream transport type is unknown or not applicable
+ (should not appear over dbus).
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Data" value="1">
+ <tp:docstring>
+ This is the high-traffic data socket, containing the audio/video
+ data for the stream.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Control" value="2">
+ <tp:docstring>
+ This is the low-traffic control socket, usually containing feedback
+ about packet loss etc.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Candidate" array-name="Candidate_List">
+ <tp:docstring>A Stream Candidate.</tp:docstring>
+ <tp:member name="Component" type="u" tp:type="Stream_Component">
+ <tp:docstring>The component number.</tp:docstring>
+ </tp:member>
+ <tp:member name="IP" type="s">
+ <tp:docstring>The IP address to use.</tp:docstring>
+ </tp:member>
+ <tp:member name="Port" type="u">
+ <tp:docstring>The port number to use.</tp:docstring>
+ </tp:member>
+ <tp:member name="Info" type="a{sv}" tp:type="Candidate_Info">
+ <tp:docstring>Additional information about the candidate.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="AddCandidates" tp:name-for-bindings="Add_Candidates">
+ <tp:docstring>
+ Add candidates to the
+ <tp:member-ref>LocalCandidates</tp:member-ref> property and
+ signal them to the remote contact(s).
+ </tp:docstring>
+ <arg name="Candidates" direction="in"
+ type="a(usua{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ The candidates to be added.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="CandidatesPrepared"
+ tp:name-for-bindings="Candidates_Prepared">
+ <tp:docstring>
+ This indicates to the CM that the initial batch of candidates
+ has been added.
+ </tp:docstring>
+ </method>
+
+ <tp:enum type="u" name="Stream_Transport_Type">
+ <tp:changed version="0.21.2">WLM_8_5 was removed</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A transport that can be used for streaming.
+ </tp:docstring>
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ The stream transport type is unknown or not applicable
+ (for streams that do not have a configurable transport).
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Raw_UDP" value="1">
+ <tp:docstring>
+ Raw UDP, with or without STUN. All streaming clients are assumed to
+ support this transport, so there is no handler capability token for
+ it in the <tp:dbus-ref namespace="ofdT.Channel.Type"
+ >Call.DRAFT</tp:dbus-ref> interface.
+ [This corresponds to "none" or "stun" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="ICE" value="2">
+ <tp:docstring>
+ Interactive Connectivity Establishment, as defined by RFC
+ 5245. Note that this value covers ICE-UDP only.
+ [This corresponds to "ice-udp" in the old
+ Media.StreamHandler interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="GTalk_P2P" value="3">
+ <tp:docstring>
+ Google Talk peer-to-peer connectivity establishment, as implemented
+ by libjingle 0.3.
+ [This corresponds to "gtalk-p2p" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="WLM_2009" value="4">
+ <tp:docstring>
+ The transport used by Windows Live Messenger 2009 or later, which
+ resembles ICE draft 19.
+ [This corresponds to "wlm-2009" in the old Media.StreamHandler
+ interface.]
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="SHM" value="5">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ Shared memory transport, as implemented by the GStreamer
+ shmsrc and shmsink plugins.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Multicast" value="6">
+ <tp:added version="0.21.5"/>
+ <tp:docstring>
+ Multicast transport.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="Transport" tp:name-for-bindings="Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read" tp:immutable="yes">
+ <tp:docstring>
+ The transport for this stream.
+ </tp:docstring>
+ </property>
+
+ <property name="LocalCandidates" tp:name-for-bindings="Local_Candidates"
+ type="a(usua{sv})" tp:type="Candidate[]" access="read">
+ <tp:docstring>
+ [FIXME]. Change notification is via the
+ <tp:member-ref>LocalCandidatesAdded</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <signal name="LocalCandidatesAdded"
+ tp:name-for-bindings="Local_Candidates_Added">
+ <tp:docstring>
+ Emitted when local candidates are added to the
+ <tp:member-ref>LocalCandidates</tp:member-ref> property.
+ </tp:docstring>
+ <arg name="Candidates" type="a(usua{sv})" tp:type="Candidate[]">
+ <tp:docstring>
+ Candidates that have been added.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:struct name="Stream_Credentials">
+ <tp:docstring>A username and password pair.</tp:docstring>
+
+ <tp:member name="Username" type="s">
+ <tp:docstring>The username.</tp:docstring>
+ </tp:member>
+
+ <tp:member name="Password" type="s">
+ <tp:docstring>The password.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="LocalCredentials" tp:name-for-bindings="Local_Credentials"
+ type="(ss)" tp:type="Stream_Credentials" access="read">
+ <tp:docstring>
+ [FIXME]. Change notification is via the
+ <tp:member-ref>LocalCredentialsChanged</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <signal name="LocalCredentialsChanged"
+ tp:name-for-bindings="Local_Credentials_Changed">
+ <tp:changed version="0.21.2">renamed from LocalCredentailsSet</tp:changed>
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>LocalCredentials</tp:member-ref> changes.
+ </tp:docstring>
+ <arg name="Username" type="s" />
+ <arg name="Password" type="s" />
+ </signal>
+
+ <signal name="RelayInfoChanged"
+ tp:name-for-bindings="Relay_Info_Changed">
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>RelayInfo</tp:member-ref> changes.
+ </tp:docstring>
+ <arg name="Relay_Info" type="aa{sv}" tp:type="String_Variant_Map[]" />
+ </signal>
+
+ <signal name="STUNServersChanged"
+ tp:name-for-bindings="STUN_Servers_Changed">
+ <tp:docstring>
+ Emitted when the value of
+ <tp:member-ref>STUNServers</tp:member-ref> changes.
+ </tp:docstring>
+ <arg name="Servers" type="a(sq)" tp:type="Socket_Address_IP[]" />
+ </signal>
+
+ <property name="STUNServers" tp:name-for-bindings="STUN_Servers"
+ type="a(sq)" tp:type="Socket_Address_IP[]" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The IP addresses of possible STUN servers to use for NAT
+ traversal, as dotted-quad IPv4 address literals or RFC2373
+ IPv6 address literals. Change notification is via the
+ <tp:member-ref>STUNServersChanged</tp:member-ref>
+ signal. The IP addresses MUST NOT be given as DNS hostnames.</p>
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ to make life easier for streaming implementations.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="RelayInfo" type="aa{sv}" access="read"
+ tp:type="String_Variant_Map[]" tp:name-for-bindings="Relay_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of mappings describing TURN or Google relay servers
+ available for the client to use in its candidate gathering, as
+ determined from the protocol. Map keys are:</p>
+
+ <dl>
+ <dt><code>ip</code> - s</dt>
+ <dd>The IP address of the relay server as a dotted-quad IPv4
+ address literal or an RFC2373 IPv6 address literal. This MUST NOT
+ be a DNS hostname.
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ and make life easier for streaming implementations.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>type</code> - s</dt>
+ <dd>
+ <p>Either <code>udp</code> for UDP (UDP MUST be assumed if this
+ key is omitted), <code>tcp</code> for TCP, or
+ <code>tls</code>.</p>
+
+ <p>The precise meaning of this key depends on the
+ <tp:member-ref>Transport</tp:member-ref> property: if
+ Transport is ICE, <code>tls</code> means
+ TLS over TCP as referenced by ICE draft 19, and if
+ Transport is GTalk_P2P, <code>tls</code> means
+ a fake SSL session over TCP as implemented by libjingle.</p>
+ </dd>
+
+ <dt><code>port</code> - q</dt>
+ <dd>The UDP or TCP port of the relay server as an ASCII unsigned
+ integer</dd>
+
+ <dt><code>username</code> - s</dt>
+ <dd>The username to use</dd>
+
+ <dt><code>password</code> - s</dt>
+ <dd>The password to use</dd>
+
+ <dt><code>component</code> - u</dt>
+ <dd>The component number to use this relay server for, as an
+ ASCII unsigned integer; if not included, this relay server
+ may be used for any or all components.
+
+ <tp:rationale>
+ In ICE draft 6, as used by Google Talk, credentials are only
+ valid once, so each component needs relaying separately.
+ </tp:rationale>
+ </dd>
+ </dl>
+
+ <tp:rationale>
+ <p>An equivalent of the gtalk-p2p-relay-token property on
+ MediaSignalling channels is not included here. The connection
+ manager should be responsible for making the necessary HTTP
+ requests to turn the token into a username and password.</p>
+ </tp:rationale>
+
+ <p>The type of relay server that this represents depends on
+ the value of the <tp:member-ref>Transport</tp:member-ref>
+ property. If Transport is ICE, this is a TURN server;
+ if Transport is GTalk_P2P, this is a Google relay server;
+ otherwise, the meaning of RelayInfo is undefined.</p>
+
+ <p>If relaying is not possible for this stream, the list is
+ empty.</p>
+
+ <p>Change notification is given via the
+ <tp:member-ref>RelayInfoChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="ServerInfoRetrieved"
+ tp:name-for-bindings="Server_Info_Retrieved">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Signals that the initial information about STUN and Relay servers
+ has been retrieved, i.e. the
+ <tp:member-ref>HasServerInfo</tp:member-ref> property is
+ now true.</p>
+ </tp:docstring>
+ </signal>
+
+ <property name="HasServerInfo" type="b"
+ tp:name-for-bindings="Has_Server_Info" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if all the initial information about STUN servers and Relay
+ servers has been retrieved. Change notification is via the
+ <tp:member-ref>ServerInfoRetrieved</tp:member-ref> signal.</p>
+
+ <tp:rationale>
+ Streaming implementations that can't cope with STUN and
+ relay servers being added later SHOULD wait for this
+ property to become true before proceeding.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="EndpointsChanged"
+ tp:name-for-bindings="Endpoints_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>Endpoints</tp:member-ref> property
+ changes.
+ </tp:docstring>
+ <arg name="Endpoints_Added" type="ao">
+ <tp:docstring>
+ Endpoints that were added.
+ </tp:docstring>
+ </arg>
+ <arg name="Endpoints_Removed" type="ao">
+ <tp:docstring>
+ Endpoints that no longer exist.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Endpoints" tp:name-for-bindings="Endpoints"
+ type="ao" access="read">
+ <tp:docstring>
+ <p>The list of <tp:dbus-ref namespace="ofdT.Call.Stream"
+ >Endpoint.DRAFT</tp:dbus-ref> objects that exist for this
+ stream.</p>
+
+ <p>Change notification is via the
+ <tp:member-ref>EndpointsChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="PleaseRestartICE"
+ tp:name-for-bindings="Please_Restart_ICE">
+ <tp:docstring>
+ Emitted when the CM does an ICE restart to notify the
+ streaming implementation that it should call
+ <tp:member-ref>SetCredentials</tp:member-ref> again.
+ </tp:docstring>
+ </signal>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel.xml b/qt4/spec/Channel.xml
new file mode 100644
index 000000000..11d3e509c
--- /dev/null
+++ b/qt4/spec/Channel.xml
@@ -0,0 +1,557 @@
+<?xml version="1.0" ?>
+<node name="/Channel" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2005-2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2005-2009 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2006 INdT</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel">
+
+ <property name="ChannelType" type="s" tp:type="DBus_Interface"
+ access="read" tp:name-for-bindings="Channel_Type">
+ <tp:added version="0.17.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The channel's type. This cannot change once the channel has
+ been created.</p>
+
+ <p>For compatibility between older connection managers and newer
+ clients, if this is unavailable or is an empty string,
+ clients MUST use the result of calling
+ <tp:member-ref>GetChannelType</tp:member-ref>.</p>
+
+ <tp:rationale>
+ The GetAll method lets clients retrieve all properties in one
+ round-trip, which is desirable.
+ </tp:rationale>
+
+ <p>When requesting a channel, the request MUST specify a channel
+ type, and the request MUST fail if the specified channel type
+ cannot be supplied.</p>
+
+ <tp:rationale>
+ Common sense.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" tp:type="DBus_Interface[]" access="read">
+ <tp:added version="0.17.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Extra interfaces provided by this channel. This SHOULD NOT include
+ the channel type and the Channel interface itself, and cannot
+ change once the channel has been created.</p>
+
+ <p>For compatibility between older connection managers and newer
+ clients, if this is unavailable, or if this is an empty list and
+ <tp:member-ref>ChannelType</tp:member-ref> is an empty string,
+ clients MUST use the result of calling
+ <tp:member-ref>GetInterfaces</tp:member-ref> instead. If this is an
+ empty list but ChannelType is non-empty, clients SHOULD NOT call
+ GetInterfaces; this implies that connection managers that implement
+ the ChannelType property MUST also implement the Interfaces property
+ correctly.</p>
+
+ <tp:rationale>
+ The GetAll method lets clients retrieve all properties in one
+ round-trip, which is desirable.
+ </tp:rationale>
+
+ <p>When requesting a channel with a particular value for this
+ property, the request must fail without side-effects unless the
+ connection manager expects to be able to provide a channel whose
+ interfaces include at least the interfaces requested.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="TargetHandle" type="u" access="read" tp:type="Handle"
+ tp:name-for-bindings="Target_Handle">
+ <tp:added version="0.17.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The handle (a representation for the identifier) of the contact,
+ chatroom, etc. with which this handle communicates. Its type
+ is given by the <tp:member-ref>TargetHandleType</tp:member-ref>
+ property.</p>
+
+ <p>This is fixed for the lifetime of the channel, so channels which
+ could potentially be used to communicate with multiple contacts,
+ and do not have an identity of their own (such as a Handle_Type_Room
+ handle), must have TargetHandleType set to Handle_Type_None and
+ TargetHandle set to 0.</p>
+
+ <p>Unlike in the telepathy-spec 0.16 API, there is no particular
+ uniqueness guarantee - there can be many channels with the same
+ (channel type, handle type, handle) tuple. This is necessary
+ to support conversation threads in XMPP and SIP, for example.</p>
+
+ <p>If this is present in a channel request, it must be nonzero,
+ <tp:member-ref>TargetHandleType</tp:member-ref>
+ MUST be present and not Handle_Type_None, and
+ <tp:member-ref>TargetID</tp:member-ref> MUST NOT be
+ present. Properties from
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface">Addressing.DRAFT</tp:dbus-ref>
+ MUST NOT be present.</p>
+
+ <p>The channel that satisfies the request MUST either:</p>
+
+ <ul>
+ <li>have the specified TargetHandle property; or</li>
+ <li>have <tp:member-ref>TargetHandleType</tp:member-ref> =
+ Handle_Type_None, TargetHandle = 0, and be configured such that
+ it could communicate with the specified handle in some other way
+ (e.g. have the requested contact handle in its Group
+ interface)</li>
+ </ul>
+ </tp:docstring>
+ </property>
+
+ <property name="TargetID" tp:name-for-bindings="Target_ID"
+ type="s" access="read">
+ <tp:added version="0.17.9"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The string that would result from inspecting the
+ <tp:member-ref>TargetHandle</tp:member-ref>
+ property (i.e. the identifier in the IM protocol of the contact,
+ room, etc. with which this channel communicates), or the empty
+ string if the TargetHandle is 0.</p>
+
+ <tp:rationale>
+ <p>The presence of this property avoids the following race
+ condition:</p>
+
+ <ul>
+ <li>New channel C is signalled with target handle T</li>
+ <li>Client calls <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>(CONTACT,
+ [T])</li>
+ <li>Channel C closes, removing the last reference to handle T</li>
+ <li><tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>(CONTACT,
+ [T]) returns an error</li>
+ </ul>
+ </tp:rationale>
+
+ <p>If this is present in a channel request,
+ <tp:member-ref>TargetHandleType</tp:member-ref>
+ MUST be present and not Handle_Type_None, and
+ <tp:member-ref>TargetHandle</tp:member-ref> MUST NOT be
+ present. Properties from
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface">Addressing.DRAFT</tp:dbus-ref>
+ MUST NOT be present.The request MUST fail with error InvalidHandle,
+ without side-effects, if the requested TargetID would not be
+ accepted by
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection">RequestHandles</tp:dbus-ref>.</p>
+
+ <p>The returned channel must be related to the handle corresponding
+ to the given identifier, in the same way as if TargetHandle
+ had been part of the request instead.</p>
+
+ <tp:rationale>
+ <p>Requesting channels with a string identifier saves a round-trip
+ (the call to RequestHandles). It also allows the channel
+ dispatcher to accept a channel request for an account that is not
+ yet connected (and thus has no valid handles), bring the account
+ online, and pass on the same parameters to the new connection's
+ CreateChannel method.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="TargetHandleType" type="u" access="read"
+ tp:type="Handle_Type" tp:name-for-bindings="Target_Handle_Type">
+ <tp:added version="0.17.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The type of <tp:member-ref>TargetHandle</tp:member-ref>.</p>
+
+ <p>If this is omitted from a channel request, connection managers
+ SHOULD treat this as equivalent to Handle_Type_None.</p>
+
+ <p>If this is omitted or is Handle_Type_None,
+ <tp:member-ref>TargetHandle</tp:member-ref> and
+ <tp:member-ref>TargetID</tp:member-ref> MUST be omitted from the
+ request.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="Close" tp:name-for-bindings="Close">
+ <tp:docstring>
+ Request that the channel be closed. This is not the case until
+ the <tp:member-ref>Closed</tp:member-ref> signal has been emitted, and
+ depending on the connection
+ manager this may simply remove you from the channel on the server,
+ rather than causing it to stop existing entirely. Some channels
+ such as contact list channels may not be closed.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ This channel may never be closed, e.g. a contact list
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This channel is not currently in a state where it can be closed,
+ e.g. a non-empty user-defined contact group
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="Closed" tp:name-for-bindings="Closed">
+ <tp:docstring>
+ Emitted when the channel has been closed. Method calls on the
+ channel are no longer valid after this signal has been emitted,
+ and the connection manager may then remove the object from the bus
+ at any point.
+ </tp:docstring>
+ </signal>
+
+ <method name="GetChannelType" tp:name-for-bindings="Get_Channel_Type">
+ <tp:deprecated version="0.17.7">Use the ChannelType
+ property if possible.</tp:deprecated>
+ <arg direction="out" type="s" tp:type="DBus_Interface"
+ name="Channel_Type">
+ <tp:docstring>The interface name</tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns the interface name for the type of this channel. Clients
+ SHOULD use the <tp:member-ref>ChannelType</tp:member-ref> property
+ instead, falling back to this method only if necessary.
+
+ <tp:rationale>
+ The GetAll method lets clients retrieve all properties in one
+ round-trip.
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <method name="GetHandle" tp:name-for-bindings="Get_Handle">
+ <tp:deprecated version="0.17.7">Use the TargetHandleType
+ and TargetHandle properties if possible.</tp:deprecated>
+ <arg direction="out" type="u" tp:type="Handle_Type"
+ name="Target_Handle_Type">
+ <tp:docstring>
+ The same as TargetHandleType.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="u" tp:type="Handle" name="Target_Handle">
+ <tp:docstring>
+ The same as TargetHandle.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns the handle type and number if this channel represents a
+ communication with a particular contact, room or server-stored list, or
+ zero if it is transient and defined only by its contents. Clients
+ SHOULD use the <tp:member-ref>TargetHandle</tp:member-ref> and
+ <tp:member-ref>TargetHandleType</tp:member-ref> properties instead,
+ falling back to this method only if necessary.
+
+ <tp:rationale>
+ The GetAll method lets clients retrieve all properties in one
+ round-trip.
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <method name="GetInterfaces" tp:name-for-bindings="Get_Interfaces">
+ <tp:deprecated version="0.17.7">Use the Interfaces
+ property if possible.</tp:deprecated>
+ <arg direction="out" type="as" tp:type="DBus_Interface[]"
+ name="Interfaces">
+ <tp:docstring>
+ An array of the D-Bus interface names
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the optional interfaces implemented by the channel.
+ Clients SHOULD use the <tp:member-ref>Interfaces</tp:member-ref>
+ property instead, falling back to this method only if necessary.
+
+ <tp:rationale>
+ The GetAll method lets clients retrieve all properties in one
+ round-trip.
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <property name="Requested" tp:name-for-bindings="Requested"
+ type="b" access="read">
+ <tp:added version="0.17.13">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if this channel was created in response to a local request,
+ such as a call to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.RequestChannel</tp:dbus-ref>
+ or
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>.</p>
+
+ <tp:rationale>
+ <p>The idea of this property is to distinguish between "incoming"
+ and "outgoing" channels, in a way that doesn't break down when
+ considering special cases like contact lists that are automatically
+ created on connection to the server, or chatrooms that an
+ IRC proxy/bouncer like irssi-proxy or bip was already in.</p>
+
+ <p>The reason we want to make that distinction is that UIs for
+ things that the user explicitly requested should start up
+ automatically, whereas for incoming messages and VoIP calls we
+ should first ask the user whether they want to open the messaging
+ UI or accept the call.</p>
+ </tp:rationale>
+
+ <p>If the channel was not explicitly requested (even if it was
+ created as a side-effect of a call to one of those functions,
+ e.g. because joining a Tube in a MUC context on XMPP implies
+ joining that MUC), then this property is false.</p>
+
+ <p>For compatibility with older connection managers, clients SHOULD
+ assume that this property is true if they see a channel announced
+ by the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.NewChannel</tp:dbus-ref>
+ signal with the suppress_handler parameter set to true.</p>
+
+ <tp:rationale>
+ <p>In a correct connection manager, the only way to get such a
+ channel is to request it.</p>
+ </tp:rationale>
+
+ <p>Clients MAY additionally assume that this property is false
+ if they see a channel announced by the NewChannel signal with the
+ suppress_handler parameter set to false.</p>
+
+ <tp:rationale>
+ <p>This is more controversial, since it's possible to get that
+ parameter set to false by requesting a channel. However, there's
+ no good reason to do so, and we've deprecated this practice.</p>
+
+ <p>In the particular case of the channel dispatcher, the only
+ side-effect of wrongly thinking a channel is unrequested
+ is likely to be that the user has to confirm that they want to
+ use it, so it seems fairly harmless to assume in the channel
+ dispatcher that channels with suppress_handler false are
+ indeed unrequested.</p>
+ </tp:rationale>
+
+ <p>It does not make sense for this property to be in channel
+ requests—it will always be true for channels returned by
+ CreateChannel, and callers of EnsureChannel cannot control whether an
+ existing channel was originally requested locally—so it MUST NOT
+ be accepted.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitiatorHandle" type="u" tp:type="Contact_Handle"
+ access="read" tp:name-for-bindings="Initiator_Handle">
+ <tp:added version="0.17.13">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The contact who initiated the channel; for instance, the contact
+ who invited the local user to a chatroom, or the contact who
+ initiated a call.</p>
+
+ <p>This does <em>not</em> necessarily represent the contact who
+ created the underlying protocol-level construct. For instance, if
+ Rob creates a chatroom, Will joins that chatroom, and Will invites Simon
+ to join it, then Simon will see Will as the InitiatorHandle of the
+ channel representing the chatroom.</p>
+
+ <tp:rationale>
+ <p>The room creator is generally a less useful piece of information
+ than the inviter, is less likely to be available at invitation
+ time (i.e. can't necessarily be an immutable property), and is
+ less likely to be available at all. The creator of a chatroom
+ is not currently available via Telepathy; if added in future, it
+ is likely to be made available as a property on the Chatroom
+ interface (<a
+ href="http://bugs.freedesktop.org/show_bug.cgi?id=23151">bug 23151</a>).</p>
+ </tp:rationale>
+
+ <p>For channels requested by the
+ local user, this MUST be the value of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.SelfHandle</tp:dbus-ref>
+ at the time the channel was created (i.e. not a channel-specific
+ handle).</p>
+
+ <tp:rationale>
+ <p>On some protocols, the SelfHandle may change (as signalled by
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.SelfHandleChanged</tp:dbus-ref>),
+ but this property is immutable. Hence, locally-requested channels'
+ InitiatorHandle and InitiatorID may not match the current
+ SelfHandle; <tp:member-ref>Requested</tp:member-ref> can be used to
+ determine whether the channel was created locally.</p>
+ </tp:rationale>
+
+ <p>For channels requested by a remote user, this MUST be their handle.
+ If unavailable or not applicable, this MUST be 0 (for instance,
+ contact lists are not really initiated by anyone in particular, and
+ it's easy to imagine a protocol where chatroom invitations can be
+ anonymous).</p>
+
+ <p>For channels with the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Group</tp:dbus-ref>
+ interface, this SHOULD be the same
+ contact who is signalled as the "Actor" causing the self-handle
+ to be placed in the local-pending set.</p>
+
+ <p>This SHOULD NOT be a channel-specific handle, if possible.</p>
+
+ <p>It does not make sense for this property to be in channel
+ requests - the initiator will always be the local user - so it
+ MUST NOT be accepted.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitiatorID" type="s" access="read"
+ tp:name-for-bindings="Initiator_ID">
+ <tp:added version="0.17.13">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The string that would result from inspecting the
+ <tp:member-ref>InitiatorHandle</tp:member-ref>
+ property (i.e. the initiator's identifier in the IM protocol).</p>
+
+ <tp:rationale>
+ <p>The presence of this property avoids the following race
+ condition:</p>
+
+ <ul>
+ <li>New StreamedMedia channel C is signalled with initiator
+ handle I</li>
+ <li>Client calls <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>(CONTACT,
+ [I])</li>
+ <li>Channel C closes, removing the last reference to handle I</li>
+ <li><tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>(CONTACT,
+ [I]) returns an error</li>
+ <li>Client can indicate that a call was missed, but not who
+ called!</li>
+ </ul>
+ </tp:rationale>
+
+ <p>It does not make sense for this property to be in channel
+ requests - the initiator will always be the local user - so it
+ MUST NOT be accepted.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>All communication in the Telepathy framework is carried out via channel
+ objects which are created and managed by connections. This interface must
+ be implemented by all channel objects, along with one single channel type,
+ such as <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Type.ContactList</tp:dbus-ref>
+ which represents a list of people (such as a buddy list) or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Type.Text</tp:dbus-ref> which
+ represents a channel over which textual messages are sent and received.</p>
+
+ <p>Each Channel's object path MUST start with the object path of
+ its associated <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>, followed
+ by '/'. There MAY be any number of additional object-path components,
+ which clients MUST NOT attempt to parse.</p>
+
+ <tp:rationale>
+ <p>This ensures that Channel object paths are unique, even between
+ Connections and CMs, because Connection object paths are
+ guaranteed-unique via their link to the well-known bus name.</p>
+
+ <p>If all connection managers in use are known to comply with at least
+ spec version 0.17.10, then the Connection's object path can
+ even be determined from the Channel's without any additional
+ information, by taking the first 7 components.</p>
+ </tp:rationale>
+
+ <p>Each channel has a number of immutable properties (which cannot vary
+ after the channel has been announced with <tp:dbus-ref
+ namespace='ofdT.Connection.Interface.Requests'>NewChannels</tp:dbus-ref>),
+ provided to clients in the
+ <tp:dbus-ref namespace='ofdT.Client.Observer'>ObserveChannels</tp:dbus-ref>,
+ <tp:dbus-ref namespace='ofdT.Client.Approver'>AddDispatchOperation</tp:dbus-ref> and
+ <tp:dbus-ref namespace='ofdT.Client.Handler'>HandleChannels</tp:dbus-ref>
+ methods to permit immediate identification of the channel. This interface
+ contains immutable properties common to all channels. In brief:</p>
+
+ <ul>
+ <li><tp:member-ref>ChannelType</tp:member-ref> specifies the kind of
+ communication carried out on this channel;</li>
+ <li><tp:member-ref>TargetHandleType</tp:member-ref>,
+ <tp:member-ref>TargetHandle</tp:member-ref> and
+ <tp:member-ref>TargetID</tp:member-ref> specify the entity with which
+ this channel communicates, such as the other party in a 1-1 call, or
+ the name of a multi-user chat room;</li>
+ <li><tp:member-ref>InitiatorHandle</tp:member-ref> and
+ <tp:member-ref>InitiatorID</tp:member-ref> specify who created this
+ channel;</li>
+ <li><tp:member-ref>Requested</tp:member-ref> indicates whether the local
+ user requested this channel, or whether it is an incoming call, a text
+ conversation started by a remote contact, a chatroom invitation,
+ etc.</li>
+ </ul>
+
+ <p>Other optional <tp:member-ref>Interfaces</tp:member-ref> can be
+ implemented to indicate other available
+ functionality, such as <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.Group</tp:dbus-ref>
+ if the channel contains a number of contacts, <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.Password</tp:dbus-ref>
+ to indicate that a channel may have a password set to require entry, and
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.ChatState</tp:dbus-ref>
+ for typing notifications. The interfaces implemented may not vary after
+ the channel has been created. These other interfaces (along with the
+ interface named by <tp:member-ref>ChannelType</tp:member-ref>) may
+ themselves specify immutable properties to be announced up-front along
+ with the properties on this interface.</p>
+
+ <p>Some channels are “anonymous”, with
+ <tp:member-ref>TargetHandleType</tp:member-ref> set to <code>None</code>,
+ which indicates that the channel is defined by some other properties. For
+ instance, transient ad-hoc chat rooms may be defined only by their members (as visible
+ through the <tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Group</tp:dbus-ref>
+ interface), and <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>ContactSearch</tp:dbus-ref>
+ channels represent a single search attempt for a particular <tp:dbus-ref
+ namespace='ofdT.Channel.Type.ContactSearch'>Server</tp:dbus-ref>.</p>
+
+ <p>Specific connection manager implementations may implement channel types and
+ interfaces which are not contained within this specification in order to
+ support further functionality. To aid interoperability between client and
+ connection manager implementations, the interfaces specified here should be
+ used wherever applicable, and new interfaces made protocol-independent
+ wherever possible. Because of the potential for 3rd party interfaces adding
+ methods or signals with conflicting names, the D-Bus interface names should
+ always be used to invoke methods and bind signals.</p>
+ </tp:docstring>
+
+ <tp:changed version="0.17.7">Previously we guaranteed that, for
+ any handle type other than Handle_Type_None, and for any channel type
+ and any handle, there would be no more than one channel with that
+ combination of channel type, handle type and handle. This guarantee
+ has now been removed in order to accommodate features like message
+ threads.
+ </tp:changed>
+
+ <tp:changed version="0.17.10">Previously we did not explicitly
+ guarantee that Channels' object paths had the Connection's object path
+ as a prefix.
+ </tp:changed>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Bundle.xml b/qt4/spec/Channel_Bundle.xml
new file mode 100644
index 000000000..d211525a4
--- /dev/null
+++ b/qt4/spec/Channel_Bundle.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Bundle"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2008 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.ChannelBundle.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A group of related channels, which should all be dispatched to the
+ same handler if possible.</p>
+
+ <p>Bundles currently have no functionality of their own, so clients
+ SHOULD NOT examine this interface, but should instead treat the
+ bundle object-path as an opaque identifier. If more functionality is
+ added to bundles in future, this interface will be used for capability
+ discovery.</p>
+
+ <p>The lifetime of a bundle is defined by its component channels -
+ as long as one or more channels whose Bundle property is <em>B</em>
+ exist, the bundle <em>B</em> will also exist.</p>
+ </tp:docstring>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" access="read" tp:type="DBus_Interface[]">
+ <tp:docstring>
+ A list of the extra interfaces provided by this channel bundle.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Dispatch_Operation.xml b/qt4/spec/Channel_Dispatch_Operation.xml
new file mode 100644
index 000000000..6ec69a67b
--- /dev/null
+++ b/qt4/spec/Channel_Dispatch_Operation.xml
@@ -0,0 +1,483 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Dispatch_Operation"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ MA 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ChannelDispatchOperation">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel dispatch operation is an object in the ChannelDispatcher
+ representing a batch of unrequested channels being announced to
+ client
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Client">Approver</tp:dbus-ref>
+ processes.</p>
+
+ <p>These objects can result from new incoming channels or channels
+ which are automatically created for some reason, but cannot result
+ from outgoing requests for channels.</p>
+
+ <p>More specifically, whenever the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.NewChannels</tp:dbus-ref>
+ signal contains channels whose
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">Requested</tp:dbus-ref>
+ property is false, or whenever the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.NewChannel</tp:dbus-ref>
+ signal contains a channel with suppress_handler false,
+ one or more ChannelDispatchOperation objects are created for those
+ channels.</p>
+
+ <p>(If some channels in a NewChannels signal are in different bundles,
+ this is an error. The channel dispatcher SHOULD recover by treating
+ the NewChannels signal as if it had been several NewChannels signals
+ each containing one channel.)</p>
+
+ <p>First, the channel dispatcher SHOULD construct a list of all the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref>s
+ that could handle all the channels (based on their <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ property), ordered by
+ priority in some implementation-dependent way. If there are handlers
+ which could handle all the channels, one channel dispatch operation
+ SHOULD be created for all the channels. If there are not, one channel
+ dispatch operation SHOULD be created for each channel, each with
+ a list of channel handlers that could handle that channel.</p>
+
+ <p>If no handler at all can handle a channel, the channel dispatcher
+ SHOULD terminate that channel instead of creating a channel dispatcher
+ for it. It is RECOMMENDED that the channel dispatcher closes
+ the channels using <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.Destroyable.Destroy</tp:dbus-ref>
+ if supported, or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Close</tp:dbus-ref>
+ otherwise. As a special case, the channel dispatcher SHOULD NOT close
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">ContactList</tp:dbus-ref>
+ channels, and if Close fails, the channel dispatcher SHOULD ignore
+ that channel.</p>
+
+ <tp:rationale>
+ <p>ContactList channels are strange. We hope to replace them with
+ something better, such as an interface on the Connection, in a
+ future version of this specification.</p>
+ </tp:rationale>
+
+ <p>When listing channel handlers, priority SHOULD be given to
+ channel handlers that are already handling channels from the same
+ bundle.</p>
+
+ <p>If a handler with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">BypassApproval</tp:dbus-ref>
+ <code>= True</code> could handle all of the channels in the dispatch
+ operation, then the channel dispatcher SHOULD call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ on that handler, and (assuming the call succeeds) emit
+ <tp:member-ref>Finished</tp:member-ref> and stop processing those
+ channels without involving any approvers.</p>
+
+ <tp:rationale>
+ <p>Some channel types can be picked up "quietly" by an existing
+ channel handler. If a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>
+ channel is added to an existing bundle containing a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ channel, there shouldn't be
+ any approvers, flashing icons or notification bubbles, if the
+ the UI for the StreamedMedia channel can just add a text box
+ and display the message.</p>
+ </tp:rationale>
+
+ <p>Otherwise, the channel dispatcher SHOULD send the channel dispatch
+ operation to all relevant approvers (in parallel) and wait for an
+ approver to claim the channels or request that they are handled.
+ See
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Approver">AddDispatchOperation</tp:dbus-ref>
+ for more details on this.</p>
+
+ <p>Finally, if the approver requested it, the channel dispatcher SHOULD
+ send the channels to a handler.</p>
+ </tp:docstring>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" access="read" tp:type="DBus_Interface[]">
+ <tp:docstring>
+ A list of the extra interfaces provided by this channel dispatch
+ operation. This property cannot change.
+ </tp:docstring>
+ </property>
+
+ <property name="Connection" tp:name-for-bindings="Connection"
+ type="o" access="read">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ with which the <tp:member-ref>Channels</tp:member-ref> are
+ associated. The well-known bus name to use can be derived from
+ this object path by removing the leading '/' and replacing all
+ subsequent '/' by '.'. This property cannot change.
+ </tp:docstring>
+ </property>
+
+ <property name="Account" tp:name-for-bindings="Account"
+ type="o" access="read">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ with which the <tp:member-ref>Connection</tp:member-ref>
+ and <tp:member-ref>Channels</tp:member-ref> are
+ associated. This property cannot change.
+ </tp:docstring>
+ </property>
+
+ <property name="Channels" tp:name-for-bindings="Channels"
+ type="a(oa{sv})" access="read" tp:type="Channel_Details[]">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>s
+ to be dispatched, and their properties. Change notification is via
+ the <tp:member-ref>ChannelLost</tp:member-ref> signal (channels
+ cannot be added to this property, only removed).
+ </tp:docstring>
+ </property>
+
+ <signal name="ChannelLost" tp:name-for-bindings="Channel_Lost">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel has closed before it could be claimed or handled. If
+ this is emitted for the last remaining channel in a channel
+ dispatch operation, it MUST immediately be followed by
+ <tp:member-ref>Finished</tp:member-ref>.</p>
+
+ <p>This signal MUST NOT be emitted until all Approvers that were
+ invoked have returned (successfully or with an error) from
+ their <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Approver">AddDispatchOperation</tp:dbus-ref>
+ method.</p>
+
+ <tp:rationale>
+ <p>This means that Approvers can connect to the ChannelLost signal
+ in a race-free way. Non-approver processes that discover
+ a channel dispatch operation in some way (such as observers)
+ will have to follow the usual "connect to signals then recover
+ state" model - first connect to ChannelLost and
+ <tp:member-ref>Finished</tp:member-ref>,
+ then download <tp:member-ref>Channels</tp:member-ref> (and
+ on error, perhaps assume that the operation has already
+ Finished).</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Channel" type="o">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>
+ that closed.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Error" type="s" tp:type="DBus_Error_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of a D-Bus error indicating why the channel closed. If
+ no better reason can be found,
+ <code>org.freedesktop.Telepathy.Error.NotAvailable</code> MAY
+ be used as a fallback; this means that this error SHOULD NOT be
+ given any more specific meaning.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Message" type="s">
+ <tp:docstring>
+ A string associated with the D-Bus error.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="PossibleHandlers" tp:name-for-bindings="Possible_Handlers"
+ type="as" access="read" tp:type="DBus_Well_Known_Name[]">
+ <tp:docstring>
+ <p>The well known bus names (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>) of the possible
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref>s
+ for these channels. The channel dispatcher MUST place the most
+ preferred handlers first, according to some reasonable heuristic.
+ As a result, approvers SHOULD use the first handler by default.</p>
+
+ <p>The heuristic used to prioritize handlers SHOULD give a higher
+ priority to handlers that are already running.</p>
+
+ <tp:rationale>
+ <p>If, for instance, Empathy and Kopete have similar functionality,
+ and Empathy is running, we should prefer to send channels to it
+ rather than launching Kopete via service activation.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <method name="HandleWith" tp:name-for-bindings="Handle_With">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by an approver to accept a channel bundle and request that
+ the given handler be used to handle it.</p>
+
+ <p>If successful, this method will cause the ChannelDispatchOperation
+ object to disappear, emitting
+ <tp:member-ref>Finished</tp:member-ref>.</p>
+
+ <p>However, this method may fail because the dispatch has already been
+ completed and the object has already gone. If this occurs, it
+ indicates that another approver has asked for the bundle to be
+ handled by a particular handler. The approver MUST NOT attempt
+ to interact with the channels further in this case, unless it is
+ separately invoked as the handler.</p>
+
+ <p>Approvers which are also channel handlers SHOULD use
+ <tp:member-ref>Claim</tp:member-ref> instead
+ of HandleWith to request that they can handle a channel bundle
+ themselves.</p>
+
+ <p>(FIXME: list some possible errors)</p>
+
+ <p>If the channel handler raises an error from <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>,
+ this method
+ MAY respond by raising that same error, even if it is not
+ specifically documented here.</p>
+ </tp:docstring>
+
+ <arg direction="in" type="s" tp:type="DBus_Bus_Name" name="Handler">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>) of the channel
+ handler that should handle the channel, or the empty string
+ if the client has no preferred channel handler.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The selected handler is non-empty, but is not a syntactically
+ correct <tp:type>DBus_Bus_Name</tp:type> or does not start with
+ "<code>org.freedesktop.Telepathy.Client.</code>".
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The selected handler is temporarily unable to handle these
+ channels.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The selected handler is syntactically correct, but will never
+ be able to handle these channels (for instance because the channels
+ do not match its HandlerChannelFilter, or because HandleChannels
+ raised NotImplemented).
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYours">
+ <tp:docstring>
+ At the time that HandleWith was called, this dispatch operation was
+ processing an earlier call to HandleWith. The earlier call has
+ now succeeded, so some Handler nominated by another approver is
+ now responsible for the channels. In this situation, the second
+ call to HandleWith MUST NOT return until the first one has
+ returned successfully or unsuccessfully, and if the first call
+ to HandleChannels fails, the channel dispatcher SHOULD try to obey
+ the choice of Handler made by the second call to HandleWith.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Claim" tp:name-for-bindings="Claim">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by an approver to claim channels for handling
+ internally. If this method is called successfully, the process
+ calling this method becomes the handler for the channel, but
+ <em>does not</em> have the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ method called on it.</p>
+
+ <p>Clients that call Claim on channels but do not immediately
+ close them SHOULD implement the Handler interface and its
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandledChannels</tp:dbus-ref>
+ property.</p>
+
+ <p>Approvers wishing to reject channels MUST call this method to
+ claim ownership of them, and MUST NOT call
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+ on the channels unless/until this method returns successfully.</p>
+
+ <tp:rationale>
+ <p>The channel dispatcher can't know how best to close arbitrary
+ channel types, so it leaves it up to the approver to do so.
+ For instance, for Text channels it is necessary
+ to acknowledge any messages that have already been displayed to
+ the user first - ideally, the approver would display and then
+ acknowledge the messages - or to call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.Destroyable.Destroy</tp:dbus-ref>
+ if the destructive behaviour of that method is desired.</p>
+
+ <p>Similarly, an Approver for StreamedMedia channels can close the
+ channel with a reason (e.g. "busy") if desired. The channel
+ dispatcher, which is designed to have no specific knowledge
+ of particular channel types, can't do that.</p>
+ </tp:rationale>
+
+ <p>If successful, this method will cause the ChannelDispatchOperation
+ object to disappear, emitting
+ <tp:member-ref>Finished</tp:member-ref>, in the same way as for
+ <tp:member-ref>HandleWith</tp:member-ref>.</p>
+
+ <p>This method may fail because the dispatch operation has already
+ been completed. Again, see HandleWith for more details. The approver
+ MUST NOT attempt to interact with the channels further in this
+ case.</p>
+
+ <p>(FIXME: list some other possible errors)</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYours">
+ <tp:docstring>
+ At the time that Claim was called, this dispatch operation was
+ processing a call to HandleWith which has now succeeded, so
+ some Handler nominated by another approver is now responsible for
+ the channel.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="HandleWithTime" tp:name-for-bindings="Handle_With_Time">
+ <tp:added version="0.19.6">
+ At the time of writing, no released implementation of the
+ Channel Dispatcher implements this method; clients should fall
+ back to calling <tp:member-ref>HandleWith</tp:member-ref>.
+ </tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A variant of <tp:member-ref>HandleWith</tp:member-ref> allowing the
+ approver to pass an user action time. This timestamp will be passed
+ to the Handler when <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ is called.</p>
+ </tp:docstring>
+
+ <arg direction="in" type="s" tp:type="DBus_Bus_Name" name="Handler">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>) of the channel
+ handler that should handle the channel, or the empty string
+ if the client has no preferred channel handler.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" type="x" tp:type="User_Action_Timestamp" name="UserActionTime">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The selected handler is non-empty, but is not a syntactically
+ correct <tp:type>DBus_Bus_Name</tp:type> or does not start with
+ "<code>org.freedesktop.Telepathy.Client.</code>".
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The selected handler is temporarily unable to handle these
+ channels.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The selected handler is syntactically correct, but will never
+ be able to handle these channels (for instance because the channels
+ do not match its HandlerChannelFilter, or because HandleChannels
+ raised NotImplemented).
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYours">
+ <tp:docstring>
+ At the time that HandleWith was called, this dispatch operation was
+ processing an earlier call to HandleWith. The earlier call has
+ now succeeded, so some Handler nominated by another approver is
+ now responsible for the channels. In this situation, the second
+ call to HandleWith MUST NOT return until the first one has
+ returned successfully or unsuccessfully, and if the first call
+ to HandleChannels fails, the channel dispatcher SHOULD try to obey
+ the choice of Handler made by the second call to HandleWith.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="Finished" tp:name-for-bindings="Finished">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when this dispatch operation finishes. The dispatch
+ operation is no longer present and further methods must not be
+ called on it.</p>
+
+ <p>Approvers that have a user interface SHOULD stop notifying the user
+ about the channels in response to this signal; they MAY assume that
+ on errors, they would have received
+ <tp:member-ref>ChannelLost</tp:member-ref> first.</p>
+
+ <p>Its object path SHOULD NOT be reused for a subsequent dispatch
+ operation; the ChannelDispatcher MUST choose object paths
+ in a way that avoids immediate re-use.</p>
+
+ <tp:rationale>
+ <p>Otherwise, clients might accidentally call
+ <tp:member-ref>HandleWith</tp:member-ref> or
+ <tp:member-ref>Claim</tp:member-ref> on a new dispatch operation
+ instead of the one they intended to handle.</p>
+ </tp:rationale>
+
+ <p>This signal MUST NOT be emitted until all Approvers that were
+ invoked have returned (successfully or with an error) from
+ their <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Approver">AddDispatchOperation</tp:dbus-ref>
+ method.</p>
+
+ <tp:rationale>
+ <p>This means that Approvers can connect to the ChannelLost signal
+ in a race-free way. Non-approver processes that discover
+ a channel dispatch operation in some way (such as observers)
+ will have to follow the usual "connect to signals then recover
+ state" model - first connect to
+ <tp:member-ref>ChannelLost</tp:member-ref> and
+ Finished, then download <tp:member-ref>Channels</tp:member-ref>
+ (and on error, perhaps assume that the operation has already
+ Finished).</p>
+ </tp:rationale>
+ </tp:docstring>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Dispatcher.xml b/qt4/spec/Channel_Dispatcher.xml
new file mode 100644
index 000000000..2dd000b40
--- /dev/null
+++ b/qt4/spec/Channel_Dispatcher.xml
@@ -0,0 +1,595 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Dispatcher"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ChannelDispatcher">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The channel dispatcher is responsible for responding to new
+ channels and launching client processes to handle them. It also
+ provides functionality for client processes to request that new
+ channels are created.</p>
+
+ <p>If a channel dispatcher is running, it is responsible for dispatching
+ new channels on all
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>s
+ created by the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">AccountManager</tp:dbus-ref>.
+ Connections not created by the AccountManager are outside the scope
+ of the channel dispatcher.</p>
+
+ <tp:rationale>
+ <p>Connections created by standalone Telepathy clients
+ that do not intend to interact with the channel dispatcher
+ should be ignored - otherwise, the channel dispatcher would try
+ to launch handlers for channels that the standalone client
+ was already handling internally.</p>
+ </tp:rationale>
+
+ <p>The current channel dispatcher is defined to be the process that
+ owns the well-known bus name
+ <code>org.freedesktop.Telepathy.ChannelDispatcher</code> on
+ the session bus. This process MUST export an object with this
+ interface at the object path
+ <code>/org/freedesktop/Telepathy/ChannelDispatcher</code>.</p>
+
+ <p>Until a mechanism exists for making a reasonable automatic choice
+ of ChannelDispatcher implementation, implementations SHOULD NOT
+ register as an activatable service for the ChannelDispatcher's
+ well-known bus name. Instead, it is RECOMMENDED that some component
+ of the user's session will select and activate a particular
+ implementation, and that other Telepathy-enabled programs
+ can detect whether channel request/dispatch functionality is available
+ by checking whether the ChannelDispatcher's well-known name is in use
+ at runtime.</p>
+
+ <p>There are three categories of client process defined by this
+ specification:</p>
+
+ <dl>
+ <dt><tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Observer</tp:dbus-ref></dt>
+ <dd><p>Observers monitor the creation of new channels. This
+ functionality can be used for things like message logging.
+ All observers are notified simultaneously.</p></dd>
+
+ <dt><tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Approver</tp:dbus-ref></dt>
+ <dd>
+ <p>Approvers notify the user that new channels have been created,
+ and also select which channel handler will be used for the channel,
+ either by asking the user or by choosing the most appropriate
+ channel handler.</p>
+ </dd>
+
+ <dt><tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref></dt>
+ <dd>
+ <p>Each new channel or set of channels is passed to exactly one
+ handler as its final destination. A typical channel handler is a
+ user interface process handling channels of a particular type.</p>
+ </dd>
+ </dl>
+ </tp:docstring>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" access="read" tp:type="DBus_Interface[]">
+ <tp:docstring>
+ A list of the extra interfaces provided by this channel dispatcher.
+ </tp:docstring>
+ </property>
+
+ <method name="CreateChannel" tp:name-for-bindings="Create_Channel">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Equivalent to calling
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref> with an empty
+ <var>Hints</var> parameter.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="Account" type="o">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ for which the new channel is to be created.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Requested_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="User_Action_Time" type="x"
+ tp:type="User_Action_Timestamp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Preferred_Handler" type="s"
+ tp:type="DBus_Well_Known_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ <tp:changed version="0.19.0">
+ Previously, the spec didn't say that this should disregard the
+ handler's filter. This has been implemented since
+ telepathy-mission-control 5.3.2.
+ </tp:changed>
+ </arg>
+
+ <arg direction="out" name="Request" type="o">
+ <tp:docstring>
+ A
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The Preferred_Handler is syntactically invalid or does
+ not start with <code>org.freedesktop.Telepathy.Client.</code>,
+ the Account does not exist, or one of the Requested_Properties
+ is invalid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+
+ </method>
+
+ <method name="EnsureChannel" tp:name-for-bindings="Ensure_Channel">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Equivalent to calling
+ <tp:member-ref>EnsureChannelWithHints</tp:member-ref> with an empty
+ <var>Hints</var> parameter.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="Account" type="o">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ for which the new channel is to be created.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Requested_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="User_Action_Time" type="x"
+ tp:type="User_Action_Timestamp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Preferred_Handler" type="s"
+ tp:type="DBus_Well_Known_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable. The behaviour and rationale are the same as for the
+ corresponding parameter to
+ <tp:member-ref>EnsureChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Request" type="o">
+ <tp:docstring>
+ A
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The Preferred_Handler is syntactically invalid or does
+ not start with <code>org.freedesktop.Telepathy.Client.</code>,
+ the Account does not exist, or one of the Requested_Properties
+ is invalid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+
+ </method>
+
+ <method name="CreateChannelWithHints"
+ tp:name-for-bindings="Create_Channel_With_Hints">
+ <tp:added version="0.21.5">
+ Support for this method is indicated by the
+ <tp:member-ref>SupportsRequestHints</tp:member-ref> property.
+ Clients MUST recover from this method being unsupported by falling back
+ to <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">CreateChannel</tp:dbus-ref>.
+ </tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Start a request to create a channel. This initially just creates a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object, which can be used to continue the request and track its
+ success or failure.</p>
+
+ <tp:rationale>
+ <p>The request can take a long time - in the worst case, the
+ channel dispatcher has to ask the account manager to put the
+ account online, the account manager has to ask the operating
+ system to obtain an Internet connection, and the operating
+ system has to ask the user whether to activate an Internet
+ connection using an on-demand mechanism like dialup.</p>
+
+ <p>This means that using a single D-Bus method call and response
+ to represent the whole request will tend to lead to that call
+ timing out, which is not the behaviour we want.</p>
+ </tp:rationale>
+
+ <p>If this method is called for an Account that is disabled, invalid
+ or otherwise unusable, no error is signalled until
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Proceed</tp:dbus-ref>
+ is called, at which point
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Failed</tp:dbus-ref>
+ is emitted with an appropriate error.</p>
+
+ <tp:rationale>
+ <p>This means there's only one code path for errors, apart from
+ InvalidArgument for "that request makes no sense".</p>
+
+ <p>It also means that the request will proceed if the account is
+ enabled after calling CreateChannel, but before calling
+ Proceed.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Account" type="o">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ for which the new channel is to be created.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Requested_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties. This has the same
+ semantics as the corresponding parameter to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>.
+ </p>
+
+ <p>Certain properties will not necessarily make sense in this
+ dictionary: for instance,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ can only be given if the requester is able to interact with a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ to the desired account.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="User_Action_Time" type="x"
+ tp:type="User_Action_Timestamp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">UserActionTime</tp:dbus-ref>
+ property will be set to this value, and it will eventually be
+ passed as the <code>User_Action_Time</code> parameter of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Preferred_Handler" type="s"
+ tp:type="DBus_Well_Known_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable. The channel dispatcher SHOULD dispatch as many as
+ possible of the resulting channels (ideally, all of them)
+ to that handler—irrespective of whether that handler's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ matches the channel—and SHOULD remember the preferred handler
+ so it can try to dispatch subsequent channels in the same bundle
+ to the same handler.</p>
+
+ <tp:rationale>
+ <p>This must be the well-known bus name, not the unique name,
+ to ensure that all handlers do indeed have the Client API,
+ and the Client object on the handler can be located easily.</p>
+
+ <p>This is partly so the channel dispatcher can call
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ on it, and partly so the channel dispatcher
+ can recover state if it crashes and is restarted.</p>
+
+ <p>The filter should be disregarded for ease of use of this
+ interface: clients will usually use this argument to request
+ channels be sent to themself, and this should trump the filter
+ not matching. This also allows a client to become the handler
+ for a channel produced by one of its own requests, while not
+ being a candidate to handle other channels of that type.</p>
+ </tp:rationale>
+
+ <p>If this is a well-known bus name and the handler has the
+ Requests interface, the channel dispatcher SHOULD
+ call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Interface.Requests">AddRequest</tp:dbus-ref>
+ on that Handler after this method has returned.</p>
+
+ <tp:rationale>
+ <p>This ordering allows a Handler which calls CreateChannel with
+ itself as the preferred handler to associate the call to
+ AddRequest with that call.</p>
+ </tp:rationale>
+
+ <p>This is copied to the ChannelRequest that is returned,
+ as the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">PreferredHandler</tp:dbus-ref>
+ property.</p>
+ </tp:docstring>
+
+ <tp:changed version="0.19.0">
+ Previously, the spec didn't say that this should disregard the
+ handler's filter. This has been implemented since
+ telepathy-mission-control 5.3.2.
+ </tp:changed>
+ </arg>
+
+ <arg direction="in" name="Hints" type="a{sv}">
+ <tp:docstring>
+ <p>Additional information about the channel request, which will be
+ used as the value for the resulting request's <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">Hints</tp:dbus-ref>
+ property.</p>
+
+ <tp:rationale>
+ <p>See the Hints property's documentation for rationale.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Request" type="o">
+ <tp:docstring>
+ A
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The Preferred_Handler is syntactically invalid or does
+ not start with <code>org.freedesktop.Telepathy.Client.</code>,
+ the Account does not exist, or one of the Requested_Properties
+ is invalid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+
+ </method>
+
+ <method name="EnsureChannelWithHints"
+ tp:name-for-bindings="Ensure_Channel_With_Hints">
+ <tp:added version="0.21.5">
+ Support for this method is indicated by the
+ <tp:member-ref>SupportsRequestHints</tp:member-ref> property.
+ Clients MUST recover from this method being unsupported by falling back
+ to <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">EnsureChannel</tp:dbus-ref>.
+ </tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Start a request to ensure that a channel exists, creating it if
+ necessary. This initially just creates a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object, which can be used to continue the request and track its
+ success or failure.</p>
+
+ <p>If this method is called for an Account that is disabled, invalid
+ or otherwise unusable, no error is signalled until
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Proceed</tp:dbus-ref>
+ is called, at which point
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Failed</tp:dbus-ref>
+ is emitted with an appropriate error.</p>
+
+ <tp:rationale>
+ <p>The rationale is as for <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.ChannelDispatcher'>CreateChannelWithHints</tp:dbus-ref>.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Account" type="o">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ for which the new channel is to be created.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Requested_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties. This has the same
+ semantics as the corresponding parameter to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.EnsureChannel</tp:dbus-ref>.
+ </p>
+
+ <p>Certain properties will not necessarily make sense in this
+ dictionary: for instance,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ can only be given if the requester is able to interact with a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ to the desired account.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="User_Action_Time" type="x"
+ tp:type="User_Action_Timestamp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Preferred_Handler" type="s"
+ tp:type="DBus_Well_Known_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable. The behaviour and rationale are the same as for the
+ corresponding parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>, except
+ as noted here.</p>
+
+ <p>If any new channels are created in response to this
+ request, the channel dispatcher SHOULD dispatch as many as
+ possible of the resulting channels (ideally, all of them)
+ to that handler, and SHOULD remember the preferred handler
+ so it can try to dispatch subsequent channels in the same bundle
+ to the same handler. If the requested channel already exists (that
+ is, <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.EnsureChannel</tp:dbus-ref>
+ returns <code>Yours=False</code>) then the channel dispatcher
+ SHOULD re-dispatch the channel to its existing handler, and MUST
+ NOT dispatch it to this client (unless it is the existing handler);
+ the request is still deemed to have succeeded in this case.</p>
+
+ <tp:rationale>
+ <p>An address book application, for example, might call <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.ChannelDispatcher'>EnsureChannel</tp:dbus-ref>
+ to ensure that a text channel with a particular contact is
+ displayed to the user; it does not care whether a new channel was
+ made. An IM client might call <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.ChannelDispatcher'>EnsureChannel</tp:dbus-ref>
+ in response to the user double-clicking an entry in the contact
+ list, with itself as the <code>Preferred_Handler</code>; if the
+ user already has a conversation with that contact in another
+ application, they would expect the existing window to be
+ presented, rather than their double-click leading to an error
+ message. So the request should succeed, even if its
+ <code>Preferred_Handler</code> is not used.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Hints" type="a{sv}">
+ <tp:docstring>
+ Additional information about the channel request, which will be used
+ as the value for the resulting request's <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">Hints</tp:dbus-ref>
+ property.</tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Request" type="o">
+ <tp:docstring>
+ A
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The Preferred_Handler is syntactically invalid or does
+ not start with <code>org.freedesktop.Telepathy.Client.</code>,
+ the Account does not exist, or one of the Requested_Properties
+ is invalid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+
+ </method>
+
+ <property name="SupportsRequestHints"
+ tp:name-for-bindings="Supports_Request_Hints"
+ type="b" access="read">
+ <tp:added version="0.21.5"/>
+ <tp:docstring>
+ If <code>True</code>, the channel dispatcher is new enough to support
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref> and
+ <tp:member-ref>EnsureChannelWithHints</tp:member-ref>, in addition
+ to the older <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">CreateChannel</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">EnsureChannel</tp:dbus-ref>
+ methods, and also new enough to emit <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">SucceededWithChannel</tp:dbus-ref>
+ before the older <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">Succeeded</tp:dbus-ref> signal.
+ If <code>False</code> or missing, only the metadata-less
+ variants are supported.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Dispatcher_Future.xml b/qt4/spec/Channel_Dispatcher_Future.xml
new file mode 100644
index 000000000..701b42470
--- /dev/null
+++ b/qt4/spec/Channel_Dispatcher_Future.xml
@@ -0,0 +1,377 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Dispatcher_Future"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ChannelDispatcher.FUTURE"
+ tp:causes-havoc="a staging area for future functionality">
+
+ <tp:requires interface="org.freedesktop.Telepathy.ChannelDispatcher"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface contains functionality which we intend to incorporate
+ into the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>
+ interface in future. It should be considered to
+ be conceptually part of the core ChannelDispatcher interface, but without
+ API or ABI guarantees.</p>
+ </tp:docstring>
+
+ <method name="CreateChannelWithHints"
+ tp:name-for-bindings="Create_Channel_With_Hints">
+ <tp:added version="0.19.12">
+ Support for this method is indicated by the
+ <tp:member-ref>SupportsRequestHints</tp:member-ref> property.
+ Clients MUST recover from this method being unsupported by falling back
+ to <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">CreateChannel</tp:dbus-ref>.
+ </tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Start a request to create a channel. This initially just creates a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object, which can be used to continue the request and track its
+ success or failure.</p>
+
+ <tp:rationale>
+ <p>The request can take a long time - in the worst case, the
+ channel dispatcher has to ask the account manager to put the
+ account online, the account manager has to ask the operating
+ system to obtain an Internet connection, and the operating
+ system has to ask the user whether to activate an Internet
+ connection using an on-demand mechanism like dialup.</p>
+
+ <p>This means that using a single D-Bus method call and response
+ to represent the whole request will tend to lead to that call
+ timing out, which is not the behaviour we want.</p>
+ </tp:rationale>
+
+ <p>If this method is called for an Account that is disabled, invalid
+ or otherwise unusable, no error is signalled until
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Proceed</tp:dbus-ref>
+ is called, at which point
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Failed</tp:dbus-ref>
+ is emitted with an appropriate error.</p>
+
+ <tp:rationale>
+ <p>This means there's only one code path for errors, apart from
+ InvalidArgument for "that request makes no sense".</p>
+
+ <p>It also means that the request will proceed if the account is
+ enabled after calling CreateChannel, but before calling
+ Proceed.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Account" type="o">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ for which the new channel is to be created.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Requested_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties. This has the same
+ semantics as the corresponding parameter to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>.
+ </p>
+
+ <p>Certain properties will not necessarily make sense in this
+ dictionary: for instance,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ can only be given if the requester is able to interact with a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ to the desired account.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="User_Action_Time" type="x"
+ tp:type="User_Action_Timestamp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">UserActionTime</tp:dbus-ref>
+ property will be set to this value, and it will eventually be
+ passed as the <code>User_Action_Time</code> parameter of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Preferred_Handler" type="s"
+ tp:type="DBus_Well_Known_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable. The channel dispatcher SHOULD dispatch as many as
+ possible of the resulting channels (ideally, all of them)
+ to that handler—irrespective of whether that handler's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ matches the channel—and SHOULD remember the preferred handler
+ so it can try to dispatch subsequent channels in the same bundle
+ to the same handler.</p>
+
+ <tp:rationale>
+ <p>This must be the well-known bus name, not the unique name,
+ to ensure that all handlers do indeed have the Client API,
+ and the Client object on the handler can be located easily.</p>
+
+ <p>This is partly so the channel dispatcher can call
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ on it, and partly so the channel dispatcher
+ can recover state if it crashes and is restarted.</p>
+
+ <p>The filter should be disregarded for ease of use of this
+ interface: clients will usually use this argument to request
+ channels be sent to themself, and this should trump the filter
+ not matching. This also allows a client to become the handler
+ for a channel produced by one of its own requests, while not
+ being a candidate to handle other channels of that type.</p>
+ </tp:rationale>
+
+ <p>If this is a well-known bus name and the handler has the
+ Requests interface, the channel dispatcher SHOULD
+ call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Interface.Requests">AddRequest</tp:dbus-ref>
+ on that Handler after this method has returned.</p>
+
+ <tp:rationale>
+ <p>This ordering allows a Handler which calls CreateChannel with
+ itself as the preferred handler to associate the call to
+ AddRequest with that call.</p>
+ </tp:rationale>
+
+ <p>This is copied to the ChannelRequest that is returned,
+ as the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">PreferredHandler</tp:dbus-ref>
+ property.</p>
+ </tp:docstring>
+
+ <tp:changed version="0.19.0">
+ Previously, the spec didn't say that this should disregard the
+ handler's filter. This has been implemented since
+ telepathy-mission-control 5.3.2.
+ </tp:changed>
+ </arg>
+
+ <arg direction="in" name="Hints" type="a{sv}">
+ <tp:docstring>
+ <p>Additional information about the channel request, which will be
+ used as the value for the resulting request's <tp:dbus-ref
+ namespace="ofdT.ChannelRequest.FUTURE">Hints</tp:dbus-ref>
+ property, but will not otherwise be interpreted by the Channel
+ Dispatcher.</p>
+
+ <tp:rationale>
+ <p>See the Hints property's documentation for rationale.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Request" type="o">
+ <tp:docstring>
+ A
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The Preferred_Handler is syntactically invalid or does
+ not start with <code>org.freedesktop.Telepathy.Client.</code>,
+ the Account does not exist, or one of the Requested_Properties
+ is invalid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+
+ </method>
+
+ <method name="EnsureChannelWithHints"
+ tp:name-for-bindings="Ensure_Channel_With_Hints">
+ <tp:added version="0.19.12">
+ Support for this method is indicated by the
+ <tp:member-ref>SupportsRequestHints</tp:member-ref> property.
+ Clients MUST recover from this method being unsupported by falling back
+ to <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">EnsureChannel</tp:dbus-ref>.
+ </tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Start a request to ensure that a channel exists, creating it if
+ necessary. This initially just creates a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object, which can be used to continue the request and track its
+ success or failure.</p>
+
+ <p>If this method is called for an Account that is disabled, invalid
+ or otherwise unusable, no error is signalled until
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Proceed</tp:dbus-ref>
+ is called, at which point
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest.Failed</tp:dbus-ref>
+ is emitted with an appropriate error.</p>
+
+ <tp:rationale>
+ <p>The rationale is as for <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.ChannelDispatcher'>CreateChannel</tp:dbus-ref>.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Account" type="o">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ for which the new channel is to be created.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Requested_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties. This has the same
+ semantics as the corresponding parameter to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.EnsureChannel</tp:dbus-ref>.
+ </p>
+
+ <p>Certain properties will not necessarily make sense in this
+ dictionary: for instance,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ can only be given if the requester is able to interact with a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ to the desired account.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="User_Action_Time" type="x"
+ tp:type="User_Action_Timestamp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.</p>
+
+ <p>This parameter is used in the same way as the corresponding
+ parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Preferred_Handler" type="s"
+ tp:type="DBus_Well_Known_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable. The behaviour and rationale are the same as for the
+ corresponding parameter to
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref>, except
+ as noted here.</p>
+
+ <p>If any new channels are created in response to this
+ request, the channel dispatcher SHOULD dispatch as many as
+ possible of the resulting channels (ideally, all of them)
+ to that handler, and SHOULD remember the preferred handler
+ so it can try to dispatch subsequent channels in the same bundle
+ to the same handler. If the requested channel already exists (that
+ is, <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.EnsureChannel</tp:dbus-ref>
+ returns <code>Yours=False</code>) then the channel dispatcher
+ SHOULD re-dispatch the channel to its existing handler, and MUST
+ NOT dispatch it to this client (unless it is the existing handler);
+ the request is still deemed to have succeeded in this case.</p>
+
+ <tp:rationale>
+ <p>An address book application, for example, might call <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.ChannelDispatcher'>EnsureChannel</tp:dbus-ref>
+ to ensure that a text channel with a particular contact is
+ displayed to the user; it does not care whether a new channel was
+ made. An IM client might call <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.ChannelDispatcher'>EnsureChannel</tp:dbus-ref>
+ in response to the user double-clicking an entry in the contact
+ list, with itself as the <code>Preferred_Handler</code>; if the
+ user already has a conversation with that contact in another
+ application, they would expect the existing window to be
+ presented, rather than their double-click leading to an error
+ message. So the request should succeed, even if its
+ <code>Preferred_Handler</code> is not used.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Hints" type="a{sv}">
+ <tp:docstring>
+ Additional information about the channel request, which will be used
+ as the value for the resulting request's <tp:dbus-ref
+ namespace="ofdT.ChannelRequest.FUTURE">Hints</tp:dbus-ref>
+ property.</tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Request" type="o">
+ <tp:docstring>
+ A
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The Preferred_Handler is syntactically invalid or does
+ not start with <code>org.freedesktop.Telepathy.Client.</code>,
+ the Account does not exist, or one of the Requested_Properties
+ is invalid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+
+ </method>
+
+ <property name="SupportsRequestHints"
+ tp:name-for-bindings="Supports_Request_Hints"
+ type="b" access="read">
+ <tp:added version="0.19.12"/>
+ <tp:docstring>
+ If <code>True</code>, the channel dispatcher is new enough to support
+ <tp:member-ref>CreateChannelWithHints</tp:member-ref> and
+ <tp:member-ref>EnsureChannelWithHints</tp:member-ref>, in addition
+ to the older <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">CreateChannel</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">EnsureChannel</tp:dbus-ref>.
+ methods. If <code>False</code> or missing, only the metadata-less
+ variants are supported.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Dispatcher_Interface_Messages.xml b/qt4/spec/Channel_Dispatcher_Interface_Messages.xml
new file mode 100644
index 000000000..f6cfd04a7
--- /dev/null
+++ b/qt4/spec/Channel_Dispatcher_Interface_Messages.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Dispatcher_Interface_Messages"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright (C) 2011 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2011 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ChannelDispatcher.Interface.Messages.DRAFT"
+ tp:causes-havoc="not yet final">
+
+ <tp:requires interface="org.freedesktop.Telepathy.ChannelDispatcher"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface allows users of the ChannelDispatcher to send one-off
+ text messages to a contact, identified by account and target ID.</p>
+
+ <tp:rationale>
+ <p>This enables entities other than the main UI to send one-off messages
+ to a contact.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <method name="SendMessage" tp:name-for-bindings="Send_Message">
+ <arg direction="in" name="Account" type="o"/>
+ <arg direction="in" name="TargetID" type="s"/>
+ <arg direction="in" name="Message" type="aa{sv}" tp:type="Message_Part[]"/>
+ <arg direction="in" name="Flags" type="u"/>
+ <arg direction="out" name="Token" type="s"/>
+ </method>
+
+ </interface>
+</node>
+
diff --git a/qt4/spec/Channel_Dispatcher_Interface_Operation_List.xml b/qt4/spec/Channel_Dispatcher_Interface_Operation_List.xml
new file mode 100644
index 000000000..be06f5caa
--- /dev/null
+++ b/qt4/spec/Channel_Dispatcher_Interface_Operation_List.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Dispatcher_Interface_Operation_List"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ChannelDispatcher.Interface.OperationList">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.ChannelDispatcher"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface allows users of the ChannelDispatcher to enumerate
+ all the pending dispatch operations, with change notification.</p>
+
+ <tp:rationale>
+ <p>The existence of the
+ <tp:member-ref>DispatchOperations</tp:member-ref> property allows a
+ newly started approver to pick up existing dispatch operations.</p>
+
+ <p>This is on a separate interface so clients that aren't interested
+ in doing this aren't woken up by its signals.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:struct name="Dispatch_Operation_Details"
+ array-name="Dispatch_Operation_Details_List">
+
+ <tp:docstring>
+ Details of a channel dispatch operation.
+ </tp:docstring>
+
+ <tp:member name="Channel_Dispatch_Operation" type="o">
+ <tp:docstring>
+ The object path of the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation</tp:dbus-ref>.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Properties of the channel dispatch operation.</p>
+
+ <p>Connection managers MUST NOT include properties in this mapping
+ if their values can change. Clients MUST ignore properties
+ that appear in this mapping if their values can change.</p>
+
+ <tp:rationale>
+ <p>The rationale is the same as for
+ <tp:type>Channel_Details</tp:type>.</p>
+ </tp:rationale>
+
+ <p>Each dictionary MUST contain at least the following keys:</p>
+ <ul>
+ <li><tp:dbus-ref>org.freedesktop.Telepathy.ChannelDispatchOperation.Interfaces</tp:dbus-ref></li>
+ <li><tp:dbus-ref>org.freedesktop.Telepathy.ChannelDispatchOperation.Connection</tp:dbus-ref></li>
+ <li><tp:dbus-ref>org.freedesktop.Telepathy.ChannelDispatchOperation.Account</tp:dbus-ref></li>
+ <li><tp:dbus-ref>org.freedesktop.Telepathy.ChannelDispatchOperation.PossibleHandlers</tp:dbus-ref></li>
+ </ul>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property
+ name="DispatchOperations" tp:name-for-bindings="Dispatch_Operations"
+ type="a(oa{sv})" tp:type="Dispatch_Operation_Details[]" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of ChannelDispatchOperation objects currently being
+ processed. Change notification is via the
+ <tp:member-ref>NewDispatchOperation</tp:member-ref> and
+ <tp:member-ref>DispatchOperationFinished</tp:member-ref> signals.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="NewDispatchOperation"
+ tp:name-for-bindings="New_Dispatch_Operation">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a dispatch operation is added to
+ <tp:member-ref>DispatchOperations</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Dispatch_Operation" type="o">
+ <tp:docstring>
+ The dispatch operation that was created.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Properties"
+ type="a{sv}" tp:type="Qualified_Property_Value_Map">
+ <tp:docstring>
+ The same properties that would appear in the Properties member of
+ <tp:type>Dispatch_Operation_Details</tp:type>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="DispatchOperationFinished"
+ tp:name-for-bindings="Dispatch_Operation_Finished">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Emitted when a dispatch operation finishes (i.e. exactly once per
+ emission of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation.Finished</tp:dbus-ref>).
+
+ <tp:rationale>
+ Strictly speaking this is redundant with
+ ChannelDispatchOperation.Finished, but it provides full
+ change-notification for the
+ <tp:member-ref>DispatchOperations</tp:member-ref> property.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Dispatch_Operation" type="o">
+ <tp:docstring>
+ The dispatch operation that was closed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Future.xml b/qt4/spec/Channel_Future.xml
new file mode 100644
index 000000000..5bbca17b1
--- /dev/null
+++ b/qt4/spec/Channel_Future.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Future"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2008 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.FUTURE"
+ tp:causes-havoc="a staging area for future Channel functionality">
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface contains functionality which we intend to incorporate
+ into the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref> interface
+ in future. It should be considered to
+ be conceptually part of the core Channel interface, but without
+ API or ABI guarantees.</p>
+
+ <tp:rationale>
+ <p>If we add new functionality to the Channel interface, libraries
+ that use generated code (notably telepathy-glib) will have it as
+ part of their ABI forever, meaning we can't make incompatible
+ changes. By using this interface as a staging area for future
+ Channel functionality, we can try out new properties, signals
+ and methods as application-specific extensions, then merge them
+ into the core Channel interface when we have enough implementation
+ experience to declare them to be stable.</p>
+
+ <p>The name is by analogy to Python's <code>__future__</code>
+ pseudo-module.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <property name="Bundle" tp:name-for-bindings="Bundle"
+ type="o" access="read">
+ <tp:added version="0.17.9">(in Channel.FUTURE
+ pseudo-interface)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelBundle.DRAFT</tp:dbus-ref>
+ to which this channel belongs.</p>
+
+ <p>A channel's Bundle property can never change.</p>
+
+ <p>Older connection managers might not have this property. Clients
+ (particularly the channel dispatcher) SHOULD recover by considering
+ each channel to be in a bundle containing only that channel,
+ distinct from all other bundles, which has no additional
+ interfaces.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Handler.xml b/qt4/spec/Channel_Handler.xml
new file mode 100644
index 000000000..edf975e4d
--- /dev/null
+++ b/qt4/spec/Channel_Handler.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Handler" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2007-2008 Collabora Limited</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.ChannelHandler">
+ <tp:deprecated version="0.17.23">
+ Clients should implement <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Client.Handler</tp:dbus-ref>
+ instead.
+ </tp:deprecated>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface exported by Mission Control 4 client applications which
+ are able to handle incoming channels.</p>
+ </tp:docstring>
+ <tp:added version="0.17.0"/>
+
+ <method name="HandleChannel" tp:name-for-bindings="Handle_Channel">
+ <tp:docstring>
+ Called when a channel handler should handle a new channel.
+ </tp:docstring>
+ <tp:added version="0.17.0"/>
+
+ <arg direction="in" type="s" name="Bus_Name" tp:type="DBus_Bus_Name">
+ <tp:docstring>
+ The bus name of the connection and channel
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" type="o" name="Connection">
+ <tp:docstring>
+ The object-path of the connection that owns the channel
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" type="s" tp:type="DBus_Interface" name="Channel_Type">
+ <tp:docstring>
+ The channel type
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" type="o" name="Channel">
+ <tp:docstring>
+ The object-path of the channel
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" type="u" tp:type="Handle_Type" name="Handle_Type">
+ <tp:docstring>The type of the handle that the channel communicates
+ with, or 0 if there is no associated handle</tp:docstring>
+ </arg>
+
+ <arg direction="in" type="u" tp:type="Handle" name="Handle">
+ <tp:docstring>The handle that the channel communicates with,
+ or 0 if there is no associated handle</tp:docstring>
+ </arg>
+
+ <!-- FIXME: possible errors? -->
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
+
diff --git a/qt4/spec/Channel_Interface_Addressing.xml b/qt4/spec/Channel_Interface_Addressing.xml
new file mode 100644
index 000000000..494fd7bf0
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Addressing.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Addressing" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Limited</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Addressing.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.12">(as draft)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface provides properties that can be used for
+ requesting channels through different contact addressing
+ schemes like vCard addresses or URIs.
+ </p>
+ </tp:docstring>
+
+ <property name="TargetVCardField" type="s" access="read"
+ tp:name-for-bindings="Target_VCard_Field">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The vCard field, normalized to lower case,
+ <tp:member-ref>TargetVCardAddress</tp:member-ref> refers to.</p>
+
+ <p>The <code>url</code> vCard field MUST NOT appear here; see
+ <tp:member-ref>TargetURI</tp:member-ref> instead.</p>
+
+ <tp:rationale>
+ <p>In practice, protocols have a limited set of URI
+ schemes that make sense to resolve as a contact.</p>
+ </tp:rationale>
+
+ <p>If this is omitted from a request,
+ <tp:member-ref>TargetVCardAddress</tp:member-ref> MUST be
+ omitted as well.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="TargetURIScheme" type="s" access="read"
+ tp:name-for-bindings="Target_URI_Scheme">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The URI scheme used in <tp:member-ref>TargetURI</tp:member-ref></p>
+
+ <tp:rationale>
+ <p>While this seems redundant, since the scheme is included in
+ <tp:member-ref>TargetURI</tp:member-ref>, it exists for constructing
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>
+ that support a limited set of URI schemes.</p>
+ </tp:rationale>
+
+ <p>If this is omitted from a request,
+ <tp:member-ref>TargetURI</tp:member-ref> MUST be
+ omitted as well.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="TargetVCardAddress" type="s" access="read"
+ tp:name-for-bindings="Target_VCard_Address">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The vCard address of the Channel's target.</p>
+
+ <p>If this is present in a channel request,
+ <tp:member-ref>TargetVCardField</tp:member-ref>
+ MUST be present, and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>,
+ and <tp:member-ref>TargetURI</tp:member-ref> MUST NOT be present.
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ must either not be present or set to Handle_Type_Contact.
+ The request MUST fail with error InvalidHandle, without
+ side-effects, if the requested vCard address cannot be found.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="TargetURI" type="s" access="read"
+ tp:name-for-bindings="Target_URI">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The URI of the Channel's target. The URI's scheme (i.e. the
+ part before the first colon) MUST be identical to
+ <tp:member-ref>TargetURIScheme</tp:member-ref>.</p>
+
+ <p>If this is present in a channel request,
+ <tp:member-ref>TargetVCardField</tp:member-ref>
+ MUST be present, and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>,
+ and <tp:member-ref>TargetVCardAddress</tp:member-ref> MUST NOT be
+ present.
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ must either not be present or set to Handle_Type_Contact.
+ The request MUST fail with error InvalidHandle, without
+ side-effects, if the requested vCard address cannot be found.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Anonymity.xml b/qt4/spec/Channel_Interface_Anonymity.xml
new file mode 100644
index 000000000..ef3a3b85d
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Anonymity.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Anonymity"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2010 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Anonymity">
+ <tp:added version="0.19.7">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for requesting the anonymity modes of a channel
+ (as defined in <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Anonymity</tp:dbus-ref>).</p>
+ </tp:docstring>
+
+ <property name="AnonymityModes" type="u" tp:type="Anonymity_Mode_Flags"
+ access="read" tp:name-for-bindings="Anonymity_Modes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The list of initially requested anonymity modes on the channel. This
+ MUST NOT change, and is Requestable.
+ </tp:docstring>
+ </property>
+
+ <property name="AnonymityMandatory" type="b" access="read"
+ tp:name-for-bindings="Anonymity_Mandatory">
+ <tp:docstring>
+ Whether or not the anonymity settings are required for this channel.
+ This MUST NOT change, and is Requestable.
+ </tp:docstring>
+ </property>
+
+ <property name="AnonymousID" type="s" access="read"
+ tp:name-for-bindings="Anonymous_ID">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This is the ID that the remote user of the channel MAY see
+ (assuming there's a single ID). For example, for SIP connections
+ where the From address has been scrambled by the CM, the scrambled
+ address would be available here for the client to see. This is
+ completely optional, and MAY be an empty string ("") in
+ cases where anonymity modes are not set, or the CM doesn't know
+ what the remote contact will see, or any other case where this
+ doesn't make sense.</p>
+
+ <p>This MAY change over the lifetime of the channel, and SHOULD NOT
+ be used with the Request interface.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Call_State.xml b/qt4/spec/Channel_Interface_Call_State.xml
new file mode 100644
index 000000000..b0aea5915
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Call_State.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Call_State" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2008 Nokia Corporation </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.CallState">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.StreamedMedia"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for streamed media channels that can indicate call
+ progress or call states. The presence of this interface is no guarantee
+ that call states will actually be signalled (for instance, SIP
+ implementations are not guaranteed to generate status 180 Ringing, so a
+ call can be accepted without the Ringing flag ever having been set;
+ similarly, Jingle implementations are not guaranteed to send
+ <code>&lt;ringing/&gt;</code>).</p>
+
+ <p>To notify the other participant in the call that they are on hold,
+ see <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Hold</tp:dbus-ref>.</p>
+ </tp:docstring>
+ <tp:added version="0.17.2"/>
+
+ <method name="GetCallStates" tp:name-for-bindings="Get_Call_States">
+ <tp:docstring>
+ Get the current call states for all contacts involved in this call.
+ </tp:docstring>
+
+ <arg tp:type="Channel_Call_State_Map" name="States" direction="out"
+ type="a{uu}">
+ <tp:docstring>
+ The current call states. Participants where the call state flags
+ would be 0 (all unset) may be omitted from this mapping.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="CallStateChanged" tp:name-for-bindings="Call_State_Changed">
+ <tp:docstring>
+ Emitted when the state of a member of the channel has changed.
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ An integer handle for the contact.
+ </tp:docstring>
+ </arg>
+
+ <arg name="State" type="u" tp:type="Channel_Call_State_Flags">
+ <tp:docstring>
+ The new state for this contact.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:mapping name="Channel_Call_State_Map">
+ <tp:docstring>
+ A map from contacts to call states.
+ </tp:docstring>
+
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>A contact involved in this call.</tp:docstring>
+ </tp:member>
+
+ <tp:member name="State" type="u" tp:type="Channel_Call_State_Flags">
+ <tp:docstring>State flags for the given contact.</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:flags name="Channel_Call_State_Flags" value-prefix="Channel_Call_State" type="u">
+ <tp:docstring>
+ A set of flags representing call states.
+ </tp:docstring>
+
+ <tp:flag suffix="Ringing" value="1">
+ <tp:docstring>
+ The contact has been alerted about the call but has not responded
+ (e.g. 180 Ringing in SIP).
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Queued" value="2">
+ <tp:docstring>
+ The contact is temporarily unavailable, and the call has been placed
+ in a queue (e.g. 182 Queued in SIP, or call-waiting in telephony).
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Held" value="4">
+ <tp:docstring>
+ The contact has placed the call on hold, and will not receive
+ media from the local user or any other participants until they
+ unhold the call again.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Forwarded" value="8">
+ <tp:docstring>
+ The initiator of the call originally called a contact other than the
+ current recipient of the call, but the call was then forwarded or
+ diverted.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="In_Progress" value="16">
+ <tp:docstring>
+ Progress has been made in placing the outgoing call, but the
+ destination contact may not have been made aware of the call yet
+ (so the Ringing state is not appropriate). This corresponds to SIP's
+ status code 183 Session Progress, and could be used when the
+ outgoing call has reached a gateway, for instance.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Conference_Host" value="32">
+ <tp:added version='0.19.11'/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This contact has merged this call into a conference. Note that GSM
+ provides a notification when the remote party merges a call into a
+ conference, but not when it is split out again; thus, this flag can
+ only indicate that the call has been part of a conference at some
+ point. If a GSM connection manager receives a notification that a
+ call has been merged into a conference a second time, it SHOULD
+ represent this by clearing and immediately re-setting this flag on
+ the remote contact.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Chat_State.xml b/qt4/spec/Channel_Interface_Chat_State.xml
new file mode 100644
index 000000000..27515d2e8
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Chat_State.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Chat_State" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2007 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.ChatState">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.Text"/>
+
+ <tp:mapping name="Chat_State_Map">
+ <tp:added version="0.19.7"/>
+ <tp:docstring>A map from contacts to their chat states.</tp:docstring>
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>A contact</tp:docstring>
+ </tp:member>
+ <tp:member name="State" type="u" tp:type="Channel_Chat_State">
+ <tp:docstring>The contact's chat state</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="ChatStates" tp:name-for-bindings="Chat_States"
+ access="read" type="a{uu}" tp:type="Chat_State_Map">
+ <tp:added version="0.19.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map containing the chat states of all contacts in this
+ channel whose chat state is not Inactive.</p>
+
+ <p>Contacts in this channel, but who are not listed in this map,
+ may be assumed to be in the Inactive state.</p>
+
+ <p>In implementations that do not have this property, its value may be
+ assumed to be empty until a
+ <tp:member-ref>ChatStateChanged</tp:member-ref> signal indicates
+ otherwise.</p>
+
+ <tp:rationale>
+ <p>This property was not present in older versions of telepathy-spec,
+ because chat states in XMPP are not state-recoverable (if you
+ miss the change notification signal, there's no way to know the
+ state). However, this property still allows clients to recover
+ state changes that were seen by the CM before the client started
+ to deal with the channel.</p>
+
+ <p>In CMs that follow older spec versions, assuming Inactive will
+ mean that initial chat states will always be assumed to be
+ Inactive, which is the best we can do. XEP 0085 specifies
+ Inactive as the "neutral" state to be assumed unless told
+ otherwise.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <method name="SetChatState" tp:name-for-bindings="Set_Chat_State">
+ <arg direction="in" name="State" type="u" tp:type="Channel_Chat_State">
+ <tp:docstring>
+ The new state.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Set the local state and notify other members of the channel that it
+ has changed.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+ <signal name="ChatStateChanged" tp:name-for-bindings="Chat_State_Changed">
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ An integer handle for the contact.
+ </tp:docstring>
+ </arg>
+ <arg name="State" type="u" tp:type="Channel_Chat_State">
+ <tp:docstring>
+ The new state of this contact.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the state of a member of the channel has changed.
+ This includes local state.
+ </tp:docstring>
+ </signal>
+ <tp:enum name="Channel_Chat_State" type="u">
+ <tp:enumvalue suffix="Gone" value="0">
+ <tp:docstring>
+ The contact has effectively ceased participating in the chat.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Inactive" value="1">
+ <tp:docstring>
+ The contact has not been active for some time.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Active" value="2">
+ <tp:docstring>
+ The contact is actively participating in the chat.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Paused" value="3">
+ <tp:docstring>
+ The contact has paused composing a message.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Composing" value="4">
+ <tp:docstring>
+ The contact is composing a message to be sent to the chat.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for channels for receiving notifications of remote contacts'
+ state, and for notifying remote contacts of the local state.</p>
+
+ <p>Clients should assume that a contact's state is Channel_Chat_State_Inactive
+ unless they receive a notification otherwise.</p>
+
+ <p>The Channel_Chat_State_Gone state is treated differently to other states:</p>
+ <ul>
+ <li>It may not be used for multi-user chats</li>
+ <li>It may not be explicitly sent</li>
+ <li>It should be automatically sent when the channel is closed</li>
+ <li>It must not be sent to the peer if a channel is closed without being used</li>
+ <li>Receiving it must not cause a new channel to be opened</li>
+ </ul>
+
+ <p>The different states are defined by XEP-0085, but may be applied to any suitable protocol.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Conference.xml b/qt4/spec/Channel_Interface_Conference.xml
new file mode 100644
index 000000000..abda59eef
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Conference.xml
@@ -0,0 +1,628 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Conference"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.Conference">
+ <tp:added version="0.19.13">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires
+ interface="org.freedesktop.Telepathy.Channel.Interface.Group"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for multi-user conference channels that can "continue
+ from" one or more individual channels. This could be used to invite
+ other contacts to an existing 1-1 text conversation, combine two phone
+ calls into one conference call, and so on, with roughly the same API in
+ each case.</p>
+
+ <tp:rationale>
+ <p>This interface addresses freedesktop.org <a
+ href="http://bugs.freedesktop.org/show_bug.cgi?id=24906">bug
+ #24906</a> (GSM-compatible conference calls) and <a
+ href="http://bugs.freedesktop.org/show_bug.cgi?id=24939">bug
+ #24939</a> (upgrading calls and chats to multi-user).
+ See those bugs for more rationale and use cases.</p>
+ </tp:rationale>
+
+ <p>Existing channels are upgraded by requesting a new channel of the same
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>,
+ listing the channels to be merged into the new conference in the
+ <tp:member-ref>InitialChannels</tp:member-ref> property of the request.
+ If <tp:member-ref>InitialInviteeHandles</tp:member-ref> and
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref> are
+ <var>Allowed_Properties</var> in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>,
+ ad-hoc conferences to a set of contacts may be created by requesting a
+ channel, specifying
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> and/or
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref> to be the contacts in
+ question. A request may specify these alongside
+ <tp:member-ref>InitialChannels</tp:member-ref>, to simultaneously
+ upgrade a channel to a conference and invite others to join it.</p>
+
+ <p>Channels with this interface MAY also implement <tp:dbus-ref
+ namespace='ofdT.Channel.Interface'>MergeableConference.DRAFT</tp:dbus-ref>
+ to support merging more 1-1 channels into an ongoing conference.
+ Similarly, 1-1 channels MAY implement <tp:dbus-ref
+ namespace='ofdT.Channel.Interface'>Splittable.DRAFT</tp:dbus-ref> to
+ support being broken out of a Conference channel.</p>
+
+ <p>The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Group</tp:dbus-ref> interface on Conference channels MAY use
+ channel-specific handles for participants; clients SHOULD support
+ both Conferences that have channel-specific handles, and those that
+ do not.</p>
+
+ <tp:rationale>
+ <p>In the GSM case, the Conference's Group interface MAY have
+ channel-specific handles, to represent the fact that the same
+ phone number may be in a conference twice (for instance, it could be
+ the number of a corporate switchboard).</p>
+
+ <p>In the XMPP case, the Conference's Group interface SHOULD have
+ channel-specific handles, to reflect the fact that the participants
+ have MUC-specific identities, and the user might also be able to see
+ their global identities, or not.</p>
+
+ <p>In most other cases, including MSN and link-local XMPP, the
+ Conference's Group interface SHOULD NOT have channel-specific
+ handles, since users' identities are always visible.</p>
+ </tp:rationale>
+
+ <p>Connection managers implementing channels with this interface
+ MUST NOT allow the object paths of channels that could be merged
+ into a Conference to be re-used, unless the channel re-using the
+ object path is equivalent to the channel that previously used it.</p>
+
+ <tp:rationale>
+ <p>If you upgrade some channels into a conference, and then close
+ the original channels, <tp:member-ref>InitialChannels</tp:member-ref>
+ (which is immutable) will contain paths to channels which no longer
+ exist. This implies that you should not re-use channel object paths,
+ unless future incarnations of the path are equivalent.</p>
+
+ <p>For instance, on protocols where you can only have
+ zero or one 1-1 text channels with Emily at one time, it would
+ be OK to re-use the same object path for every 1-1 text channel
+ with Emily; but on protocols where this is not true, it would
+ be misleading.</p>
+ </tp:rationale>
+
+ <h4>Examples of usage</h4>
+
+ <p>A pair of 1-1 GSM calls <var>C1</var> and <var>C2</var> can be merged
+ into a single conference call by calling:</p>
+
+ <blockquote>
+ <code><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>({
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>: ...Call,
+ ...<tp:member-ref>InitialChannels</tp:member-ref>: [C1, C2]
+ })</code>
+ </blockquote>
+
+ <p>which returns a new channel <var>Cn</var> implementing the conference
+ interface. (As a quirk of GSM, both 1-1 will cease to function normally
+ until they are <tp:dbus-ref
+ namespace="ofdT.Channel.Interface.Splittable.DRAFT">Split</tp:dbus-ref>
+ from the conference, or the conference ends.)</p>
+
+ <p>An XMPP 1-1 conversation <var>C3</var> (with
+ <tt>chris@example.com</tt>, say) can be continued in a newly created
+ multi-user chatroom by calling:</p>
+
+ <blockquote>
+ <code>CreateChannel({
+ ...ChannelType: ...Text,
+ ...<tp:member-ref>InitialChannels</tp:member-ref>: [C3]
+ })</code>
+ </blockquote>
+
+ <p>Or, to invite <tt>emily@example.net</tt> to join the newly-created MUC
+ at the same time:</p>
+
+ <blockquote>
+ <code>CreateChannel({
+ ...ChannelType: ...Text,
+ ...<tp:member-ref>InitialChannels</tp:member-ref>: [C3],
+ ...<tp:member-ref>InitialInviteeIDs</tp:member-ref>: ['emily@example.net']
+ })</code>
+ </blockquote>
+
+ <p>To continue <var>C3</var> in a particular multi-user
+ chatroom (rather than the implementation inventing a unique name for
+ the room), call:</p>
+
+ <blockquote>
+ <code><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">EnsureChannel</tp:dbus-ref>({
+ ...ChannelType: ...Text,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>: ...Room,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>: 'telepathy@conf.example.com',
+ ...<tp:member-ref>InitialChannels</tp:member-ref>: [C3]
+ })</code>
+ </blockquote>
+
+ <p>Note the use of EnsureChannel — if a channel for
+ <tt>telepathy@conf.example.com</tt> is already open, this SHOULD be
+ equivalent to inviting <tt>chris@example.com</tt> to the existing
+ channel.</p>
+
+ <p>In the above cases, the text channel <var>C3</var> SHOULD remain open
+ and fully functional (until explicitly closed by a client); new
+ incoming 1-1 messages from <tt>chris@example.com</tt> SHOULD appear in
+ <var>C3</var>, and messages sent using <var>C3</var> MUST be relayed
+ only to <tt>chris@example.com</tt>.</p>
+
+ <tp:rationale>
+ <p>If there is an open 1-1 text channel with a contact, in every
+ other situation new messages will appear in that channel. Given
+ that the old channel remains open — which is the least surprising
+ behaviour, and eases us towards a beautiful world where channels
+ never close themselves — it stands to reason that it should be
+ where new messages from Chris should appear. On MSN, creating a
+ conference from <var>C3</var> should migrate the underlying
+ switchboard from <var>C3</var> to the new channel; this is an
+ implementation detail, and should not affect the representation on
+ D-Bus. With a suitable change of terminology, Skype has the same
+ behaviour.</p>
+
+ <p>If the current handler of that channel doesn't want this to happen
+ (maybe it transformed the existing tab into the group chat window,
+ and so there'd be no UI element still around to show new messages),
+ then it should just <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref> the
+ old 1-1 channel; it'll respawn if necessary.</p>
+ </tp:rationale>
+
+ <p>Either of the XMPP cases could work for Call channels, to
+ upgrade from 1-1 Jingle to multi-user Jingle. Any of the XMPP cases
+ could in principle work for link-local XMPP (XEP-0174).</p>
+
+ <p>XMPP and MSN do not natively have a concept of merging two or more
+ channels C1, C2... into one channel, Cn. However, the GSM-style
+ merging API can be supported on XMPP and MSN, as an API short-cut
+ for upgrading C1 into a conference Cn (which invites the
+ TargetHandle of C1 into Cn), then immediately inviting the
+ TargetHandle of C2, the TargetHandle of C3, etc. into Cn as well.</p>
+
+ <h4>Sample <tp:dbus-ref namespace='ofdT.Connection.Interface.Requests'
+ >RequestableChannelClasses</tp:dbus-ref></h4>
+
+ <p>A GSM connection might advertise the following channel class for
+ conference calls:</p>
+
+ <blockquote>
+ <code>
+( Fixed = {<br/>
+    ...<tp:dbus-ref namespace='ofdT.Channel'>ChannelType</tp:dbus-ref>:
+ ...<tp:dbus-ref namespace='ofdT.Channel.Type'>StreamedMedia</tp:dbus-ref><br/>
+  },<br/>
+  Allowed = [ <tp:member-ref>InitialChannels</tp:member-ref>,
+ <tp:dbus-ref namespace='ofdT.Channel.Type.StreamedMedia'
+ >InitialAudio</tp:dbus-ref>
+ ]<br/>
+)
+ </code>
+ </blockquote>
+
+ <p>This indicates support for starting audio-only conference calls by
+ merging two or more existing channels (since
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> and
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref> are not allowed).</p>
+
+ <p>An XMPP connection might advertise the following classes for ad-hoc
+ multi-user text chats:</p>
+
+ <blockquote>
+ <code>
+( Fixed = {<br/>
+    ...<tp:dbus-ref namespace='ofdT.Channel'>ChannelType</tp:dbus-ref>:
+ ...<tp:dbus-ref namespace='ofdT.Channel.Type'>Text</tp:dbus-ref><br/>
+  },<br/>
+  Allowed = [ <tp:member-ref>InitialChannels</tp:member-ref>,
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref>,
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref>,
+ <tp:member-ref>InvitationMessage</tp:member-ref>
+ ]<br/>
+),<br/>
+( Fixed = {<br/>
+    ...<tp:dbus-ref namespace='ofdT.Channel'>ChannelType</tp:dbus-ref>:
+ ...<tp:dbus-ref namespace='ofdT.Channel.Type'>Text</tp:dbus-ref>,<br/>
+    ...<tp:dbus-ref namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>:
+ Room<br/>
+  },<br/>
+  Allowed = [ <tp:dbus-ref namespace='ofdT.Channel'>TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref namespace='ofdT.Channel'>TargetID</tp:dbus-ref>,<br/>
+              <tp:member-ref>InitialChannels</tp:member-ref>,
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref>,
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref>,
+ <tp:member-ref>InvitationMessage</tp:member-ref>
+ ]<br/>
+)
+ </code>
+ </blockquote>
+
+ <p>The first class indicates support for starting ad-hoc (nameless) chat
+ rooms, upgraded from existing 1-1 channels and/or inviting new
+ contacts, along with a message to be sent along with the invitations.
+ The second indicates support for upgrading to a particular named chat
+ room.</p>
+ </tp:docstring>
+
+ <property name="Channels" tp:name-for-bindings="Channels"
+ access="read" type="ao">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The individual <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>s that
+ are continued by this conference, which have the same <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >ChannelType</tp:dbus-ref> as this one, but with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref> = CONTACT.</p>
+
+ <p>This property MUST NOT be requestable; instead, the
+ <tp:member-ref>InitialChannels</tp:member-ref> property may be
+ specified when requesting a channel.</p>
+
+ <tp:rationale>
+ <p>This is consistent with requesting
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> and
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref>, rather than
+ requesting <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Group.Members</tp:dbus-ref>
+ and some hypothetical ID version of that property.</p>
+ </tp:rationale>
+
+ <p>Change notification is via the
+ <tp:member-ref>ChannelMerged</tp:member-ref> and
+ <tp:member-ref>ChannelRemoved</tp:member-ref> signals.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="ChannelMerged" tp:name-for-bindings="Channel_Merged">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new channel is added to the value of
+ <tp:member-ref>Channels</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Channel" type="o">
+ <tp:docstring>The channel that was added to
+ <tp:member-ref>Channels</tp:member-ref>.</tp:docstring>
+ </arg>
+
+ <arg name="Channel_Specific_Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>A new channel-specific handle for the <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> of
+ <var>Channel</var>, as will appear in
+ <tp:member-ref>OriginalChannels</tp:member-ref>, or <tt>0</tt> if a
+ global handle is used for
+ <var>Channel</var>'s TargetHandle on the <tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Group</tp:dbus-ref> interface
+ of this channel.</tp:docstring>
+ </arg>
+
+ <arg name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring><var>Channel</var>'s immutable properties.</tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="ChannelRemoved" tp:name-for-bindings="Channel_Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a channel is removed from the value of
+ <tp:member-ref>Channels</tp:member-ref>, either because it closed
+ or because it was split using the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Splittable.DRAFT.Split</tp:dbus-ref> method.</p>
+
+ <p>If a channel is removed because it was closed, <tp:dbus-ref
+ namespace='ofdT.Channel'>Closed</tp:dbus-ref> should be emitted
+ before this signal.</p>
+ </tp:docstring>
+
+ <arg name="Channel" type="o">
+ <tp:docstring>The channel that was removed from
+ <tp:member-ref>Channels</tp:member-ref>.</tp:docstring>
+ </arg>
+
+ <arg name="Details" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Additional information about the removal, which may include
+ the same well-known keys as the Details argument of
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Group"
+ >MembersChangedDetailed</tp:dbus-ref>, with the same semantics.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="InitialChannels" tp:name-for-bindings="Initial_Channels"
+ access="read" type="ao" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The initial value of <tp:member-ref>Channels</tp:member-ref>.</p>
+
+ <p>This property SHOULD be requestable. Omitting it from a request is
+ equivalent to providing it with an empty list as value. Requests
+ where its value has at least two channel paths SHOULD be expected to
+ succeed on any implementation of this interface. If
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> and
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref> are
+ <var>Allowed_Properties</var> in <tp:dbus-ref
+ namespace='ofdT.Connection.Interface.Requests'
+ >RequestableChannelClasses</tp:dbus-ref>, then requests with zero
+ or one channel paths SHOULD also succeed; otherwise, clients SHOULD
+ NOT make requests with zero or one paths for this property.</p>
+
+ <tp:rationale>
+ <p>In GSM, a pair of calls can be merged into a conference, but you
+ can't start a conference call from zero or one existing calls. In
+ XMPP and MSN, you can create a new chatroom, or upgrade one 1-1
+ channel into a chatroom; however, on these protocols, it is also
+ possible to fake GSM-style merging by upgrading the first channel,
+ then inviting the targets of all the other channels into it.</p>
+ </tp:rationale>
+
+ <p>If possible, the <tp:member-ref>Channels</tp:member-ref>' states SHOULD
+ NOT be altered by merging them into a conference. However, depending on
+ the protocol, the Channels MAY be placed in a "frozen" state by placing
+ them in this property's value or by calling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >MergeableConference.DRAFT.Merge</tp:dbus-ref> on them.</p>
+
+ <tp:rationale>
+ <p>In Jingle, nothing special will happen to merged calls. UIs MAY
+ automatically place calls on hold before merging them, if that is
+ the desired behaviour; this SHOULD always work. Not doing
+ an implicit hold/unhold seems to preserve least-astonishment.</p>
+
+ <p>In GSM, the calls that are merged go into a state similar to
+ Hold, but they cannot be unheld, only split from the conference
+ call using <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Channel.Interface.Splittable.DRAFT.Split</tp:dbus-ref>.</p>
+ </tp:rationale>
+
+ <p>Depending on the protocol, it might be signalled to remote users
+ that this channel is a continuation of all the requested channels,
+ or that it is only a continuation of the first channel in the
+ list.</p>
+
+ <tp:rationale>
+ <p>In MSN, the conference steals the underlying switchboard (protocol
+ construct) from one of its component channels, so the conference
+ appears to remote users to be a continuation of that channel and no
+ other. The connection manager has to make some arbitrary choice, so
+ we arbitrarily mandate that it SHOULD choose the first channel in
+ the list as the one to continue.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialInviteeHandles"
+ tp:name-for-bindings="Initial_Invitee_Handles"
+ access="read" type="au" tp:type="Contact_Handle[]" tp:immutable="yes"
+ tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of additional contacts invited to this conference when it
+ was created.</p>
+
+ <p>If it is possible to invite new contacts when creating a conference
+ (as opposed to merging several channels into one new conference
+ channel), this property SHOULD be requestable, and appear in the allowed
+ properties in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests"
+ >RequestableChannelClasses</tp:dbus-ref>. Otherwise, this property
+ SHOULD NOT be requestable, and its value SHOULD always be the empty
+ list.</p>
+
+ <tp:rationale>
+ <p>On GSM you have to place a 1-1 call before you can merge it into a
+ conference; on the other hand, you can invite new contacts to XMPP
+ Muji calls and XMPP/MSN/Skype ad-hoc chat rooms without starting a
+ 1-1 channel with them first.</p>
+ </tp:rationale>
+
+ <p>If included in a request, the given contacts are automatically
+ invited into the new channel, as if they had been added with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Group.AddMembers</tp:dbus-ref>(InitialInviteeHandles,
+ <tp:member-ref>InvitationMessage</tp:member-ref>) immediately after
+ the channel was created.</p>
+
+ <tp:rationale>
+ <p>This is a simple convenience API for the common case that a UI
+ upgrades a 1-1 chat to a multi-user chat solely in order to invite
+ someone else to participate.</p>
+ </tp:rationale>
+
+ <p>If the local user was not the initiator of this channel, the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Group.SelfHandle</tp:dbus-ref> SHOULD appear in the value of this
+ property, together with any other contacts invited at the same time
+ (if that information is known).</p>
+
+ <p>InitialInviteeHandles, InitialInviteeIDs and InitialChannels MAY be
+ combined in a single request.</p>
+
+ <tp:rationale>
+ <p>For example, if you have a 1-1 channel C1 with Rob, and you want
+ to invite Sjoerd to join the discussion, you can do so by
+ requesting a channel with InitialChannels=[C1] and
+ InitialInviteeHandles=[sjoerd],
+ or InitialChannels=[C1] and
+ InitialInviteeIDs=["sjoerd@example.com"].</p>
+ </tp:rationale>
+
+ <p>If a request includes some combination of InitialInviteeHandles,
+ InitialInviteeIDs and InitialChannels, then the value of
+ InitialInviteeHandles on the resulting channel SHOULD be the union of
+ the handles from InitialInviteeHandles, the handles corresponding
+ to the InitialInviteeIDs, and the target handles of the
+ InitialChannels, with any duplicate handles removed. Because this
+ property is immutable, its value SHOULD be computed before the
+ channel is announced via the NewChannels signal.</p>
+
+ <tp:rationale>
+ <p>This simplifies identification of new channels in clients - they
+ only have to look at one of the properties, not both. For example,
+ after either of the requests mentioned above, the NewChannels
+ signal would announce the channel with InitialChannels=[C1],
+ InitialInviteeHandles=[rob, sjoerd], and
+ InitialInviteeIDs=["rob@example.net", "sjoerd.example.com"].</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialInviteeIDs"
+ tp:name-for-bindings="Initial_Invitee_IDs"
+ access="read" type="as" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of additional contacts invited to this conference when it
+ was created.</p>
+
+ <p>This property SHOULD be requestable if and only if
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> is requestable.
+ Its semantics are the same, except that it takes a list of the
+ string representations of contact handles; invitations are sent to
+ any contact present in either or both of these properties.</p>
+
+ <p>When a channel is created, the values of InitialInviteeHandles and
+ InitialInviteeIDs MUST correspond to each other. In particular, this
+ means that the value of InitialInviteeIDs will include the TargetID
+ of each channel in InitialChannels, and the ID corresponding to each
+ handle in InitialInviteeHandles.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InvitationMessage" tp:name-for-bindings="Invitation_Message"
+ access="read" type="s" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The message that was sent to the
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> when they were
+ invited.</p>
+
+ <p>This property SHOULD be requestable, and appear in the allowed
+ properties in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests"
+ >RequestableChannelClasses</tp:dbus-ref>, in protocols where
+ invitations can have an accompanying text message.</p>
+
+ <tp:rationale>
+ <p>This allows invitations with a message to be sent when using
+ <tp:member-ref>InitialInviteeHandles</tp:member-ref> or
+ <tp:member-ref>InitialInviteeIDs</tp:member-ref>.</p>
+ </tp:rationale>
+
+ <p>If the local user was not the initiator of this channel, the
+ message with which they were invited (if any) SHOULD appear in the
+ value of this property.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="OriginalChannels" tp:name-for-bindings="Original_Channels"
+ type="a{uo}" tp:type="Channel_Originator_Map"
+ access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>On GSM conference calls, it is possible to have the same phone
+ number in a conference twice; for instance, it could be the number of
+ a corporate switchboard. This is represented using channel-specific
+ handles; whether or not a channel uses channel-specific handles is
+ reported in <tp:dbus-ref
+ namespace='ofdT.Channel.Interface'>Group.GroupFlags</tp:dbus-ref>.
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Group.HandleOwners</tp:dbus-ref>
+ property specifies the mapping from opaque channel-specific handles
+ to actual numbers; this property specifies the original 1-1 channel
+ corresponding to each channel-specific handle in the conference.</p>
+
+ <p>In protocols where this situation cannot arise, such as XMPP,
+ this property MAY remain empty.</p>
+
+ <p>For example, consider this situation:</p>
+
+ <ol>
+ <li>Place a call (with path <tt>/call/to/simon</tt>) to the contact
+ <tt>+441234567890</tt> (which is assigned the handle <var>h</var>,
+ say), and ask to be put through to Simon McVittie;</li>
+ <li>Put that call on hold;</li>
+ <li>Place another call (with path <tt>/call/to/jonny</tt>) to
+ <tt>+441234567890</tt>, and ask to be put
+ through to Jonny Lamb;</li>
+ <li>Request a new channel with
+ <tp:member-ref>InitialChannels</tp:member-ref>:
+ <tt>['/call/to/simon', '/call/to/jonny']</tt>.</li>
+ </ol>
+
+ <p>The new channel will have the following properties, for some handles
+ <var>s</var> and <var>j</var>:</p>
+
+ <blockquote>
+ <code>{<br/>
+ ...<tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Group.GroupFlags</tp:dbus-ref>:
+ Channel_Specific_Handles | (other flags),<br/>
+ ...<tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Group.Members</tp:dbus-ref>:
+ [self_handle, s, j],<br/>
+ ...<tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Group.HandleOwners</tp:dbus-ref>:
+ { s: h, j: h },<br/>
+ ...<tp:member-ref>InitialChannels</tp:member-ref>:
+ ['/call/to/simon', '/call/to/jonny'],<br/>
+ ...<tp:member-ref>Channels</tp:member-ref>:
+ ['/call/to/simon', '/call/to/jonny'],<br/>
+ ...<tp:member-ref>OriginalChannels</tp:member-ref>:
+ { s: '/call/to/simon', j: '/call/to/jonny' },<br/>
+ # ...standard properties like ChannelType: Group elided...<br/>
+ }</code>
+ </blockquote>
+
+ <p>Change notification is via the
+ <tp:member-ref>ChannelMerged</tp:member-ref> and
+ <tp:member-ref>ChannelRemoved</tp:member-ref> signals: if
+ <var>Channel_Specific_Handle</var> in the former is non-zero, this
+ property SHOULD be updated to map that handle to the merged channel's
+ path.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:mapping name="Channel_Originator_Map">
+ <tp:member name="Channel_Specific_Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A channel-specific handle for a participant in this conference.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Original_Channel" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The object path of <tp:member-ref>Channels</tp:member-ref>
+ representing the original 1-1 channel with
+ <var>Channel_Specific_Handle</var>.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A mapping from members of a conference to the original 1-1 channel with
+ that contact, if any. See
+ <tp:member-ref>OriginalChannels</tp:member-ref> for details.
+ </tp:docstring>
+ </tp:mapping>
+ </interface>
+</node>
diff --git a/qt4/spec/Channel_Interface_Credentials_Storage.xml b/qt4/spec/Channel_Interface_Credentials_Storage.xml
new file mode 100644
index 000000000..e44b13e32
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Credentials_Storage.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Credentials_Storage"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2011 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.CredentialsStorage.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.10">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel interface for SASL authentication channels that can save the
+ credentials in the connection manager.</p>
+
+ <p>This interface is unlikely to be present for any SASL channels that are
+ more complex than a simple password prompt (e.g.
+ <code>X-TELEPATHY-PASSWORD</code> or <code>PLAIN</code>).</p>
+
+ <p>In practice, this interface should only be implemented by connection
+ managers that implement the <tp:dbus-ref
+ namespace="ofdT">ConnectionManager.Interface.AccountStorage.DRAFT</tp:dbus-ref>
+ interface. To clear a password that has been saved in this manner, a
+ client should call <tp:dbus-ref
+ namespace="ofdT.ConnectionManager.Interface">AccountStorage.DRAFT.ForgetCredentials</tp:dbus-ref>
+ on the Account.</p>
+ </tp:docstring>
+
+ <method name="StoreCredentials" tp:name-for-bindings="Store_Credentials">
+ <arg direction="in" name="Store" type="b">
+ <tp:docstring>
+ Whether to store the authentication credentials.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This method tells the connection manager whether to store the
+ authentication response in order to allow the connection manager to
+ sign-on automatically in the future.</p>
+ <p>If credentials have been stored in this way, the client SHOULD NOT
+ attempt to store the credentials locally in a keyring.</p>
+ <p>This method MUST be called before <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication">AcceptSASL</tp:dbus-ref>
+ is called or it will have no effect.</p>
+ </tp:docstring>
+ </method>
+ </interface>
+</node>
diff --git a/qt4/spec/Channel_Interface_DTMF.xml b/qt4/spec/Channel_Interface_DTMF.xml
new file mode 100644
index 000000000..d74ea6fa7
--- /dev/null
+++ b/qt4/spec/Channel_Interface_DTMF.xml
@@ -0,0 +1,342 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_DTMF" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2005-2010 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2005-2010 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2006 INdT</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.DTMF">
+ <tp:xor-requires>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.StreamedMedia"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT"/>
+ </tp:xor-requires>
+ <tp:changed version="0.19.6">The <tp:type>Stream_ID</tp:type>s in this
+ interface should now be ignored by CMs. This is primarily to allow this
+ interface to be used with <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>Call.DRAFT</tp:dbus-ref>
+ channels.</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An interface that gives a Channel the ability to send DTMF events over
+ audio streams which have been established using the StreamedMedia channel
+ type. The event codes used are in common with those defined in <a
+ href="http://www.rfc-editor.org/rfc/rfc4733.txt">RFC4733</a>, and are
+ listed in the <tp:type>DTMF_Event</tp:type> enumeration.
+ </tp:docstring>
+
+ <method name="StartTone" tp:name-for-bindings="Start_Tone">
+ <tp:changed version="0.19.6">The <var>Stream_ID</var> parameter became
+ vestigial.</tp:changed>
+ <arg direction="in" name="Stream_ID" type="u" tp:type="Stream_ID">
+ <tp:docstring>A stream ID as defined in the StreamedMedia channel
+ type. This argument is included for backwards compatibility and MUST
+ be ignored by the implementations - the tone SHOULD be sent to all
+ eligible streams in the channel.</tp:docstring>
+ </arg>
+ <arg direction="in" name="Event" type="y" tp:type="DTMF_Event">
+ <tp:docstring>A numeric event code from the DTMF_Event enum.</tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ <p>Start sending a DTMF tone to all eligible streams in the channel.
+ Where possible, the tone will continue until
+ <tp:member-ref>StopTone</tp:member-ref> is called. On certain protocols,
+ it may only be possible to send events with a predetermined length. In
+ this case, the implementation MAY emit a fixed-length tone, and the
+ StopTone method call SHOULD return NotAvailable.</p>
+ <tp:rationale>
+ The client may wish to control the exact duration and timing of the
+ tones sent as a result of user's interaction with the dialpad, thus
+ starting and stopping the tone sending explicitly.
+ </tp:rationale>
+
+ <p>Tone overlaping or queueing is not supported, so this method can only
+ be called if no DTMF tones are already being played.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError" />
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The given stream ID was invalid. Deprecated, since stream IDs
+ are ignored.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ There are no eligible audio streams.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.ServiceBusy">
+ <tp:docstring>
+ DTMF tones are already being played.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="StopTone" tp:name-for-bindings="Stop_Tone">
+ <tp:changed version="0.19.6">The <var>Stream_ID</var> parameter became
+ vestigial.</tp:changed>
+ <arg direction="in" name="Stream_ID" type="u" tp:type="Stream_ID">
+ <tp:docstring>A stream ID as defined in the StreamedMedia channel
+ type. This argument is included for backwards compatibility and MUST
+ be ignored by the implementations - the sending SHOULD be stoped in
+ all eligible streams in the channel.</tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Stop sending any DTMF tones which have been started using the
+ <tp:member-ref>StartTone</tp:member-ref> or
+ <tp:member-ref>MultipleTones</tp:member-ref> methods.
+ If there is no current tone, this method will do nothing.
+ If MultipleTones was used, the client should not assume the
+ sending has stopped immediately; instead, the client should wait
+ for the StoppedTones signal.
+ <tp:rationale>
+ On some protocols it might be impossible to cancel queued tones
+ immediately.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError" />
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The given stream ID was invalid. Deprecated, since stream IDs
+ are ignored.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ Continuous tones are not supported by this stream. Deprecated,
+ since stream IDs are ignored.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="MultipleTones" tp:name-for-bindings="Multiple_Tones">
+ <tp:added version="0.19.6" />
+ <tp:changed version="0.21.3">The characters [pPxXwW,] must
+ also be supported.</tp:changed>
+ <arg direction="in" name="Tones" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A string representation of one or more DTMF
+ events. Implementations of this method MUST support all of the
+ following characters in this string:</p>
+
+ <ul>
+ <li>the digits 0-9, letters A-D and a-d, and symbols '*' and '#'
+ correspond to the members of <tp:type>DTMF_Event</tp:type></li>
+
+ <li>any of 'p', 'P', 'x', 'X' or ',' (comma) results in an
+ implementation-defined pause, typically for 3 seconds</li>
+
+ <li>'w' or 'W' waits for the user to continue, by stopping
+ interpretation of the string, and if there is more to be played,
+ emitting the <tp:member-ref>TonesDeferred</tp:member-ref> signal
+ with the rest of the string as its argument: see that signal
+ for details</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ <p>Send multiple DTMF events to all eligible streams in the channel.
+ Each tone will be played for an implementation-defined number of
+ milliseconds (typically 250ms), followed by a gap before the next tone
+ is played (typically 100ms). The
+ duration and gap are defined by the protocol or connection manager.</p>
+
+ <tp:rationale>
+ <p>In cases where the client knows in advance the tone sequence it
+ wants to send, it's easier to use this method than manually start
+ and stop each tone in the sequence.</p>
+
+ <p>The tone and gap lengths may need to vary for interoperability,
+ according to the protocol and other implementations' ability to
+ recognise tones. At the time of writing, GStreamer uses a
+ minimum of 250ms tones and 100ms gaps when playing in-band DTMF
+ in the normal audio stream, or 70ms tones and 50ms gaps when
+ encoding DTMF as <code>audio/telephone-event</code>.</p>
+ </tp:rationale>
+
+ <p>Tone overlaping or queueing is not supported, so this method can only
+ be called if no DTMF tones are already being played.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError" />
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The supplied Tones string was invalid.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ There are no eligible audio streams.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.ServiceBusy">
+ <tp:docstring>
+ DTMF tones are already being played.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <property name="CurrentlySendingTones"
+ tp:name-for-bindings="Currently_Sending_Tones" type="b" access="read">
+ <tp:added version="0.19.6" />
+ <tp:docstring>
+ Indicates whether there are DTMF tones currently being sent in the
+ channel. If so, the client should wait for
+ <tp:member-ref>StoppedTones</tp:member-ref> signal before trying to
+ send more tones.
+ </tp:docstring>
+ </property>
+
+ <property name="InitialTones" tp:name-for-bindings="Initial_Tones"
+ type="s" access="read">
+ <tp:added version="0.19.6" />
+ <tp:docstring>
+ <p>If non-empty in a channel request that will create a new channel,
+ the connection manager should send the tones immediately after
+ at least one eligible audio stream has been created in the
+ channel.</p>
+
+ <p>This property is immutable (cannot change).</p>
+ </tp:docstring>
+ </property>
+
+ <property name="DeferredTones" tp:name-for-bindings="Deferred_Tones"
+ type="s" access="read">
+ <tp:added version="0.21.3" />
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The tones waiting for the user to continue, if any.</p>
+
+ <p>When this property is set to a non-empty value,
+ <tp:member-ref>TonesDeferred</tp:member-ref> is emitted.
+ When any tones are played (i.e. whenever
+ <tp:member-ref>SendingTones</tp:member-ref> is emitted),
+ this property is reset to the empty string.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="TonesDeferred" tp:name-for-bindings="Tones_Deferred">
+ <tp:added version="0.21.3" />
+ <arg name="Tones" type="s">
+ <tp:docstring>The new non-empty value of
+ <tp:member-ref>DeferredTones</tp:member-ref>.</tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when 'w' or 'W', indicating "wait for the user to continue",
+ is encountered while playing a DTMF string queued by
+ <tp:member-ref>MultipleTones</tp:member-ref> or
+ <tp:member-ref>InitialTones</tp:member-ref>. Any queued DTMF events
+ after the 'w', which have not yet been played, are placed in the
+ <tp:member-ref>DeferredTones</tp:member-ref> property and copied
+ into this signal's argument.</p>
+
+ <p>When the channel handler is ready to continue, it MAY pass the
+ value of <tp:member-ref>DeferredTones</tp:member-ref> to
+ <tp:member-ref>MultipleTones</tp:member-ref>, to resume sending.
+ Alternatively, it MAY ignore the deferred tones, or even play
+ different tones instead. Any deferred tones are discarded the next
+ time a tone is played.</p>
+
+ <p>This signal SHOULD NOT be emitted if there is nothing left to play,
+ i.e. if the 'w' was the last character in the DTMF string.</p>
+ </tp:docstring>
+ </signal>
+
+ <signal name="SendingTones" tp:name-for-bindings="Sending_Tones">
+ <tp:added version="0.19.6" />
+ <arg name="Tones" type="s">
+ <tp:docstring>DTMF string (one or more events) that is to be played.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>DTMF tone(s)are being sent to all eligible streams in the channel.
+ The signal is provided to indicating the fact that the streams are
+ currently being used to send one or more DTMF tones, so any other
+ media input is not getting through to the audio stream. It also
+ serves as a cue for the
+ <tp:member-ref>StopTone</tp:member-ref> method.</p>
+ </tp:docstring>
+ </signal>
+
+ <signal name="StoppedTones" tp:name-for-bindings="Stopped_Tones">
+ <tp:added version="0.19.6" />
+ <arg name="Cancelled" type="b">
+ <tp:docstring>True if the DTMF tones were actively cancelled via
+ <tp:member-ref>StopTone</tp:member-ref>.</tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>DTMF tones have finished playing on streams in this channel.</p>
+ </tp:docstring>
+ </signal>
+
+ <tp:enum name="DTMF_Event" type="y">
+ <tp:enumvalue suffix="Digit_0" value="0">
+ <tp:docstring>0</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_1" value="1">
+ <tp:docstring>1</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_2" value="2">
+ <tp:docstring>2</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_3" value="3">
+ <tp:docstring>3</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_4" value="4">
+ <tp:docstring>4</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_5" value="5">
+ <tp:docstring>5</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_6" value="6">
+ <tp:docstring>6</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_7" value="7">
+ <tp:docstring>7</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_8" value="8">
+ <tp:docstring>8</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Digit_9" value="9">
+ <tp:docstring>9</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Asterisk" value="10">
+ <tp:docstring>*</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Hash" value="11">
+ <tp:docstring>#</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Letter_A" value="12">
+ <tp:docstring>A</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Letter_B" value="13">
+ <tp:docstring>B</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Letter_C" value="14">
+ <tp:docstring>C</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Letter_D" value="15">
+ <tp:docstring>D</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Destroyable.xml b/qt4/spec/Channel_Interface_Destroyable.xml
new file mode 100644
index 000000000..ce5592327
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Destroyable.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Destroyable"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2008 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.Destroyable">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:added version="0.17.14">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface exists to support channels where
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Close</tp:dbus-ref>
+ is insufficiently destructive. At the moment this means
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Type.Text</tp:dbus-ref>,
+ but the existence of this interface means that unsupported channels
+ can be terminated in a non-channel-type-specific way.</p>
+ </tp:docstring>
+
+ <method name="Destroy" tp:name-for-bindings="Destroy">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Close the channel abruptly, possibly with loss of data. The
+ connection manager MUST NOT re-create the channel unless/until
+ more events occur.</p>
+
+ <tp:rationale>
+ <p>The main motivating situation for this method is that when a Text
+ channel with pending messages is closed with Close, it comes back
+ as an incoming channel (to avoid a race between Close and an
+ incoming message). If Destroy is called on a Text channel, the CM
+ should delete all pending messages and close the channel, and
+ the channel shouldn't be re-created until/unless another message
+ arrives.</p>
+ </tp:rationale>
+
+ <p>Most clients SHOULD call
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Close</tp:dbus-ref>
+ instead. However, if a client explicitly intends to destroy the
+ channel with possible loss of data, it SHOULD call this method
+ if this interface is supported (according to the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interfaces</tp:dbus-ref>
+ property), falling back to Close if not.</p>
+
+ <p>In particular, channel dispatchers SHOULD use this method if
+ available when terminating channels that cannot be handled
+ correctly (for instance, if no handler has been installed for
+ a channel type, or if the handler crashes repeatedly).</p>
+
+ <p>Connection managers do not need to implement this interface on
+ channels where Close and Destroy would be equivalent.</p>
+
+ <tp:rationale>
+ <p>Callers need to be able to fall back to Close in any case.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Group.xml b/qt4/spec/Channel_Interface_Group.xml
new file mode 100644
index 000000000..92de9c5c4
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Group.xml
@@ -0,0 +1,1166 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Group" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2005-2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2005-2009 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2006 INdT</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Group">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+
+ <tp:struct name="Local_Pending_Info" array-name="Local_Pending_Info_List">
+ <tp:docstring>A structure representing a contact whose attempt to
+ join a group is to be confirmed by the local user using
+ <tp:member-ref>AddMembers</tp:member-ref>.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="To_Be_Added">
+ <tp:docstring>
+ The contact to be added to the group
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" tp:type="Contact_Handle" name="Actor">
+ <tp:docstring>
+ The contact requesting or causing the change
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" tp:type="Channel_Group_Change_Reason" name="Reason">
+ <tp:docstring>
+ The reason for the change
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Message">
+ <tp:docstring>
+ A human-readable message from the Actor, or an empty string
+ if there is no message
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="AddMembers" tp:name-for-bindings="Add_Members">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of contact handles to invite to the channel
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A string message, which can be blank if desired
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Invite all the given contacts into the channel, or accept requests for
+ channel membership for contacts on the pending local list.</p>
+
+ <p>A message may be provided along with the request, which will be sent
+ to the server if supported. See the CHANNEL_GROUP_FLAG_MESSAGE_ADD and
+ CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT
+ <tp:member-ref>GroupFlags</tp:member-ref> to see in which cases this
+ message should be provided.</p>
+
+ <p>Attempting to add contacts who are already members is allowed;
+ connection managers must silently accept this, without error.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Full"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.InviteOnly"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Banned"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetAllMembers" tp:name-for-bindings="Get_All_Members">
+ <tp:deprecated version="0.17.6">Use GetAll on the D-Bus
+ Properties D-Bus interface to get properties including Members,
+ RemotePendingMembers and LocalPendingMembers instead, falling back to
+ this method and GetLocalPendingMembersWithInfo if necessary.
+ </tp:deprecated>
+
+ <arg direction="out" type="au" tp:type="Contact_Handle[]"
+ name="Members">
+ <tp:docstring>
+ array of handles of current members
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="au" tp:type="Contact_Handle[]"
+ name="Local_Pending">
+ <tp:docstring>
+ array of handles of local pending members
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="au" tp:type="Contact_Handle[]"
+ name="Remote_Pending">
+ <tp:docstring>
+ array of handles of remote pending members
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns arrays of all current, local and remote pending channel
+ members.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:flags name="Channel_Group_Flags" value-prefix="Channel_Group_Flag" type="u">
+ <tp:flag suffix="Can_Add" value="1">
+ <tp:docstring>
+ The <tp:member-ref>AddMembers</tp:member-ref> method can be used to
+ add or invite members who are
+ not already in the local pending list (which is always valid).
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Can_Remove" value="2">
+ <tp:docstring>
+ The <tp:member-ref>RemoveMembers</tp:member-ref> method can be used
+ to remove channel members
+ (removing those on the pending local list is always valid).
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Can_Rescind" value="4">
+ <tp:docstring>
+ The <tp:member-ref>RemoveMembers</tp:member-ref> method can be used
+ on people on the remote
+ pending list.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Message_Add" value="8">
+ <tp:docstring>
+ A message may be sent to the server when calling
+ <tp:member-ref>AddMembers</tp:member-ref> on
+ contacts who are not currently pending members.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Message_Remove" value="16">
+ <tp:docstring>
+ A message may be sent to the server when calling
+ <tp:member-ref>RemoveMembers</tp:member-ref> on
+ contacts who are currently channel members.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Message_Accept" value="32">
+ <tp:docstring>
+ A message may be sent to the server when calling
+ <tp:member-ref>AddMembers</tp:member-ref> on
+ contacts who are locally pending.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Message_Reject" value="64">
+ <tp:docstring>
+ A message may be sent to the server when calling
+ <tp:member-ref>RemoveMembers</tp:member-ref> on
+ contacts who are locally pending.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Message_Rescind" value="128">
+ <tp:docstring>
+ A message may be sent to the server when calling
+ <tp:member-ref>RemoveMembers</tp:member-ref> on
+ contacts who are remote pending.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Channel_Specific_Handles" value="256">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The members of this group have handles which are specific to
+ this channel, and are not valid as general-purpose handles on
+ the connection. Depending on the channel, it may be possible to
+ check the <tp:member-ref>HandleOwners</tp:member-ref> property or
+ call <tp:member-ref>GetHandleOwners</tp:member-ref> to find the
+ owners of these handles, which should be done if you wish to (e.g.)
+ subscribe to the contact's presence.
+ </p>
+
+ <p>
+ Connection managers must ensure that any given handle is not
+ simultaneously a general-purpose handle and a channel-specific
+ handle.
+ </p>
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Only_One_Group" value="512">
+ <tp:docstring>
+ Placing a contact in multiple groups of this type is not allowed
+ and will raise NotAvailable (on services where contacts may only
+ be in one user-defined group, user-defined groups will have
+ this flag).
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Handle_Owners_Not_Available" value="1024">
+ <tp:docstring>
+ In rooms with channel specific handles (ie Channel_Specific_Handles
+ flag is set), this flag indicates that no handle owners are
+ available, apart from the owner of the
+ <tp:member-ref>SelfHandle</tp:member-ref>.
+
+ <tp:rationale>
+ This used to be an important optimization to avoid repeated
+ GetHandleOwners calls, before we introduced the
+ <tp:member-ref>HandleOwners</tp:member-ref> property and
+ <tp:member-ref>HandleOwnersChanged</tp:member-ref> signal.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Properties" value="2048">
+ <tp:docstring>
+ This flag indicates that all the properties introduced in
+ specification 0.17.6 are fully supported.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Members_Changed_Detailed" value="4096">
+ <tp:docstring>
+ Indicates that <tp:member-ref>MembersChangedDetailed</tp:member-ref>
+ will be emitted for changes to this group's members in addition to
+ <tp:member-ref>MembersChanged</tp:member-ref>.
+ Clients can then connect to the former and ignore emission of the
+ latter. This flag's state MUST NOT change over the lifetime of a
+ channel.
+
+ <tp:rationale>
+ If it were allowed to change, client bindings would have to always
+ connect to MembersChanged just in case the flag ever went away (and
+ generally be unnecessarily complicated), which would mostly negate
+ the point of having this flag in the first place.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Message_Depart" value="8192">
+ <tp:added version="0.17.21"/>
+ <tp:docstring>
+ A message may be sent to the server when calling
+ <tp:member-ref>RemoveMembers</tp:member-ref> on
+ the <tp:member-ref>SelfHandle</tp:member-ref>.
+
+ <tp:rationale>
+ This would be set for XMPP Multi-User Chat or IRC channels,
+ but not for a typical implementation of streamed media calls.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <property name="GroupFlags" type="u" tp:type="Channel_Group_Flags"
+ access="read" tp:name-for-bindings="Group_Flags">
+ <tp:docstring>
+ An integer representing the bitwise-OR of flags on this
+ channel. The user interface can use this to present information about
+ which operations are currently valid. Change notification is via
+ the <tp:member-ref>GroupFlagsChanged</tp:member-ref> signal.
+ </tp:docstring>
+ <tp:added version="0.17.6">For backwards compatibility,
+ clients should fall back to calling GetGroupFlags if
+ Channel_Group_Flag_Properties is not present.</tp:added>
+ </property>
+
+ <method name="GetGroupFlags" tp:name-for-bindings="Get_Group_Flags">
+ <arg direction="out" type="u" tp:type="Channel_Group_Flags"
+ name="Group_Flags">
+ <tp:docstring>
+ The value of the GroupFlags property
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns the value of the <tp:member-ref>GroupFlags</tp:member-ref> property.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Use GetAll on the D-Bus
+ Properties D-Bus interface to get properties including GroupFlags
+ instead, falling back to this method if necessary.</tp:deprecated>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:mapping name="Handle_Owner_Map">
+ <tp:docstring>
+ A map from channel-specific handles to their owners.
+ </tp:docstring>
+ <tp:added version="0.17.6">For backwards compatibility,
+ clients should fall back to calling GetHandleOwners if
+ Channel_Group_Flag_Properties is not present.</tp:added>
+
+ <tp:member type="u" name="Channel_Specific_Handle"
+ tp:type="Contact_Handle">
+ <tp:docstring>
+ A nonzero channel-specific handle
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Global_Handle" tp:type="Contact_Handle">
+ <tp:docstring>
+ The global handle that owns the corresponding channel-specific
+ handle, or 0 if this could not be determined
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="HandleOwners" type="a{uu}" tp:type="Handle_Owner_Map"
+ access="read" tp:name-for-bindings="Handle_Owners">
+ <tp:docstring>
+ A map from channel-specific handles to their owners, including
+ at least all of the channel-specific handles in this channel's members,
+ local-pending or remote-pending sets as keys. Any handle not in
+ the keys of this mapping is not channel-specific in this channel.
+ Handles which are channel-specific, but for which the owner is
+ unknown, MUST appear in this mapping with 0 as owner. Change
+ notification is via the
+ <tp:member-ref>HandleOwnersChanged</tp:member-ref> signal.
+ </tp:docstring>
+ <tp:added version="0.17.6"/>
+ </property>
+
+ <signal name="HandleOwnersChanged"
+ tp:name-for-bindings="Handle_Owners_Changed">
+ <tp:docstring>
+ Emitted whenever the <tp:member-ref>HandleOwners</tp:member-ref>
+ property changes.
+ </tp:docstring>
+ <tp:added version="0.17.6">This signal should not be relied on
+ unless Channel_Group_Flag_Properties is present.</tp:added>
+
+ <arg name="Added" type="a{uu}" tp:type="Handle_Owner_Map">
+ <tp:docstring>
+ A map from channel-specific handles to their owners, in which the
+ keys include all the handles that were added to the keys of the
+ HandleOwners property, and all the handles in that property whose
+ owner has changed
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The channel-specific handles that were removed from the keys of the
+ HandleOwners property, as a result of the contact leaving this group
+ in a previous <tp:member-ref>MembersChanged</tp:member-ref> signal
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="GetHandleOwners" tp:name-for-bindings="Get_Handle_Owners">
+ <arg direction="in" name="Handles" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of integer handles representing members of the channel
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="au" tp:type="Contact_Handle[]" name="Owners">
+ <tp:docstring>
+ An array of integer handles representing the owner handles of
+ the given room members, in the same order, or 0 if the
+ owner is not available
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ If the CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES flag is set on
+ the channel, then the handles of the group members are specific
+ to this channel, and are not meaningful in a connection-wide
+ context such as contact lists. This method allows you to find
+ the owner of the handle if it can be discovered in this channel,
+ or 0 if the owner is not available.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Clients should use the
+ HandleOwners property and HandleOwnersChanged signal if
+ Channel_Group_Flag_Properties is present.</tp:deprecated>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This channel doesn't have the CHANNEL_SPECIFIC_HANDLES flag,
+ so handles in this channel are globally meaningful and calling
+ this method is not necessary
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ One of the given handles is not a member
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetLocalPendingMembers"
+ tp:name-for-bindings="Get_Local_Pending_Members">
+ <arg direction="out" type="au" tp:type="Contact_Handle[]"
+ name="Handles"/>
+ <tp:docstring>
+ Returns the To_Be_Added handle (only) for each structure in the
+ <tp:member-ref>LocalPendingMembers</tp:member-ref> property.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Use the LocalPendingMembers
+ property, if Channel_Group_Flag_Properties is present.</tp:deprecated>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetLocalPendingMembersWithInfo"
+ tp:name-for-bindings="Get_Local_Pending_Members_With_Info">
+ <tp:added version="0.15.0" />
+ <tp:docstring>
+ Returns the <tp:member-ref>LocalPendingMembers</tp:member-ref> property.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Use the LocalPendingMembers
+ property, if Channel_Group_Flag_Properties is present.</tp:deprecated>
+ <arg direction="out" type="a(uuus)" tp:type="Local_Pending_Info[]"
+ name="Info">
+ <tp:docstring>
+ An array of structs containing:
+ <ul>
+ <li>
+ A handle representing the contact requesting channel membership
+ </li>
+ <li>
+ A handle representing the contact making the request, or 0 if
+ unknown
+ </li>
+ <li>
+ The reason for the request: one of the values of
+ <tp:type>Channel_Group_Change_Reason</tp:type>
+ </li>
+ <li>
+ A string message containing the reason for the request if any (or
+ blank if none)
+ </li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="LocalPendingMembers" access="read"
+ type="a(uuus)" tp:type="Local_Pending_Info[]"
+ tp:name-for-bindings="Local_Pending_Members">
+ <tp:docstring>
+ An array of structs containing handles representing contacts
+ requesting channel membership and awaiting local approval with
+ <tp:member-ref>AddMembers</tp:member-ref>.
+ </tp:docstring>
+ <tp:added version="0.17.6">If Channel_Group_Flag_Properties is
+ not present, clients should fall back to using the
+ deprecated GetLocalPendingMembersWithInfo method, or fall back
+ from that to the deprecated GetAllMembers method.</tp:added>
+ </property>
+
+ <property name="Members" tp:name-for-bindings="Members"
+ access="read" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The members of this channel.
+ </tp:docstring>
+ <tp:added version="0.17.6">If Channel_Group_Flag_Properties
+ is not set, fall back to calling GetAllMembers.</tp:added>
+ </property>
+
+ <method name="GetMembers" tp:name-for-bindings="Get_Members">
+ <arg direction="out" type="au" tp:type="Contact_Handle[]"
+ name="Handles"/>
+ <tp:docstring>
+ Returns the <tp:member-ref>Members</tp:member-ref> property.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Use the Members
+ property, if Channel_Group_Flag_Properties is present.</tp:deprecated>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="RemotePendingMembers" access="read" type="au"
+ tp:type="Contact_Handle[]" tp:name-for-bindings="Remote_Pending_Members">
+ <tp:docstring>
+ An array of handles representing contacts who have been
+ invited to the channel and are awaiting remote approval.
+ </tp:docstring>
+ <tp:added version="0.17.6">If Channel_Group_Flag_Properties
+ is not set, fall back to calling GetAllMembers.</tp:added>
+ </property>
+
+ <method name="GetRemotePendingMembers"
+ tp:name-for-bindings="Get_Remote_Pending_Members">
+ <arg direction="out" type="au" tp:type="Contact_Handle[]"
+ name="Handles"/>
+ <tp:docstring>
+ Returns an array of handles representing contacts who have been
+ invited to the channel and are awaiting remote approval.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Use the
+ <tp:member-ref>RemotePendingMembers</tp:member-ref>
+ property, if Channel_Group_Flag_Properties is present.</tp:deprecated>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="SelfHandleChanged" tp:name-for-bindings="Self_Handle_Changed">
+ <tp:docstring>
+ Emitted whenever the <tp:member-ref>SelfHandle</tp:member-ref> property
+ changes.
+ </tp:docstring>
+ <tp:added version="0.17.6">This signal should not be relied on
+ unless Channel_Group_Flag_Properties is present.</tp:added>
+
+ <arg type="u" tp:type="Contact_Handle" name="Self_Handle">
+ <tp:docstring>
+ The new value of the SelfHandle property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="SelfHandle" type="u" tp:type="Contact_Handle"
+ access="read" tp:name-for-bindings="Self_Handle">
+ <tp:docstring>
+ The handle for the user on this channel (which can also be a
+ local or remote pending member), or 0 if the user is not a member at
+ all (which is likely to be the case, for instance, on <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">ContactList</tp:dbus-ref>
+ channels). Note that this is different from the result of
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.GetSelfHandle</tp:dbus-ref>
+ on some protocols, so the value of this handle should
+ always be used with the methods of this interface.
+ </tp:docstring>
+ <tp:added version="0.17.6">For backwards compatibility,
+ clients should fall back to calling GetSelfHandle if
+ Channel_Group_Flag_Properties is not present.</tp:added>
+ </property>
+
+ <method name="GetSelfHandle" tp:name-for-bindings="Get_Self_Handle">
+ <arg direction="out" type="u" tp:type="Contact_Handle"
+ name="Self_Handle"/>
+ <tp:docstring>
+ Returns the value of the <tp:member-ref>SelfHandle</tp:member-ref>
+ property.
+ </tp:docstring>
+ <tp:deprecated version="0.17.6">Clients should retrieve the
+ SelfHandle property using GetAll instead,
+ if Channel_Group_Flag_Properties is present.</tp:deprecated>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="GroupFlagsChanged" tp:name-for-bindings="Group_Flags_Changed">
+ <arg name="Added" type="u" tp:type="Channel_Group_Flags">
+ <tp:docstring>
+ A bitwise OR of the flags which have been set
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="u" tp:type="Channel_Group_Flags">
+ <tp:docstring>
+ A bitwise OR of the flags which have been cleared
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the flags as returned by
+ <tp:member-ref>GetGroupFlags</tp:member-ref> are changed.
+ The user interface should be updated as appropriate.
+ </tp:docstring>
+ </signal>
+
+ <tp:enum name="Channel_Group_Change_Reason" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The reason for a set of handles to move to one of
+ <tp:member-ref>Members</tp:member-ref>,
+ <tp:member-ref>LocalPendingMembers</tp:member-ref> or
+ <tp:member-ref>RemotePendingMembers</tp:member-ref>, or to be removed
+ from the group. A client may supply a reason when attempting to
+ remove members from a group with
+ <tp:member-ref>RemoveMembersWithReason</tp:member-ref>, and reasons
+ are supplied by the CM when emitting
+ <tp:member-ref>MembersChanged</tp:member-ref> and
+ <tp:member-ref>MembersChangedDetailed</tp:member-ref>. Some reason
+ codes have different meanings depending on the <var>Actor</var> in a
+ MembersChanged signal.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>No reason was provided for this change.</p>
+
+ <p>In particular, this reason SHOULD be used when representing
+ users joining a named chatroom in the usual way, users leaving
+ a chatroom by their own request, and normal termination of a
+ StreamedMedia call by the remote user.</p>
+
+ <p>If the <tp:member-ref>SelfHandle</tp:member-ref> is removed from
+ a group for this reason and the actor is not the SelfHandle, the
+ equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Terminated</code>.</p>
+
+ <p>If the SelfHandle is removed from a group for this reason and
+ the actor is also the SelfHandle, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cancelled</code>.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Offline" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is due to a user going offline. Also used when
+ user is already offline, but this wasn't known previously.</p>
+
+ <p>If a one-to-one <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ call fails because the contact being called is offline, the
+ connection manager SHOULD indicate this by removing both the
+ <tp:member-ref>SelfHandle</tp:member-ref> and the other contact's
+ handle from the Group interface with reason Offline.</p>
+
+ <tp:rationale>
+ For 1-1 calls, the call terminates as a result of removing the
+ remote contact, so the SelfHandle should be removed at the same
+ time as the remote contact and for the same reason.
+ </tp:rationale>
+
+ <p>If a handle is removed from a group for this reason, the
+ equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Offline</code>.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Kicked" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is due to a kick operation.</p>
+
+ <p>If the <tp:member-ref>SelfHandle</tp:member-ref> is removed
+ from a group for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Channel.Kicked</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Busy" value="3">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is due to a busy indication.</p>
+
+ <p>If a one-to-one <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ call fails because the contact being called is busy, the
+ connection manager SHOULD indicate this by removing both the
+ <tp:member-ref>SelfHandle</tp:member-ref> and the other contact's
+ handle from the Group interface with reason Busy.</p>
+
+ <tp:rationale>
+ For 1-1 calls, the call terminates as a result of removing the
+ remote contact, so the SelfHandle should be removed at the same
+ time as the remote contact and for the same reason.
+ </tp:rationale>
+
+ <p>If the <tp:member-ref>SelfHandle</tp:member-ref> is removed
+ from a group for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Busy</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Invited" value="4">
+ <tp:docstring>
+ The change is due to an invitation. This reason SHOULD only be used
+ when contacts are added to the remote-pending set (to indicate that
+ the contact has been invited) or to the members (to indicate that
+ the contact has accepted the invitation).
+
+ <tp:rationale>
+ Otherwise, what would it mean?
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Banned" value="5">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is due to a kick+ban operation.</p>
+
+ <p>If the <tp:member-ref>SelfHandle</tp:member-ref> is removed
+ from a group for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Channel.Banned</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Error" value="6">
+ <tp:docstring>
+ The change is due to an error occurring.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Invalid_Contact" value="7">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is because the requested contact does not exist.</p>
+
+ <p>For instance, if the user invites a nonexistent contact to a
+ chatroom or attempts to call a nonexistent contact, this could
+ be indicated by the CM adding that contact's handle to
+ remote-pending for reason None or Invited, then removing it for
+ reason Invalid_Contact. In the case of a 1-1 StreamedMedia
+ call, the CM SHOULD remove the self handle from the Group
+ in the same signal.</p>
+
+ <tp:rationale>
+ For 1-1 calls, the call terminates as a result of removing the
+ remote contact, so the SelfHandle should be removed at the same
+ time as the remote contact and for the same reason.
+ </tp:rationale>
+
+ <p>If a contact is removed from a group for this reason, the
+ equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.DoesNotExist</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="No_Answer" value="8">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is because the requested contact did not respond.</p>
+
+ <p>If a one-to-one <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ call fails because the contact being called did not respond, or the
+ local user did not respond to an incoming call, the
+ connection manager SHOULD indicate this by removing both the
+ <tp:member-ref>SelfHandle</tp:member-ref> and the other contact's
+ handle from the Group interface with reason No_Answer.</p>
+
+ <tp:rationale>
+ Documenting existing practice.
+ </tp:rationale>
+
+ <p>If a contact is removed from a group for this reason, the
+ equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.NoAnswer</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Renamed" value="9">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is because a contact's unique identifier changed.
+ There must be exactly one handle in the removed set and exactly
+ one handle in one of the added sets. The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Renaming">Renamed</tp:dbus-ref>
+ signal on the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Renaming</tp:dbus-ref>
+ interface will have been emitted for the same handles,
+ shortly before this <tp:member-ref>MembersChanged</tp:member-ref> signal is emitted.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Permission_Denied" value="10">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is because there was no permission to contact the
+ requested handle.</p>
+
+ <p>If a contact is removed from a group for this reason, the
+ equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.PermissionDenied</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Separated" value="11">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If members are removed with this reason code, the change is
+ because the group has split into unconnected parts which can only
+ communicate within themselves (e.g. netsplits on IRC use this
+ reason code).
+ </p>
+ <p>
+ If members are added with this reason code, the change is because
+ unconnected parts of the group have rejoined. If this channel
+ carries messages (e.g. <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Tubes</tp:dbus-ref>
+ channels) applications must
+ assume that the contacts being added are likely to have missed some
+ messages as a result of the separation, and that the contacts
+ in the group are likely to have missed some messages from the
+ contacts being added.
+ </p>
+ <p>Note that from the added contacts' perspective, they have been
+ in the group all along, and the contacts we indicate to be in
+ the group (including the local user) have just rejoined
+ the group with reason Separated. Application protocols in Tubes
+ should be prepared to cope with this situation.
+ </p>
+
+ <p>The <tp:member-ref>SelfHandle</tp:member-ref> SHOULD NOT be
+ removed from channels with this reason.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <signal name="MembersChanged" tp:name-for-bindings="Members_Changed">
+ <arg name="Message" type="s">
+ <tp:docstring>
+ A string message from the server, or blank if not
+ </tp:docstring>
+ </arg>
+ <arg name="Added" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members added to the channel
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members removed from the channel
+ </tp:docstring>
+ </arg>
+ <arg name="Local_Pending" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who are pending local approval
+ </tp:docstring>
+ </arg>
+ <arg name="Remote_Pending" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who are pending remote approval
+ </tp:docstring>
+ </arg>
+ <arg name="Actor" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact handle of the person who made the change, or 0
+ if not known
+ </tp:docstring>
+ </arg>
+ <arg name="Reason" type="u" tp:type="Channel_Group_Change_Reason">
+ <tp:docstring>
+ A reason for the change
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when contacts join any of the three lists (members, local
+ pending or remote pending) or when they leave any of the three lists.
+ There may also be a message from the server regarding this change,
+ which may be displayed to the user if desired.</p>
+
+ <p>All channel-specific handles that are mentioned in this signal
+ MUST be represented in the value of the
+ <tp:member-ref>HandleOwners</tp:member-ref> property.
+ In practice, this will mean that
+ <tp:member-ref>HandleOwnersChanged</tp:member-ref> is
+ emitted <em>before</em> emitting a MembersChanged signal in which
+ channel-specific handles are added, but that it is emitted
+ <em>after</em> emitting a MembersChanged signal in which
+ channel-specific handles are removed.</p>
+
+ <p>See <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ for an overview of how group state changes are used to indicate the
+ progress of a call.</p>
+ </tp:docstring>
+ </signal>
+
+ <tp:mapping name="Handle_Identifier_Map">
+ <tp:docstring>
+ A map from handles to the corresponding normalized string identifier.
+ </tp:docstring>
+ <tp:added version="0.17.17"/>
+
+ <tp:member type="u" name="Handle" tp:type="Contact_Handle">
+ <tp:docstring>
+ A nonzero handle
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Identifier">
+ <tp:docstring>
+ The same string that would be returned by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>
+ for this handle.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <signal name="MembersChangedDetailed"
+ tp:name-for-bindings="Members_Changed_Detailed">
+ <arg name="Added" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members added to the channel
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members removed from the channel
+ </tp:docstring>
+ </arg>
+ <arg name="Local_Pending" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who are pending local approval
+ </tp:docstring>
+ </arg>
+ <arg name="Remote_Pending" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who are pending remote approval
+ </tp:docstring>
+ </arg>
+ <arg name="Details" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Information about the change, which may include the following
+ well-known keys:</p>
+
+ <dl>
+ <dt>actor (u — <tp:type>Contact_Handle</tp:type>)</dt>
+ <dd>The contact handle of the person who made the change; 0 or
+ omitted if unknown or not applicable.</dd>
+
+ <dt>change-reason (u — <tp:type>Channel_Group_Change_Reason</tp:type>)</dt>
+ <dd>A reason for the change.</dd>
+
+ <dt>contact-ids (a{us} — <tp:type>Handle_Identifier_Map</tp:type>)</dt>
+ <dd>
+ <p>The string identifiers for handles mentioned in this signal, to
+ give clients the minimal information necessary to react to the
+ event without waiting for round-trips. Connection managers
+ SHOULD include the identifiers for members added to the group and
+ for the actor (if any); they MAY omit the identifiers for handles
+ which have been removed from the group.</p>
+
+ <tp:rationale>
+ <p>On IRC, an event such as a netsplit could cause the vast
+ majority of a channel to leave. Given that clients should
+ already know the identifiers of a channel's members, including
+ potentially hundreds of strings in the netsplit signal is
+ unnecessary.</p>
+ </tp:rationale>
+
+ <p>Clients MUST NOT assume that the presence or absence of a
+ handle in this mapping is meaningful. This mapping is merely
+ an optimization for round-trip reduction, and connection
+ managers MAY add additional handles, omit some handles, or
+ omit the mapping completely.</p>
+ </dd>
+
+ <dt>message (s)</dt>
+ <dd>A string message from the server regarding the change</dd>
+
+ <dt>error (s — <tp:type>DBus_Error_Name</tp:type>)</dt>
+ <dd>A (possibly implementation-specific) DBus error describing the
+ change, providing more specific information than the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum allows. This
+ MUST only be present if it is strictly more informative than
+ 'change-reason'; if present, 'change-reason' MUST be set to the
+ closest available reason.
+
+ <tp:rationale>
+ A SIP connection manager might want to signal "402 Payment
+ required" as something more specific than Error or
+ Permission_Denied so that a SIP-aware UI could handle it
+ specially; including a namespaced error permits this to be done
+ without <tp:type>Channel_Group_Change_Reason</tp:type> being
+ extended to encompass every error any CM ever wants to report.
+ </tp:rationale>
+ </dd>
+
+ <dt>debug-message (s)</dt>
+ <dd>Debugging information on the change. SHOULD NOT be shown to
+ users in normal circumstances.</dd>
+ </dl>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when contacts join any of the three lists (members, local
+ pending or remote pending) or when they leave any of the three
+ lists. This signal provides a superset of the information provided by
+ <tp:member-ref>MembersChanged</tp:member-ref>;
+ if the channel's <tp:member-ref>GroupFlags</tp:member-ref>
+ contains Members_Changed_Detailed, then clients may listen exclusively
+ to this signal in preference to that signal.</p>
+
+ <p>All channel-specific handles that are mentioned in this signal
+ MUST be represented in the value of the
+ <tp:member-ref>HandleOwners</tp:member-ref> property. In practice,
+ this will mean that
+ <tp:member-ref>HandleOwnersChanged</tp:member-ref> is emitted
+ <em>before</em> emitting a MembersChangedDetailed signal in which
+ channel-specific handles are added, but that it is emitted
+ <em>after</em> emitting a MembersChangedDetailed signal in which
+ channel-specific handles are removed.</p>
+
+ <p>See <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ for an overview of how group state changes are used to indicate the
+ progress of a call.</p>
+ </tp:docstring>
+ <tp:added version="0.17.16"/>
+ </signal>
+
+ <method name="RemoveMembers" tp:name-for-bindings="Remove_Members">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of contact handles to remove from the channel
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A string message, which can be blank if desired
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Requests the removal of contacts from a channel, reject their
+ request for channel membership on the pending local list, or
+ rescind their invitation on the pending remote list.</p>
+
+ <p>If the <tp:member-ref>SelfHandle</tp:member-ref> is in a Group,
+ it can be removed via this method, in order to leave the group
+ gracefully. This is the recommended way to leave a chatroom, close
+ or reject a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ call, and so on.</p>
+
+ <p>Accordingly, connection managers SHOULD support
+ doing this, regardless of the value of
+ <tp:member-ref>GroupFlags</tp:member-ref>.
+ If doing so fails with PermissionDenied, this is considered to a bug
+ in the connection manager, but clients MUST recover by falling back
+ to closing the channel with the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+ method.</p>
+
+ <p>Removing any contact from the local pending list is always
+ allowed. Removing contacts other than the
+ <tp:member-ref>SelfHandle</tp:member-ref> from the channel's members
+ is allowed if and only if Channel_Group_Flag_Can_Remove is in the
+ <tp:member-ref>GroupFlags</tp:member-ref>,
+ while removing contacts other than the
+ <tp:member-ref>SelfHandle</tp:member-ref> from the remote pending list
+ is allowed if and only if Channel_Group_Flag_Can_Rescind is in the
+ <tp:member-ref>GroupFlags</tp:member-ref>.</p>
+
+ <p>A message may be provided along with the request, which will be
+ sent to the server if supported. See the
+ Channel_Group_Flag_Message_Remove,
+ Channel_Group_Flag_Message_Depart,
+ Channel_Group_Flag_Message_Reject and
+ Channel_Group_Flag_Message_Rescind
+ <tp:member-ref>GroupFlags</tp:member-ref> to see in which cases this
+ message should be provided.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RemoveMembersWithReason"
+ tp:name-for-bindings="Remove_Members_With_Reason">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of contact handles to remove from the channel
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A string message, which can be blank if desired
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Reason" type="u"
+ tp:type="Channel_Group_Change_Reason">
+ <tp:docstring>
+ A reason for the change
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ As <tp:member-ref>RemoveMembers</tp:member-ref>, but a reason code may
+ be provided where
+ appropriate. The reason code may be ignored if the underlying
+ protocol is unable to represent the given reason.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The provided reason code was invalid.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for channels which have multiple members, and where the members
+ of the channel can change during its lifetime. Your presence in the channel
+ cannot be presumed by the channel's existence (for example, a channel you
+ may request membership of but your request may not be granted).</p>
+
+ <p>This interface implements three lists: a list of current members
+ (<tp:member-ref>Members</tp:member-ref>), and two lists of local pending
+ and remote pending members
+ (<tp:member-ref>LocalPendingMembers</tp:member-ref> and
+ <tp:member-ref>RemotePendingMembers</tp:member-ref>, respectively).
+ Contacts on the remote
+ pending list have been invited to the channel, but the remote user has not
+ accepted the invitation. Contacts on the local pending list have requested
+ membership of the channel, but the local user of the framework must accept
+ their request before they may join. A single contact should never appear on
+ more than one of the three lists. The lists are empty when the channel is
+ created, and the <tp:member-ref>MembersChanged</tp:member-ref> signal
+ (and, if the channel's <tp:member-ref>GroupFlags</tp:member-ref> contains
+ Members_Changed_Detailed, the
+ <tp:member-ref>MembersChangedDetailed</tp:member-ref> signal)
+ should be emitted when information
+ is retrieved from the server, or changes occur.</p>
+
+ <p>If the <tp:member-ref>MembersChanged</tp:member-ref> or
+ <tp:member-ref>MembersChangedDetailed</tp:member-ref> signal indicates
+ that the <tp:member-ref>SelfHandle</tp:member-ref> has been removed from
+ the channel, and the channel subsequently emits <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Closed</tp:dbus-ref>,
+ clients SHOULD consider the details given in the MembersChanged or
+ MembersChangedDetailed signal to be the reason why the channel closed.</p>
+
+ <p>Addition of members to the channel may be requested by using
+ <tp:member-ref>AddMembers</tp:member-ref>. If
+ remote acknowledgement is required, use of the AddMembers method will cause
+ users to appear on the remote pending list. If no acknowledgement is
+ required, AddMembers will add contacts to the member list directly. If a
+ contact is awaiting authorisation on the local pending list, AddMembers
+ will grant their membership request.</p>
+
+ <p>Removal of contacts from the channel may be requested by using
+ <tp:member-ref>RemoveMembers</tp:member-ref>. If a contact is awaiting
+ authorisation on the local pending
+ list, RemoveMembers will refuse their membership request. If a contact is
+ on the remote pending list but has not yet accepted the invitation,
+ RemoveMembers will rescind the request if possible.</p>
+
+ <p>It should not be presumed that the requester of a channel implementing this
+ interface is immediately granted membership, or indeed that they are a
+ member at all, unless they appear in the list. They may, for instance,
+ be placed into the remote pending list until a connection has been
+ established or the request acknowledged remotely.</p>
+
+ <p>If the local user joins a Group channel whose members or other state
+ cannot be discovered until the user joins (e.g. many chat room
+ implementations), the connection manager should ensure that the channel
+ is, as far as possible, in a consistent state before adding the local
+ contact to the members set; until this happens, the local contact should
+ be in the remote-pending set. For instance, if the connection manager
+ queries the server to find out the initial members list for the
+ channel, it should leave the local contact in the remote-pending set
+ until it has finished receiving the initial members list.
+ </p>
+
+ <p>If the protocol provides no reliable way to tell whether the complete
+ initial members list has been received yet, the connection manager
+ should make a best-effort attempt to wait for the full list
+ (in the worst case, waiting for a suitable arbitrary timeout)
+ rather than requiring user interfaces to do so on its behalf.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_HTML.xml b/qt4/spec/Channel_Interface_HTML.xml
new file mode 100644
index 000000000..ad86867ca
--- /dev/null
+++ b/qt4/spec/Channel_Interface_HTML.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_HTML"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2008 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.HTML.DRAFT"
+ tp:causes-havoc="unfinished">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.Text"/>
+ <tp:requires
+ interface="org.freedesktop.Telepathy.Channel.Interface.Messages"/>
+ <tp:added version="0.17.5">(draft version, not API-stable)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface extends the Messages interface to support
+ capability discovery, so clients can decide what subset of HTML
+ is supported.</p>
+
+ <p>(However, the capability discovery mechanism has not been written
+ yet, so this interface MUST NOT be used. It exists only to
+ indicate what direction we intend to go in.)</p>
+
+ <tp:rationale>
+ <p>XMPP supports all of XHTML-IM, and SIP (at least theoretically)
+ supports all of XHTML. However, many protocols are more limited -
+ for instance, in MSN you can only set font properties for a
+ whole message at a time. We should not mislead users into thinking
+ they can send MSN messages where individual words are emphasized.</p>
+ </tp:rationale>
+
+ <p>If this interface is present, clients MAY send XHTML formatted text
+ in message parts with type "text/html", and SHOULD interpret
+ "text/html" message parts received in reply.</p>
+
+ <p>Client authors SHOULD pay careful attention to the security
+ considerations in XEP-0071, "XHTML-IM", to avoid exposing client users
+ to security risks. Clients MUST NOT assume that connection managers
+ will filter messages to remove unsafe HTML.</p>
+
+ <tp:rationale>
+ <p>Connection managers are the components in Telepathy that are most
+ likely to be exploitable by a remote attacker to run malicious code
+ (since they are network-facing), so any filtering that the CM does
+ might be subverted.</p>
+ </tp:rationale>
+
+ <p>To avoid misleading users, clients SHOULD only present UI for the
+ subset of HTML that is indicated to be supported by this
+ interface. It follows that clients SHOULD NOT send unsupported
+ markup to the connection manager. However, even if the connection
+ manager cannot send arbitrary XHTML, it MUST cope gracefully
+ with being given arbitrary XHTML by a client.</p>
+
+ <tp:rationale>
+ <p>Connection managers should be lenient in what they receive.</p>
+ </tp:rationale>
+
+ <p>Clients MUST NOT send HTML that is not well-formed XML, but
+ connection managers MAY signal HTML that is malformed or invalid.
+ Clients SHOULD attempt to parse messages as XHTML, but fall back
+ to using a permissive "tag-soup" HTML parser if that fails.
+ (FIXME: or should the presence of this interface imply that the
+ CM fixes up "text/html" to be XHTML? In practice that would result
+ in all the CMs having to link against libxml2 or something... the
+ rationale above no longer applies here, since dropping a malformed
+ message is "safe")</p>
+ </tp:docstring>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Hold.xml b/qt4/spec/Channel_Interface_Hold.xml
new file mode 100644
index 000000000..ef5a08f19
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Hold.xml
@@ -0,0 +1,222 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Hold" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005-2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005-2008 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Hold">
+ <tp:xor-requires>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.StreamedMedia"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT"/>
+ </tp:xor-requires>
+ <tp:changed version="0.17.4">first API-stable version</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for channels where you may put the channel on hold.
+ This only makes sense for channels where
+ you are streaming media to or from the members. (To see whether the
+ other participant has put you on hold, see <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">CallState</tp:dbus-ref>.)</p>
+
+ <p>If you place a channel on hold, this indicates that you do not wish
+ to be sent media streams by any of its members and will be ignoring
+ any media streams you continue to receive. It also requests that the
+ connection manager free up any resources that are only needed for
+ an actively used channel (e.g. in a GSM or PBX call, it will be
+ necessary to place an active call on hold before you can start
+ another call).</p>
+ </tp:docstring>
+
+ <method name="GetHoldState" tp:name-for-bindings="Get_Hold_State">
+ <tp:docstring>
+ Return whether the local user has placed the channel on hold.
+ </tp:docstring>
+
+ <arg name="HoldState" direction="out" type="u"
+ tp:type="Local_Hold_State">
+ <tp:docstring>
+ The state of the channel
+ </tp:docstring>
+ </arg>
+
+ <arg name="Reason" direction="out" type="u"
+ tp:type="Local_Hold_State_Reason">
+ <tp:docstring>
+ The reason why the channel is in that state
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="HoldStateChanged" tp:name-for-bindings="Hold_State_Changed">
+ <tp:docstring>
+ Emitted to indicate that the hold state has changed for this channel.
+ This may occur as a consequence of you requesting a change with
+ <tp:member-ref>RequestHold</tp:member-ref>, or the state changing as a
+ result of a request from
+ another process.
+ </tp:docstring>
+
+ <arg name="HoldState" type="u" tp:type="Local_Hold_State">
+ <tp:docstring>
+ The state of the channel
+ </tp:docstring>
+ </arg>
+
+ <arg name="Reason" type="u" tp:type="Local_Hold_State_Reason">
+ <tp:docstring>
+ The reason for the state change
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Local_Hold_State" type="u">
+ <tp:docstring>
+ The hold state of a channel.
+ </tp:docstring>
+
+ <tp:enumvalue value="0" suffix="Unheld">
+ <tp:docstring>
+ All streams are unheld (the call is active). New channels SHOULD
+ have this hold state.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="1" suffix="Held">
+ <tp:docstring>
+ All streams are held (the call is on hold)
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="2" suffix="Pending_Hold">
+ <tp:docstring>
+ The connection manager is attempting to move to state Held, but
+ has not yet completed that operation. It is unspecified whether
+ any, all or none of the streams making up the channel are on hold.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="3" suffix="Pending_Unhold">
+ <tp:docstring>
+ The connection manager is attempting to move to state Held, but
+ has not yet completed that operation. It is unspecified whether
+ any, all or none of the streams making up the channel are on hold.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Local_Hold_State_Reason" type="u">
+ <tp:docstring>
+ The reason for a change to the Local_Hold_State. Clients MUST
+ treat unknown values as equivalent to Local_Hold_State_Reason_None.
+ </tp:docstring>
+
+ <tp:enumvalue value="0" suffix="None">
+ <tp:docstring>
+ The reason cannot be described by any of the predefined values
+ (connection managers SHOULD avoid this reason, but clients MUST
+ handle it gracefully)
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="1" suffix="Requested">
+ <tp:docstring>
+ The change is in response to a user request
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="2" suffix="Resource_Not_Available">
+ <tp:docstring>
+ The change is because some resource was not available
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <method name="RequestHold" tp:name-for-bindings="Request_Hold">
+ <arg direction="in" name="Hold" type="b">
+ <tp:docstring>
+ A boolean indicating whether or not the channel should be on hold
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the channel be put on hold (be instructed not to send
+ any media streams to you) or be taken off hold.</p>
+
+ <p>If the connection manager can immediately tell that the requested
+ state change could not possibly succeed, this method SHOULD
+ return the NotAvailable error. If the requested state is the
+ same as the current state, this method SHOULD return successfully
+ without doing anything.</p>
+
+ <p>Otherwise, this method SHOULD immediately set the hold state to
+ Local_Hold_State_Pending_Hold or Local_Hold_State_Pending_Unhold
+ (as appropriate), emitting
+ <tp:member-ref>HoldStateChanged</tp:member-ref> if this is a change,
+ and return successfully.</p>
+
+ <p>The eventual success or failure of the request is indicated by a
+ subsequent HoldStateChanged signal, changing the hold state to
+ Local_Hold_State_Held or Local_Hold_State_Unheld.</p>
+
+ <p>If the channel has multiple streams, and the connection manager
+ succeeds in changing the hold state of one stream but fails to
+ change the hold state of another, it SHOULD attempt to revert
+ all streams to their previous hold states.</p>
+
+ <p>The following state transitions SHOULD be used, where
+ appropriate:</p>
+
+ <ul>
+ <li>Successful hold:
+ (Unheld, any reason) → (Pending_Hold, Requested) →
+ (Held, Requested)
+ </li>
+ <li>Successful unhold:
+ (Held, any reason) → (Pending_Unhold, Requested) →
+ (Unheld, Requested)
+ </li>
+ <li>Attempting to unhold fails at the first attempt to acquire a
+ resource:
+ (Held, any reason) → (Pending_Unhold, Requested) →
+ (Held, Resource_Not_Available)
+ </li>
+ <li>Attempting to unhold acquires one resource, but fails to acquire
+ a second, and takes time to release the first:
+ (Held, any reason) → (Pending_Unhold, Requested) →
+ (Pending_Hold, Resource_Not_Available) →
+ (Held, Resource_Not_Available)
+ </li>
+ </ul>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The requested hold state cannot be achieved; for example,
+ if only a limited number of channels can be in the "not on hold"
+ state, attempts to exceed this number will raise NotAvailable.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Media_Signalling.xml b/qt4/spec/Channel_Interface_Media_Signalling.xml
new file mode 100644
index 000000000..242c95284
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Media_Signalling.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Media_Signalling" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2009 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright © 2005-2009 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.MediaSignalling">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.StreamedMedia"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for signalling a channel containing synchronised media
+ sessions which can contain an arbitrary number of streams. The
+ presence of this interface on a Channel indicates that the connection
+ manager will not carry out the actual streaming for this channel,
+ and that the client handling the channel is responsible for doing
+ so; in most cases we recommend doing this by using the
+ telepathy-farsight library.</p>
+
+ <tp:rationale>
+ <p>Streaming audio and (particularly) video requires a high level of
+ integration with the UI, and having the connection manager act as
+ a proxy would be likely to introduce unacceptable latency. As a
+ result, audio/video streaming is offloaded into the client
+ where possible, as an exception to the general design of
+ Telepathy.</p>
+ </tp:rationale>
+
+ <p>The negotiation interface is based on the API of the
+ <a href="http://farsight.freedesktop.org/">Farsight</a> library.
+ This, in turn, is based upon the IETF MMusic ICE drafts, where
+ connections are established by signalling potential connection
+ candidates to the peer until a usable connection is found, and
+ codecs are negotiated with an SDP-style offer and answer. However,
+ the principles should be applicable to other media streaming methods
+ and the API re-used without difficulty.</p>
+
+ <p>Note that the naming conventions used in the MediaStreamHandler
+ and MediaSessionHandler interfaces are rather confusing; methods
+ have signal-like names and signals have method-like names, due to
+ the API being based rather too closely on that of Farsight. This
+ is for historical reasons and will be fixed in a future release
+ of the Telepathy specification.</p>
+ </tp:docstring>
+
+ <tp:simple-type name="Media_Session_Type" type="s">
+ <tp:docstring>The type of a media session. Currently, the only supported
+ value is "rtp".</tp:docstring>
+ </tp:simple-type>
+
+ <tp:struct name="Media_Session_Handler_Info"
+ array-name="Media_Session_Handler_Info_List">
+ <tp:docstring>A struct representing a active session handler.</tp:docstring>
+ <tp:member type="o" name="Session_Handler">
+ <tp:docstring>The object path of the session handler, which is on the
+ same bus name as the channel.</tp:docstring>
+ </tp:member>
+ <tp:member type="s" tp:type="Media_Session_Type" name="Media_Session_Type">
+ <tp:docstring>The media session's type</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="GetSessionHandlers"
+ tp:name-for-bindings="Get_Session_Handlers">
+ <arg direction="out" type="a(os)" tp:type="Media_Session_Handler_Info[]"
+ name="Session_Handlers"/>
+ <tp:docstring>
+ Returns all currently active session handlers on this channel
+ as a list of (session_handler_path, type).
+ </tp:docstring>
+ </method>
+
+ <signal name="NewSessionHandler" tp:name-for-bindings="New_Session_Handler">
+ <arg name="Session_Handler" type="o">
+ <tp:docstring>
+ Object path of the new <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Media.SessionHandler</tp:dbus-ref>
+ object
+ </tp:docstring>
+ </arg>
+ <arg name="Session_Type" tp:type="Media_Session_Type" type="s">
+ <tp:docstring>
+ String indicating type of session, eg &quot;rtp&quot;
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal that a session handler object has been created. The client
+ should create a session object and create streams for the streams
+ within.
+ </tp:docstring>
+ </signal>
+
+ <tp:property name="nat-traversal" type="s">
+ <tp:deprecated version="0.17.22">Use the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">NATTraversal</tp:dbus-ref>
+ property on the Media.StreamHandler, if available; use this
+ as a fallback.</tp:deprecated>
+ <tp:docstring>
+ A string indicating the NAT traversal techniques employed by the
+ streams within this channel if they do not have a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">NATTraversal</tp:dbus-ref>
+ property. The possible values are the same as for the NATTraversal
+ property on the streams.
+ </tp:docstring>
+ </tp:property>
+
+ <tp:property name="stun-server" type="s">
+ <tp:deprecated version="0.17.22">Use the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">STUNServers</tp:dbus-ref>
+ property on the Media.StreamHandler, if available; use this
+ as a fallback.</tp:deprecated>
+ <tp:docstring>
+ The IP address or hostname of the STUN server to use for NAT traversal
+ if the individual streams do not specify one.
+ </tp:docstring>
+ </tp:property>
+
+ <tp:property name="stun-port" type="q">
+ <tp:deprecated version="0.17.22">Use the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">STUNServers</tp:dbus-ref>
+ property on the Media.StreamHandler, if available; use this
+ as a fallback.</tp:deprecated>
+ <tp:docstring>
+ The UDP port number to use on the provided STUN server.
+ </tp:docstring>
+ </tp:property>
+
+ <tp:property name="gtalk-p2p-relay-token" type="s">
+ <tp:deprecated version="0.17.22">XMPP connection managers
+ supporting the Google Talk relay server SHOULD make the necessary
+ HTTP requests to find a username and password, and use those
+ to populate the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">RelayInfo</tp:dbus-ref>
+ property on the Media.StreamHandler.</tp:deprecated>
+ <tp:docstring>
+ The authentication token for use with the Google Talk peer-to-peer relay
+ server.
+ </tp:docstring>
+ </tp:property>
+
+ <tp:hct name="gtalk-p2p">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">NATTraversal</tp:dbus-ref>
+ property is <code>gtalk-p2p</code>.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="ice-udp">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">NATTraversal</tp:dbus-ref>
+ property is <code>ice-udp</code>.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="wlm-8.5">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">NATTraversal</tp:dbus-ref>
+ property is <code>wlm-8.5</code>.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="wlm-2009">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media.StreamHandler">NATTraversal</tp:dbus-ref>
+ property is <code>wlm-2009</code>.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="video/h264" is-family="yes">
+ <tp:docstring>
+ <p>The client supports media streaming with H264 (etc.).</p>
+
+ <p>This handler capability token is a one of a family
+ of similar tokens: for any other audio or video codec whose MIME
+ type is audio/<em>subtype</em> or video/<em>subtype</em>, a handler
+ capability token of this form may exist (the subtype MUST appear
+ in lower case in this context). Clients MAY support more
+ codecs than they explicitly advertise support for; clients SHOULD
+ explicitly advertise support for their preferred codec(s), and
+ for codecs like H264 that are, in practice, significant in codec
+ negotiation.</p>
+
+ <tp:rationale>
+ <p>For instance, the XMPP capability used by the Google Video
+ Chat web client to determine whether a client is compatible
+ with it requires support for H264 video, so an XMPP
+ connection manager that supports this version of Jingle should
+ not advertise the Google Video Chat capability unless there
+ is at least one installed client that declares that it supports
+ <code>video/h264</code> on StreamedMedia channels.</p>
+ </tp:rationale>
+
+ <p>For example, a client could advertise support for
+ Speex, Theora and H264 by having three
+ handler capability tokens,
+ <code>org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/audio/speex</code>,
+ <code>org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/video/theora</code> and
+ <code>org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/video/h264</code>,
+ in its <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">Capabilities</tp:dbus-ref>
+ property.</p>
+
+ <p>Clients MAY have media signalling abilities without explicitly
+ supporting any particular codec, and connection managers SHOULD
+ support this usage.</p>
+
+ <tp:rationale>
+ <p>This is necessary to support gatewaying between two Telepathy
+ connections, in which case the available codecs might not be
+ known to the gatewaying process.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:hct>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Mergeable_Conference.xml b/qt4/spec/Channel_Interface_Mergeable_Conference.xml
new file mode 100644
index 000000000..cd606c1b7
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Mergeable_Conference.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Mergeable_Conference"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.MergeableConference.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Interface.Conference"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for multi-user conference channels that can have
+ additional individual channels merged into them after they are
+ created.</p>
+
+ <tp:rationale>
+ <p>This interface addresses part of freedesktop.org <a
+ href="http://bugs.freedesktop.org/show_bug.cgi?id=24906">bug
+ #24906</a> (GSM-compatible conference calls). GSM is currently
+ the only protocol known to implement this; PBXs might implement
+ it too.</p>
+
+ <p>It might be made into a mandatory-to-implement part of Conference,
+ or kept as a separate interface, when stabilized.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <method name="Merge"
+ tp:name-for-bindings="Merge">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the given channel be incorporated into this
+ channel.</p>
+
+ <p>The given channel SHOULD be added to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Conference.Channels</tp:dbus-ref> if and only if the
+ underlying protocol signals the merge in some way. It MUST NOT be
+ added to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Conference.InitialChannels</tp:dbus-ref> (to preserve
+ immutability).</p>
+
+ <tp:rationale>
+ <p>In GSM it is possible to merge additional calls into an ongoing
+ conference.</p>
+
+ <p>In XMPP this method could be implemented to merge a 1-1 Text
+ channel into a MUC Text channel by inviting the peer from the Text
+ channel into the MUC, or to merge a 1-1 Jingle call into a Muji
+ call by inviting the peer from the Jingle call into the Muji call.
+ (MUC and Muji channels are both implemented by XMPP MUCs, with
+ Handle_Type_Room.)</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Channel" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel with the same <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >ChannelType</tp:dbus-ref>
+ as this one, but with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref> = CONTACT.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The given channel isn't suitable for merging into this one: for
+ instance, it might have the wrong channel type or handle type.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ It will never be possible to merge channels into this particular
+ conference.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The given channel is theoretically suitable for merging into this
+ one, but that's not currently possible for some reason (for
+ instance, this SHOULD be raised if a limit on the number of
+ channels in a conference is exceeded).
+ <strong>[FIXME: PermissionDenied?]</strong>
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
diff --git a/qt4/spec/Channel_Interface_Messages.xml b/qt4/spec/Channel_Interface_Messages.xml
new file mode 100644
index 000000000..62e83846a
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Messages.xml
@@ -0,0 +1,1433 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Messages"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008–2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008–2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.Messages">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.Text"/>
+ <tp:added version="0.17.16">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface extends the <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy.Channel.Type'>Text</tp:dbus-ref>
+ interface to support more general messages, including:</p>
+
+ <ul>
+ <li>messages with attachments (like MIME multipart/mixed)</li>
+ <li>groups of alternatives (like MIME multipart/alternative)</li>
+ <li>delivery reports (which replace <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text.SendError</tp:dbus-ref>),
+ addding support for protocols where the message content is not echoed
+ back to the sender on failure and for receiving positive
+ acknowledgements, as well as ensuring that incoming delivery reports
+ are not lost if no client is handling the channel yet;</li>
+ <li>any extra types of message we need in future</li>
+ </ul>
+
+ <p>Incoming messages, outgoing messages, and delivery reports are all
+ represented as lists of <tp:type>Message_Part</tp:type> structures,
+ with a format reminiscent of e-mail. Messages are sent by calling
+ <tp:member-ref>SendMessage</tp:member-ref>; outgoing messages are
+ announced to other clients which may be interested in the channel by
+ the <tp:member-ref>MessageSent</tp:member-ref> signal. Incoming
+ messages and delivery reports are signalled by
+ <tp:member-ref>MessageReceived</tp:member-ref>, and are stored in the
+ the <tp:member-ref>PendingMessages</tp:member-ref> property until
+ acknowledged by calling <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text.AcknowledgePendingMessages</tp:dbus-ref>.
+ Only the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref>
+ for a channel should acknowledge messages; <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Observer</tp:dbus-ref>s
+ (such as loggers) and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Approver</tp:dbus-ref>s
+ for the channel may listen for incoming messages, and send messages of their own, but SHOULD NOT acknowledge messages.</p>
+
+ <tp:rationale>
+ <p>If observers were allowed to acknowledge messages, then messages
+ might have been acknowledged before the handler even got to see the
+ channel, and hence could not be shown to the user.</p>
+ </tp:rationale>
+
+ <p>If this interface is present, clients that support it SHOULD
+ listen for the <tp:member-ref>MessageSent</tp:member-ref> and
+ <tp:member-ref>MessageReceived</tp:member-ref> signals, and
+ ignore the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">Sent</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">SendError</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">Received</tp:dbus-ref>
+ signals on the Text interface (which are guaranteed to duplicate
+ signals from this interface).</p>
+
+ <p>Although this specification supports formatted (rich-text)
+ messages with unformatted alternatives, implementations SHOULD NOT
+ attempt to send formatted messages until the Telepathy specification
+ has also been extended to cover capability discovery for message
+ formatting.</p>
+
+ <tp:rationale>
+ We intend to expose all rich-text messages as XHTML-IM, but on some
+ protocols, formatting is an extremely limited subset of that format
+ (e.g. there are protocols where foreground/background colours, font
+ and size can be set, but only for entire messages).
+ Until we can tell UIs what controls to offer to the user, it's
+ unfriendly to offer the user controls that may have no effect.
+ </tp:rationale>
+ </tp:docstring>
+
+ <property name="SupportedContentTypes" type="as" access="read"
+ tp:name-for-bindings="Supported_Content_Types"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of MIME types supported by this channel, with more preferred
+ MIME types appearing earlier in the list. The list MAY include "*/*"
+ to indicate that attachments with arbitrary MIME types can be sent.
+ This list MUST NOT be empty, since all Messages implementations
+ MUST accept messages containing a single "text/plain" part.</p>
+
+ <p>Items in this list MUST be normalized to lower-case.</p>
+
+ <p>Some examples of how this property interacts with the
+ <tp:member-ref>MessagePartSupportFlags</tp:member-ref>:</p>
+
+ <dl>
+ <dt>A simple IM implementation: only plain text messages are
+ allowed</dt>
+ <dd>SupportedContentTypes = ['text/plain'],
+ MessagePartSupportFlags = 0</dd>
+
+ <dt>Formatted text with a plain text alternative is allowed (see the
+ HTML interface draft)</dt>
+ <dd>SupportedContentTypes = ['text/html', 'text/plain'],
+ MessagePartSupportFlags = 0</dd>
+
+ <dt>JPEG or PNG images may be sent, but without any attached
+ text</dt>
+ <dd>SupportedContentTypes = ['text/plain', 'image/jpeg',
+ 'image/png'], MessagePartSupportFlags = 0</dd>
+
+ <dt>Unformatted text to which an optional JPEG or PNG image may be
+ attached</dt>
+ <dd>SupportedContentTypes = ['text/plain', 'image/jpeg',
+ 'image/png'], MessagePartSupportFlags = One_Attachment</dd>
+
+ <dt>Formatted text to which arbitrarily many images may be
+ attached</dt>
+ <dd>SupportedContentTypes = ['text/html', 'text/plain', 'image/jpeg',
+ 'image/png', 'image/x-ms-bmp'], MessagePartSupportFlags =
+ One_Attachment | Multiple_Attachments</dd>
+
+ <dt>A full SIP implementation: arbitrary MIME messages are
+ allowed</dt>
+ <dd>SupportedContentTypes = ['*/*'], MessagePartSupportFlags =
+ One_Attachment | Multiple_Attachments</dd>
+ </dl>
+ </tp:docstring>
+ </property>
+
+ <property name="MessageTypes" type="au"
+ tp:type="Channel_Text_Message_Type[]" access="read"
+ tp:name-for-bindings="Message_Types"
+ tp:immutable="yes">
+ <tp:added version="0.21.5">
+ This supersedes <tp:dbus-ref namespace="ofdT.Channel.Type.Text"
+ >GetMessageTypes</tp:dbus-ref>; fall back to that method for
+ compatibility with older connection managers.
+ </tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of message types which may be sent on this channel.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="MessagePartSupportFlags" type="u"
+ tp:type="Message_Part_Support_Flags" access="read"
+ tp:name-for-bindings="Message_Part_Support_Flags"
+ tp:immutable="yes">
+ <tp:docstring>
+ Flags indicating the level of support for message parts on this
+ channel.
+ </tp:docstring>
+ </property>
+
+ <tp:flags name="Message_Part_Support_Flags"
+ value-prefix="Message_Part_Support_Flag" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Flags indicating the level of support for message parts on this
+ channel. They are designed such that setting more flags always
+ implies that the channel has more capabilities.</p>
+
+ <p>If no flags are set, this indicates that messages may contain
+ a single message part whose content-type is any of the types
+ from SupportedContentTypes, possibly with some alternatives.</p>
+
+ <p>There is no flag indicating support for alternatives. This is
+ because the SendMessage implementation can always accept messages
+ containing alternatives, even if the underlying protocol does not,
+ by deleting all alternatives except the first (most preferred)
+ that is supported.</p>
+
+ <tp:rationale>
+ Each of the flags so far implies the previous flag, so we could
+ have used a simple enumeration here; however, we've defined
+ the message-part support indicator as a flag set for future
+ expansion.
+ </tp:rationale>
+
+ <p>See <tp:member-ref>SupportedContentTypes</tp:member-ref> for some
+ examples.</p>
+ </tp:docstring>
+
+ <tp:flag suffix="One_Attachment" value="1">
+ <tp:docstring>
+ <tp:member-ref>SendMessage</tp:member-ref> will accept messages
+ containing a textual message body,
+ plus a single attachment of any type listed in the
+ SupportedContentTypes property. It does not make sense for this
+ flag to be set if Message_Part_Support_Flag_Data_Only is not also set
+ (because the connection manager can trivially provide an empty text
+ part if necessary).
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Multiple_Attachments" value="2">
+ <tp:docstring>
+ SendMessage will accept messages containing a textual message body,
+ plus an arbitrary number of attachments of any type listed in the
+ SupportedContentTypes property. It does not make sense for this
+ flag to be set if Message_Part_Support_Flag_One_Attachment is not
+ also set.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:mapping name="Message_Part" array-name="Message_Part_List"
+ array-depth="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Part of a message's content. In practice, this mapping never
+ appears in isolation: incoming messages are represented by a list of
+ <tp:type>Message_Part</tp:type> mappings in the
+ <tp:member-ref>MessageReceived</tp:member-ref> signal, and outgoing
+ messages are passed to <tp:member-ref>SendMessage</tp:member-ref> as
+ a list of these mappings.</p>
+
+ <p>The first part of the message contains "headers", which refer
+ to the entire message. The second and subsequent parts contain the
+ message's content, including plain text, formatted text and/or
+ attached files. Well-known keys for the header and body parts are
+ defined by the <tp:type>Message_Header_Key</tp:type> and
+ <tp:type>Message_Body_Key</tp:type> types, respectively. It is an
+ error for a connection manager to put keys referring to the message
+ as a whole in the second or subsequent Message_Part, or keys intended
+ for body parts in the first Message_Part; clients MUST recover from
+ this error by ignoring these mis-placed keys.</p>
+
+ <tp:rationale>
+ <p>Instead of representing messages as aa{sv} where the first
+ dictionary is special (a dictionary of headers), we could have
+ used a signature like (a{sv}aa{sv}) to separate out the headers
+ and the body parts.</p>
+
+ <p>However, this would make access to the messages more awkward.
+ In Python, the syntax for access to a header field would remain
+ <code>message[0]['message-type']</code>, but access to a body
+ field in the second body part would change from
+ <code>message[2]['content'] to message[1][1]['content']</code>. In
+ GLib, the message would change from being a
+ <code>GPtrArray(GHashTable)</code> to being a
+ <code>GValueArray(GHashTable, GPtrArray(GHashTable))</code> which
+ is rather inconvenient to dereference.</p>
+ </tp:rationale>
+
+ <p>In any group of parts with the same non-empty value for the
+ <tt>alternative</tt> key (which represent alternative versions of the
+ same content), more faithful versions of the intended message MUST
+ come before less faithful versions (note that this order is the
+ opposite of MIME <tt>multipart/alternative</tt> parts). Clients
+ SHOULD display the first alternative that they understand.</p>
+
+ <tp:rationale>
+ <p>Specifying the preference order means that if the underlying
+ protocol doesn't support alternatives, the CM can safely delete
+ everything apart from the first supported alternative when
+ sending messages.</p>
+
+ <p>The order is the reverse of MIME because MIME's rationale for
+ placing the "plainest" part first (legibility in pre-MIME UAs)
+ does not apply to us, and placing the most preferred part
+ first simplifies display (a client can iterate the message
+ in order, display the first alternative that it understands,
+ and skip displaying all subsequent parts with the same
+ "alternative" key).</p>
+ </tp:rationale>
+
+ <p>Clients SHOULD present all parts that are not redundant
+ alternatives in the order they appear in this array, possibly
+ excluding parts that are referenced by another displayed part.
+ It is implementation-specific how the parts are presented to the
+ user.</p>
+
+ <tp:rationale>
+ <p>This allows CMs to assume that all parts are actually shown to
+ the user, even if they are not explicitly referenced - we do
+ not yet recommend formatted text, and there is no way for
+ plain text to reference an attachment since it has no concept of
+ markup or references. This also forces clients to do something
+ sensible with messages that consist entirely of "attachments",
+ with no "body" at all.</p>
+
+ <p>For instance, when displaying the above example, a client that
+ understands the HTML part should display the JPEG image once,
+ between the two lines "Here is a photo of my cat:" and
+ "Isn't it cute?"; it may additionally present the image in some
+ way for a second time, after "Isn't it cute?", or may choose
+ not to.</p>
+
+ <p>A client that does not understand HTML, displaying the same
+ message, should display the plain-text part, followed by the JPEG
+ image.</p>
+ </tp:rationale>
+
+ <p>Connection managers, clients and extensions to this specification
+ SHOULD NOT include <tp:type>Handle</tp:type>s as values in a
+ Message_Part, except for <code>message-sender</code> in the
+ header.</p>
+
+ <tp:rationale>
+ <p>Reference-counting handles in clients becomes problematic if
+ the channel proxy cannot know whether particular map values
+ are handles or not.</p>
+ </tp:rationale>
+
+ <h4>Example messages</h4>
+
+ <p>A rich-text message, with an embedded image, might be represented
+ as:</p>
+
+ <pre>
+[
+ {
+ 'message-token': '9de9546a-3400-4419-a505-3ea270cb834c',
+ 'message-sender': 42,
+ 'message-sent': 1210067943,
+ 'message-received': 1210067947,
+ 'message-type': 0, # = Channel_Text_Message_Type_Normal
+ 'pending-message-id': 437,
+ },
+ { 'alternative': 'main',
+ 'content-type': 'text/html',
+ 'content': 'Here is a photo of my cat:&lt;br /&gt;' +
+ '&lt;img src="cid:catphoto" alt="lol!" /&gt;' +
+ '&lt;br /&gt;Isn't it cute?',
+ },
+ { 'alternative': 'main',
+ 'content-type': 'text/plain',
+ 'content': 'Here is a photo of my cat:\n[IMG: lol!]\nIsn't it cute?',
+ },
+ { 'identifier': 'catphoto',
+ 'content-type': 'image/jpeg',
+ 'size': 101000,
+ 'needs-retrieval': True,
+ },
+]</pre>
+
+ <p>telepathy-ring, Nokia's GSM connection manager, represents vCards
+ sent via SMS as:</p>
+
+ <pre>
+[
+ {
+ 'message-token': '9de9546a-3400-4419-a505-3ea270cb834c',
+ 'message-sender': 42,
+ 'message-sent': 1210067943,
+ 'message-received': 1210067947,
+ 'message-type': 0, # = Channel_Text_Message_Type_Normal
+ 'pending-message-id': 437,
+ },
+ { 'content-type': 'text/x-vcard',
+ 'content': [ 0x66, 0x69, 0x71, ...], # vCard data as an array of bytes
+ },
+]</pre>
+
+ <h3>Delivery reports</h3>
+
+ <div>
+ <p>Delivery reports are also represented as messages with the
+ <tt>message-type</tt> header mapping to
+ <tp:type>Channel_Text_Message_Type</tp:type> Delivery_Report.
+ Delivery reports SHOULD contain the <tt>message-sender</tt> header,
+ mapping to the intended recipient of the original message, if
+ possible; other headers specific to delivery reports are defined by
+ the <tp:type>Delivery_Report_Header_Key</tp:type> type. The second
+ and subsequent parts, if present, are a human-readable report from
+ the IM service.</p>
+
+ <p>For backwards- and forwards-compatibility, whenever a delivery
+ error report is signalled—that is, with <tt>delivery-status</tt>
+ mapping to <tp:type>Delivery_Status</tp:type> Temporarily_Failed or
+ Permanently_Failed—<tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">SendError</tp:dbus-ref>
+ SHOULD also be emitted; whenever <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">SendError</tp:dbus-ref>
+ is emitted, a delivery report MUST also be signalled.
+ Delivery report messages on this interface MUST be represented in
+ emissions of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">Received</tp:dbus-ref>
+ as messages with the Non_Text_Content
+ <tp:type>Channel_Text_Message_Flags</tp:type>; clients which
+ understand this interface SHOULD ignore the SendError signal in
+ favour of listening for delivery reports, as mentioned in the
+ introduction.</p>
+
+ <p>The result of attempting to send delivery reports using
+ <tp:member-ref>SendMessage</tp:member-ref> is currently
+ undefined.</p>
+
+ <h4>Example delivery reports</h4>
+
+ <dl>
+ <dt>A minimal delivery report indicating permanent failure of the
+ sent message whose token was
+ <code>b9a991bd-8845-4d7f-a704-215186f43bb4</code> for an unknown
+ reason</dt>
+ <dd><pre>
+[{
+# header
+'message-sender': 123,
+'message-type': Channel_Text_Message_Type_Delivery_Report,
+'delivery-status': Delivery_Status_Permanently_Failed,
+'delivery-token': 'b9a991bd-8845-4d7f-a704-215186f43bb4',
+}
+# no body
+]</pre></dd>
+
+ <dt>A delivery report where the failed message is echoed back to the
+ sender rather than being referenced by ID, and the failure reason
+ is that this protocol cannot send messages to offline contacts
+ such as the contact with handle 123</dt>
+ <dd><pre>
+[{ # header
+'message-sender': 123,
+'message-type': Channel_Text_Message_Type_Delivery_Report,
+'delivery-status': Delivery_Status_Temporarily_Failed,
+'delivery-error': Channel_Text_Send_Error_Offline,
+'delivery-echo':
+ [{ # header of original message
+ 'message-sender': 1,
+ 'message-sent': 1210067943,
+ },
+ { # body of original message
+ 'content-type': 'text/plain',
+ 'content': 'Hello, world!',
+ }]
+ ],
+
+# no body
+]</pre></dd>
+
+ <dt>A maximally complex delivery report: the server reports a
+ bilingual human-readable failure message because the user sent
+ a message "Hello, world!" with token
+ <code>b9a991bd-8845-4d7f-a704-215186f43bb4</code> to a contact
+ with handle 123, but that handle represents a contact who does not
+ actually exist</dt>
+ <dd><pre>
+[{ # header
+'message-sender': 123,
+'message-type': Channel_Text_Message_Type_Delivery_Report,
+'delivery-status': Delivery_Status_Permanently_Failed,
+'delivery-error': Channel_Text_Send_Error_Invalid_Contact,
+'delivery-token': 'b9a991bd-8845-4d7f-a704-215186f43bb4',
+'delivery-echo':
+ [{ # header of original message
+ 'message-sender': 1,
+ 'message-sent': 1210067943,
+ },
+ { # body of original message
+ 'content-type': 'text/plain',
+ 'content': 'Hello, world!',
+ }]
+ ],
+},
+{ # message from server (alternative in English)
+'alternative': '404',
+'content-type': 'text/plain',
+'lang': 'en',
+'content': 'I have no contact with that name',
+},
+{ # message from server (alternative in German)
+'alternative': '404'.
+'content-type': 'text/plain',
+'lang': 'de',
+'content', 'Ich habe keinen Kontakt mit diesem Namen',
+}
+]</pre></dd>
+
+ <dt>A minimal delivery report indicating successful delivery
+ of the sent message whose token was
+ <code>b9a991bd-8845-4d7f-a704-215186f43bb4</code></dt>
+ <dd><pre>
+[{
+# header
+'message-sender': 123,
+'message-type': Channel_Text_Message_Type_Delivery_Report,
+'delivery-status': Delivery_Status_Delivered,
+'delivery-token': 'b9a991bd-8845-4d7f-a704-215186f43bb4',
+}
+# no body
+]</pre></dd>
+
+ </dl>
+
+ </div>
+ </tp:docstring>
+
+ <tp:member name="Key" type="s">
+ <tp:docstring>
+ A key, which SHOULD be one of the well-known keys specified by
+ <tp:type>Message_Header_Key</tp:type>,
+ <tp:type>Message_Body_Key</tp:type> or
+ <tp:type>Delivery_Report_Header_Key</tp:type> if possible.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Value" type="v">
+ <tp:docstring>
+ The value corresponding to the given key, which SHOULD be one of the
+ specified types for well-known keys.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:simple-type type="s" name="Message_Header_Key">
+ <tp:added version="0.19.8"/>
+ <tp:changed version="0.21.5">
+ Removed <tt>protocol-token</tt>—which had never been implemented—and
+ respecified <tt>message-token</tt> not to have unimplementable
+ uniqueness guarantees.
+ </tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Well-known keys for the first <tp:type>Message_Part</tp:type> of a
+ message, which contains metadata about the message as a whole, along
+ with the corresponding value types. Some keys make sense for both
+ incoming and outgoing messages, while others are only meaningful for
+ one or the other.</p>
+
+ <dl>
+ <dt>message-token (s -
+ <tp:type>Protocol_Message_Token</tp:type>)
+ </dt>
+ <dd>
+ <p>An opaque identifier for the message, as used by the
+ underlying protocol. For outgoing messages, this SHOULD be
+ globally unique; for incoming messages, this is <em>not</em>
+ guaranteed to uniquely identify a message, <em>even within the
+ scope of a single channel or contact</em>; the only guarantee
+ made is that two messages with different <tt>message-token</tt>
+ headers are different messages.</p>
+
+ <p>Clients wishing to determine whether a new message with the
+ <tt>scrollback</tt> header matches a previously-logged message
+ with the same <tt>message-token</tt> SHOULD compare the
+ message's sender, contents, <tt>message-sent</tt> or
+ <tt>message-received</tt> timestamp, etc. Note that, in XMPP,
+ the server only supplies a timestamp for scrollback messages,
+ not for messages received while you are in a room; thus,
+ non-scrollback messages will lack a <tt>message-sent</tt>
+ timestamp.</p>
+
+ <tp:rationale>
+ <p>In practice, most protocols do not provide globally-unique
+ identifiers for messages. Connection managers, being
+ stateless, do not have the necessary information — namely, IM
+ logs — to generate reliable unique tokens for messages.</p>
+
+ <p>For instance, some XMPP clients (including Gabble) stamp
+ messages they send with unique identifiers, but others number
+ outgoing messages in a conversation from 1 upwards.</p>
+ </tp:rationale>
+ </dd>
+
+ <dt>message-sent (x - <tp:type>Unix_Timestamp64</tp:type>)</dt>
+ <dd>The time the message was sent (if unavailable, the time
+ it arrived at a central server MAY be used). Omitted if no
+ reasonable approximation is available; SHOULD always be present
+ on outgoing messages.</dd>
+
+ <dt>message-received (x - <tp:type>Unix_Timestamp64</tp:type>)</dt>
+ <dd>The time the message was received locally. SHOULD always
+ be present.</dd>
+
+ <dt>message-sender (u - <tp:type>Contact_Handle</tp:type>)</dt>
+ <dd>The contact who sent the message. If 0 or omitted, the contact
+ who sent the message could not be determined.</dd>
+
+ <dt>message-sender-id (s)</dt>
+ <dd>The identifier of the contact who sent the message,
+ i.e. the result of calling <tp:dbus-ref
+ namespace="ofdT.Connection">InspectHandles</tp:dbus-ref>
+ on <code>message-sender</code>. If omitted, clients MUST
+ fall back to looking at <code>message-sender</code>.</dd>
+
+ <dt>sender-nickname (s)</dt>
+ <dd>The nickname chosen by the sender of the message, which can be
+ different for each message in a conversation.</dd>
+
+ <dt>message-type (u - <tp:type>Channel_Text_Message_Type</tp:type>)
+ </dt>
+ <dd>The type of message; if omitted,
+ Channel_Text_Message_Type_Normal MUST be assumed. MAY
+ be omitted for normal chat messages.</dd>
+
+ <dt>supersedes (s – <tp:type>Protocol_Message_Token</tp:type>)</dt>
+ <dd>If present, this message supersedes a previous message,
+ identified by its <tt>protocol-token</tt> or
+ <tt>message-token</tt> header. The user interface MAY, for
+ example, choose to replace the superseded message with this
+ message, or grey out the superseded message.
+
+ <tp:rationale>Skype, for example, allows the user to amend
+ messages they have already sent (to correct typos,
+ etc.).</tp:rationale>
+ </dd>
+
+ <dt>pending-message-id (u - <tp:type>Message_ID</tp:type>)</dt>
+ <dd>The incoming message ID. This MUST NOT be present on outgoing
+ messages. Clients SHOULD NOT store this key - it is only valid
+ for as long as the message remains unacknowledged.</dd>
+
+ <dt>interface (s - <tp:type>DBus_Interface</tp:type>)</dt>
+ <dd>This message is specific to the given interface, which is
+ neither Text nor Messages. It SHOULD be ignored if that
+ interface is not supported. (Note that an 'interface' key
+ can also appear on the second and subsequent parts, where
+ it indicates that that part (only) should be ignored if
+ unsupported.)</dd>
+
+ <dt>scrollback (b)</dt>
+ <dd>If present and true, the incoming message was part of a
+ replay of message history (this matches the Scrollback flag in
+ <tp:type>Channel_Text_Message_Flags</tp:type>). This flag
+ does not make sense on outgoing messages and SHOULD NOT
+ appear there.</dd>
+
+ <dt>rescued (b)</dt>
+ <dd>If present and true, the incoming message has been seen in
+ a previous channel during the lifetime of the Connection,
+ but had not been acknowledged when that channel closed, causing
+ an identical channel (in which the message now appears) to open.
+ This matches the Rescued flag in
+ <tp:type>Channel_Text_Message_Flags</tp:type>; it
+ does not make sense on outgoing messages, and SHOULD NOT
+ appear there.</dd>
+ </dl>
+
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type type="s" name="Message_Body_Key">
+ <tp:added version="0.19.8"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Well-known keys for the second and subsequent
+ <tp:type>Message_Part</tp:type>s of a message, which contain the
+ message content, along with the corresponding value types.</p>
+
+ <dl>
+ <dt>identifier (s —
+ <tp:type>Protocol_Content_Identifier</tp:type>)</dt>
+ <dd>An opaque identifier for this part.
+ Parts of a message MAY reference other parts by treating
+ this identifier as if it were a MIME Content-ID and using
+ the cid: URI scheme.</dd>
+
+ <dt>alternative (s)</dt>
+ <dd>
+ <p>If present, this part of the message is an alternative for
+ all other parts with the same value for "alternative".
+ Clients SHOULD only display one of them (this is expected to
+ be used for XHTML messages in a future version of this
+ specification).</p>
+
+ <p>If omitted, this part is not an alternative for any other
+ part.</p>
+
+ <p>Parts of a message MAY reference the group of alternatives
+ as a whole (i.e. a reference to whichever of them is chosen)
+ by treating this identifier as if it were the MIME Content-ID
+ of a multipart/alternative part, and using the cid: URI
+ scheme.</p>
+ </dd>
+
+ <dt>content-type (s)</dt>
+ <dd>
+ <p>The MIME type of this part. See the documentation
+ for <tp:member-ref>MessageReceived</tp:member-ref> and
+ <tp:member-ref>MessageSent</tp:member-ref> for notes on the
+ special status of "text/plain" parts.</p>
+
+ <p>Connection managers MUST NOT signal parts without a
+ 'content-type' key; if a protocol provides no way to determine
+ the MIME type, the connection manager is responsible for
+ guessing it, but MAY fall back to "text/plain" for text and
+ "application/octet-stream" for non-text.</p>
+
+ <p>Clients MUST ignore parts without a 'content-type' key, which
+ are reserved for future expansion.</p>
+
+ <p>When sending messages, clients SHOULD normalize the
+ content-type to lower case, but connection managers SHOULD NOT
+ rely on this. When signalling sent or received messages,
+ connection managers MUST normalize the content-type to lower
+ case.</p>
+ </dd>
+
+ <dt>lang (s)</dt>
+ <dd>The natural language of this part, identified by a
+ RFC 3066 language tag.
+
+ <tp:rationale>
+ XMPP allows alternative-selection by language as well as
+ by content-type.
+ </tp:rationale>
+ </dd>
+
+ <dt>size (u)</dt>
+ <dd>The size in bytes (if needs-retrieval is true, this MAY be an
+ estimated or approximate size). SHOULD be omitted if 'content'
+ is provided.
+
+ <tp:rationale>
+ There's no point in providing the size if you're already
+ providing all the content.
+ </tp:rationale>
+ </dd>
+
+ <dt>thumbnail (b)</dt>
+ <dd>
+ <p>This part is a thumbnail. To represent an image together with
+ its thumbnail in a single message, there should be one part for
+ the full image followed by a part for the thumbnail (following
+ the “more complete versions first” requirement), with the same
+ 'alternative' value. For example:</p>
+
+ <pre>
+[ ... ,
+ { 'alternative': 'catphoto',
+ 'content-type': 'image/jpeg',
+ 'size': 150000,
+ 'content': [0xFF, 0xD8, ... 0xFF 0xD9],
+ },
+ { 'alternative': 'catphoto',
+ 'content-type': 'image/jpeg'
+ 'size': 1024,
+ 'thumbnail': True,
+ 'content': [0xFF, 0xD8, ... 0xFF 0xD9],
+ },
+ ...
+]</pre>
+ </dd>
+
+ <dt>needs-retrieval (b)</dt>
+ <dd>If false or omitted, the connection
+ manager already holds this part in memory. If present and true,
+ this part must be retrieved on demand (like MIME's
+ <tt>message/external-body</tt>) by a mechanism to be defined later.
+
+ <tp:rationale>The mechanism was meant to be
+ <tp:member-ref>GetPendingMessageContent</tp:member-ref>, but
+ that didn't work out. It's worth leaving the header in in
+ preparation for a future mechanism.
+ </tp:rationale>
+ </dd>
+
+ <dt>truncated (b)</dt>
+ <dd>The content available via the 'content' key has been truncated
+ by the server or connection manager (equivalent to
+ Channel_Text_Message_Flag_Truncated in the Text interface).
+ </dd>
+
+ <dt>content (s or ay)</dt>
+ <dd>The part's content, if it is available and
+ sufficiently small to include here (implies that
+ 'needs-retrieval' is false or omitted). Otherwise, omitted.
+ If the part is human-readable text or HTML, the value for this
+ key MUST be a UTF-8 string (D-Bus signature 's').
+ If the part is not text, the value MUST be a byte-array
+ (D-Bus signature 'ay'). If the part is a text-based format
+ that is not the main body of the message (e.g. an iCalendar
+ or an attached XML document), the value SHOULD be a UTF-8 string,
+ transcoding from another charset to UTF-8 if necessary, but
+ MAY be a byte-array (of unspecified character set) if
+ transcoding fails or the source charset is not known.</dd>
+
+ <!-- FIXME: "sufficiently small to include" is not currently
+ defined; we should add some API so clients can tell the
+ CM how large a message it should emit in the signal.-->
+
+ <dt>interface (s - <tp:type>DBus_Interface</tp:type>)</dt>
+ <dd>This part is specific to the given interface, which is
+ neither Text nor Messages. It SHOULD be ignored if that
+ interface is not supported. (Note that an 'interface' key
+ can also appear on the first part, where it indicates that the
+ entire message should be ignored if unsupported.)</dd>
+ </dl>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type type="s" name="Delivery_Report_Header_Key">
+ <tp:added version="0.19.8"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Well-known keys for the first <tp:type>Message_Part</tp:type> of a
+ delivery report, along with the corresponding value types. Some of
+ these are special-cases of headers defined by
+ <tp:type>Message_Header_Key</tp:type>.</p>
+
+ <dl>
+ <dt>message-sender (u - <tp:type>Contact_Handle</tp:type>, as
+ defined by <tp:type>Message_Header_Key</tp:type>)</dt>
+ <dd>MUST be the intended recipient of the original message, if
+ available (zero or omitted if the intended recipient is
+ unavailable or is not a contact, e.g. a chatroom), even if the
+ delivery report actually came from an intermediate server.</dd>
+
+ <dt>message-type (u - <tp:type>Channel_Text_Message_Type</tp:type>,
+ as defined by <tp:type>Message_Header_Key</tp:type>)</dt>
+ <dd>MUST be Channel_Text_Message_Type_Delivery_Report.</dd>
+
+ <dt>delivery-status (u - <tp:type>Delivery_Status</tp:type>)</dt>
+ <dd>The status of the message. All delivery reports MUST contain
+ this key in the first Message_Part.</dd>
+
+ <dt>delivery-token (s - <tp:type>Protocol_Message_Token</tp:type>)</dt>
+
+ <dd>
+ <p>An identifier for the message to which this delivery report
+ refers. MUST NOT be an empty string. Omitted if not
+ available.</p>
+
+ <p>Clients may match this against the token produced by the
+ SendMessage method and MessageSent signal. A status report
+ with no token could match any sent message, and a sent
+ message with an empty token could match any status report.
+ If multiple sent messages match, clients SHOULD use some
+ reasonable heuristic.</p>
+
+ <tp:rationale>
+ In an ideal world, we could unambiguously match reports
+ against messages; however, deployed protocols are not ideal,
+ and not all reports and messages can be matched.
+ </tp:rationale>
+ </dd>
+
+ <dt>delivery-error (u -
+ <tp:type>Channel_Text_Send_Error</tp:type>)</dt>
+ <dd>
+ The reason for the failure. MUST be omitted if this was a
+ successful delivery; SHOULD be omitted if it would be
+ Channel_Text_Send_Error_Unknown.
+ </dd>
+
+ <dt>delivery-dbus-error (s -
+ <tp:type>DBus_Error_Name</tp:type>)</dt>
+ <dd>
+ The reason for the failure, specified as a (possibly
+ implementation-specific) D-Bus error. MUST be omitted if this was
+ a successful delivery. If set, the 'delivery-error' key SHOULD be
+ set to the closest available value.
+ </dd>
+
+ <dt>delivery-error-message (s)</dt>
+ <dd>
+ Debugging information on why the message could not be delivered.
+ MUST be omitted if this was a successful delivery; MAY always be
+ omitted.
+ </dd>
+
+ <dt>delivery-echo (aa{sv} - <tp:type>Message_Part[]</tp:type>)</dt>
+ <dd>
+ <p>The message content, as defined by the Messages interface.
+ Omitted if no content is available. Content MAY have been
+ truncated, message parts MAY have been removed, and message
+ parts MAY have had their content removed (i.e. the message part
+ metadata is present, but the 'content' key is not).</p>
+
+ <tp:rationale>
+ Some protocols, like XMPP, echo the failing message back to
+ the sender. This is sometimes the only way to match it
+ against the sent message, so we include it here.
+ </tp:rationale>
+ </dd>
+
+ </dl>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type type="u" name="Message_Part_Index"
+ array-name="Message_Part_Index_List">
+ <tp:deprecated version="0.21.5">
+ This type is only used by
+ <tp:member-ref>GetPendingMessageContent</tp:member-ref>, which is
+ unimplemented and deprecated.
+ </tp:deprecated>
+ <tp:added version="0.17.17"/>
+ <tp:docstring>
+ The index of a message part within a message.
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:mapping name="Message_Part_Content_Map">
+ <tp:added version="0.17.17"/>
+ <tp:deprecated version="0.21.5">
+ This structure is only used by
+ <tp:member-ref>GetPendingMessageContent</tp:member-ref>, which is
+ unimplemented and deprecated.
+ </tp:deprecated>
+ <tp:docstring>
+ A mapping from message part indexes to their content, as returned by
+ <tp:member-ref>GetPendingMessageContent</tp:member-ref>.
+ </tp:docstring>
+
+ <tp:member type="u" tp:type="Message_Part_Index" name="Part">
+ <tp:docstring>
+ Indexes into the array of <tp:type>Message_Part</tp:type>s that
+ represents a message. The "headers" part (which is not a valid
+ argument to GetPendingMessageContent) is considered to be part 0,
+ so the valid part numbers start at 1 (for the second message part).
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="v" name="Content">
+ <tp:docstring>
+ The message part's content. The variant MUST contain either type
+ 's' or 'ay' (UTF-8 text string, or byte array), following the
+ same rules as for the value of the 'content' key in
+ the <tp:type>Message_Part</tp:type> mappings.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:simple-type type="s" name="Protocol_Message_Token"
+ array-name="Protocol_Message_Token_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An opaque token used to identify messages in the underlying.
+ protocol. As a special case, the empty string indicates that there
+ is no particular identification for a message.</p>
+
+ <p>CM implementations SHOULD use an identifier expected to be unique,
+ such as a UUID, for outgoing messages (if possible).</p>
+
+ <p>Some protocols can only track a limited number of messages
+ in a small message-ID space (SMS messages are identified by a single
+ byte), and some implementations send non-unique identifiers (some
+ XMPP clients use very simple message IDs, such as an incrementing
+ integer that resets to 1 at the beginning of each connection). As a
+ result, clients MUST NOT assume that protocol tokens will not be
+ re-used.</p>
+
+ <p>In particular, clients SHOULD use a heuristic to assign delivery
+ reports to messages, such as matching on message content or
+ timestamp (if available), or assuming that the delivery report
+ refers to the most recent message with that ID.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Protocol_Content_Identifier" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A protocol-specific identifier for a blob of content, as used for
+ the <tt>identifier</tt> key in a <tp:type>Message_Part</tp:type>. The
+ same identifier MAY be re-used if the same content, byte-for-byte,
+ appears as a part of several messages.</p>
+
+ <tp:rationale>
+ <p>On XMPP, these identifiers might be Content-IDs for custom
+ smileys implemented using <a
+ href="http://xmpp.org/extensions/xep-0231.html">XEP-0232 Bits of
+ Binary</a>; the same smiley might well appear in multiple
+ messages.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <method name="SendMessage" tp:name-for-bindings="Send_Message">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Submit a message to the server for sending.
+ If this method returns successfully, the message has been submitted
+ to the server and the <tp:member-ref>MessageSent</tp:member-ref>
+ signal is emitted. A corresponding
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">Sent</tp:dbus-ref>
+ signal on the Text interface MUST also be emitted.</p>
+
+ <p>This method MUST return before the MessageSent signal is
+ emitted.</p>
+
+ <tp:rationale>
+ <p>This means that the process sending the message is the first
+ to see the <tp:type>Protocol_Message_Token</tp:type>, and can
+ relate the message to the corresponding
+ <tp:member-ref>MessageSent</tp:member-ref> signal by comparing
+ message tokens (if supported by the protocol).</p>
+ </tp:rationale>
+
+ <p>If this method fails, message submission to the server has failed
+ and no signal on this interface (or the Text interface) is
+ emitted.</p>
+
+ <p>If this method succeeds, message submission to the server has
+ succeeded, but the message has not necessarily reached its intended
+ recipient. If a delivery failure is detected later, this is
+ signalled by receiving a message whose <code>message-type</code>
+ header maps to
+ <tp:type>Channel_Text_Message_Type</tp:type>_Delivery_Report.
+ Similarly, if delivery is detected to have been successful
+ (which is not possible in all protocols), a successful delivery
+ report will be signalled.</p>
+ </tp:docstring>
+
+ <arg direction="in" type="aa{sv}" tp:type="Message_Part[]"
+ name="Message">
+ <tp:docstring>
+ The message content, including any attachments or alternatives.
+ This MUST NOT include the following headers, or any others that
+ do not make sense for a client to specify:
+ <code>message-sender</code>, <code>message-sender-id</code>,
+ <code>message-sent</code>, <code>message-received</code>,
+ <code>pending-message-id</code>.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Flags" type="u"
+ tp:type="Message_Sending_Flags">
+ <tp:docstring>
+ Flags affecting how the message is sent. The channel MAY ignore some
+ or all flags, depending on
+ <tp:member-ref>DeliveryReportingSupport</tp:member-ref>; the flags
+ that were handled by the CM are provided in
+ <tp:member-ref>MessageSent</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="s" tp:type="Protocol_Message_Token"
+ name="Token">
+ <tp:docstring>
+ An opaque token used to match any incoming delivery or failure
+ reports against this message, or an empty string if the message
+ is not readily identifiable.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The requested message is malformed and cannot be sent.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:flags name="Message_Sending_Flags" value-prefix="Message_Sending_Flag"
+ type="u">
+ <tp:docstring>
+ Flags altering the way a message is sent. The "most usual" action
+ should always be to have these flags unset. Some indication of which
+ flags are supported is provided by the
+ <tp:member-ref>DeliveryReportingSupport</tp:member-ref> property.
+ </tp:docstring>
+
+ <tp:flag suffix="Report_Delivery" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Provide a successful delivery report if possible, even if this is
+ not the default for this protocol. Ignored if delivery reports are
+ not possible on this protocol.</p>
+
+ <tp:rationale>
+ <p>In some protocols, like XMPP, it is not conventional to request
+ or send positive delivery notifications.</p>
+ </tp:rationale>
+
+ <p>Delivery failure reports SHOULD always be sent, but if this flag
+ is present, the connection manager MAY also try harder to obtain
+ failed delivery reports or allow them to be matched to outgoing
+ messages.</p>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Report_Read" value="2">
+ <tp:added version="0.19.9"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Provide a delivery report when the message is read by the
+ recipient, even if this is not the default for this protocol.
+ Ignored if read reports are not possible on this protocol.</p>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Report_Deleted" value="4">
+ <tp:added version="0.19.9"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Provide a delivery report when the message is deleted by the
+ recipient, even if this is not the default for this protocol.
+ Ignored if such reports are not possible on this protocol.</p>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <signal name="MessageSent" tp:name-for-bindings="Message_Sent">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Signals that a message has been submitted for sending. This
+ MUST be emitted exactly once per emission of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">Sent</tp:dbus-ref>
+ signal on the Text interface, for backwards-compatibility; clients
+ SHOULD ignore the latter if this interface is present, as mentioned
+ in the introduction.</p>
+
+ <p>This SHOULD be emitted as soon as the CM determines it's
+ theoretically possible to send the message (e.g. the parameters are
+ supported and correct).</p>
+
+ <tp:rationale>
+ <p>This signal allows a process that is not the caller of
+ SendMessage to log sent messages.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg type="aa{sv}" tp:type="Message_Part[]" name="Content">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The message content (see <tp:type>Message_Part</tp:type> for full
+ details). If the message that was passed to
+ <tp:member-ref>SendMessage</tp:member-ref> has a formatted text
+ part that the connection manager recognises, but no
+ <tt>text/plain</tt> alternative, the CM MUST use the formatted text
+ part to generate a <tt>text/plain</tt> alternative which is also
+ included in this signal argument.</p>
+
+ <p>The connection manager SHOULD include the
+ <code>message-sender</code>, <code>message-sender-id</code> and
+ <code>message-sent</code> headers in the representation of the
+ message that is signalled here. If the channel has
+ channel-specific handles, the <code>message-sender</code> and
+ <code>message-sender-id</code> SHOULD reflect the sender that
+ other contacts will see.</p>
+
+ <p>If the connection manager can predict that the message will be
+ altered during transmission, this argument SHOULD reflect what
+ other contacts will receive, rather than being a copy of the
+ argument to SendMessage (if the message is truncated,
+ formatting or alternatives are dropped, etc., then the edited
+ version SHOULD appear in this signal).</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Flags" type="u" tp:type="Message_Sending_Flags">
+ <tp:docstring>
+ <p>Flags affecting how the message was sent. The flags might be a
+ subset of those passed to SendMessage if the caller requested
+ unsupported flags.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Message_Token" type="s" tp:type="Protocol_Message_Token">
+ <tp:docstring>
+ An opaque token used to match any incoming delivery or failure
+ reports against this message, or an empty string if the message
+ is not readily identifiable.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="PendingMessages" type="aaa{sv}" access="read"
+ tp:type="Message_Part[][]" tp:name-for-bindings="Pending_Messages">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of incoming messages that have neither been acknowledged nor
+ rejected. This list is a more detailed version of the one returned
+ by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text.ListPendingMessages</tp:dbus-ref>,
+ and contains the same messages, uniquely identified by the same
+ pending message IDs. Its items can be removed using
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text.AcknowledgePendingMessages</tp:dbus-ref>.</p>
+
+ <p>Change notification is via
+ <tp:member-ref>MessageReceived</tp:member-ref> and
+ <tp:member-ref>PendingMessagesRemoved</tp:member-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="PendingMessagesRemoved"
+ tp:name-for-bindings="Pending_Messages_Removed">
+ <tp:docstring>
+ The messages with the given IDs have been removed from the
+ <tp:member-ref>PendingMessages</tp:member-ref> list. Clients SHOULD NOT
+ attempt to acknowledge those messages.
+
+ <tp:rationale>
+ This completes change notification for the PendingMessages property
+ (previously, there was change notification when pending messages
+ were added, but not when they were removed).
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Message_IDs" type="au" tp:type="Message_ID[]">
+ <tp:docstring>
+ The messages that have been removed from the pending message list.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="GetPendingMessageContent"
+ tp:name-for-bindings="Get_Pending_Message_Content">
+ <tp:deprecated version='0.21.5'
+ xmlns="http://www.w3.org/1999/xhtml">
+ This method has never been implemented, and in any case would have been
+ impossible to use correctly when multiple clients (such as a logger and
+ the handler) are interested in a text channel. See <a
+ href='https://bugs.freedesktop.org/show_bug.cgi?id=26417'>freedesktop.org
+ bug #26417</a> for more details.
+ </tp:deprecated>
+ <tp:docstring>
+ Retrieve the content of one or more parts of a pending message.
+ Note that this function may take a considerable amount of time
+ to return if the part's 'needs-retrieval' flag is true; consider
+ extending the default D-Bus method call timeout. Additional API is
+ likely to be added in future, to stream large message parts.
+ </tp:docstring>
+
+ <arg name="Message_ID" type="u" tp:type="Message_ID" direction="in">
+ <tp:docstring>
+ The ID of a pending message
+ </tp:docstring>
+ </arg>
+
+ <arg name="Parts" type="au" direction="in"
+ tp:type="Message_Part_Index[]">
+ <tp:docstring>
+ The desired entries in the array of message parts, identified by
+ their position. The "headers" part (which is not a valid argument
+ to this method) is considered to be part 0, so the valid part
+ numbers start at 1 (for the second Message_Part).
+ </tp:docstring>
+ </arg>
+
+ <arg name="Content" type="a{uv}" direction="out"
+ tp:type="Message_Part_Content_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The content of the requested parts. The keys in this mapping
+ are positions in the array of message parts; the values are
+ either of type 's' or 'ay' (UTF-8 text string, or byte array),
+ following the same rules as for the value of the 'content' key in
+ the <tp:type>Message_Part</tp:type> mappings.</p>
+
+ <p>If the one of the requested part numbers was greater than zero
+ but referred to a part that had no content (i.e. it had no
+ 'content-type' key or no 'content' key), it is simply omitted from
+ this mapping; this is not considered to be an error condition.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Either there is no pending message with the given message ID,
+ or one of the part numbers given was 0 or too large.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="MessageReceived" tp:name-for-bindings="Message_Received">
+ <tp:docstring>
+ Signals that a message has been received and added to the pending
+ messages queue. This MUST be emitted exactly once per emission of the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.Text">Received</tp:dbus-ref>
+ signal on the Text interface, for backwards-compatibility; clients
+ SHOULD ignore the latter in favour of this signal if this interface is
+ present, as mentioned in the introduction.
+ </tp:docstring>
+
+ <arg type="aa{sv}" tp:type="Message_Part[]" name="Message">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The message content, including any attachments or alternatives. If
+ the incoming message contains formatted text without a plain text
+ alternative, the connection manager MUST generate a
+ <tt>text/plain</tt> alternative from the formatted text, and
+ include it in this message (both here, and in the
+ <tp:member-ref>PendingMessages</tp:member-ref> property).</p>
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Delivery_Status" value-prefix="Delivery_Status"
+ plural="Delivery_Statuses" type="u">
+ <tp:docstring>
+ <p>The status of a message as indicated by a delivery report.</p>
+
+ <p>If this enum is extended in future specifications, this should
+ only be to add new, non-overlapping conditions (i.e. all failures
+ should still be signalled as either Temporarily_Failed
+ or Permanently_Failed). If additional detail is required (e.g.
+ distinguishing between the various types of permanent failure) this
+ will be done using additional
+ <tp:type>Delivery_Report_Header_Key</tp:type>s.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ The message's disposition is unknown.
+ Clients SHOULD consider all messages to have status
+ Delivery_Status_Unknown unless otherwise specified; connection
+ managers SHOULD NOT signal this delivery status explicitly.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Delivered" value="1">
+ <tp:docstring>
+ The message has been delivered to the intended recipient.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Temporarily_Failed" value="2">
+ <tp:docstring>
+ Delivery of the message has failed. Clients SHOULD notify the user,
+ but MAY automatically try sending another copy of the message.
+
+ <tp:rationale>
+ Similar to errors with type="wait" in XMPP; analogous to
+ 4xx errors in SMTP.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Permanently_Failed" value="3">
+ <tp:docstring>
+ Delivery of the message has failed. Clients SHOULD NOT try again
+ unless by specific user action. If the user does not modify the
+ message or alter configuration before re-sending, this error is
+ likely to happen again.
+
+ <tp:rationale>
+ Similar to errors with type="cancel", type="modify"
+ or type="auth" in XMPP; analogous to 5xx errors in SMTP.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Accepted" value="4">
+ <tp:docstring>
+ An intermediate server has accepted the message but the message
+ has not been yet delivered to the ultimate recipient. The
+ connection manager might send a Failed report or Delivered report
+ later.
+
+ <tp:rationale>
+ Similar to "202 Accepted" success code in SIP; analogous to
+ 251 and 252 responses in SMTP.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Read" value="5">
+ <tp:added version="0.19.9"/>
+ <tp:docstring>
+ The message has been read by the intended recipient.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Deleted" value="6">
+ <tp:added version="0.19.9"/>
+ <tp:docstring>
+ The message has been deleted by the intended recipient. This MAY be
+ signalled on its own if the message is deleted without being read, or
+ after <code>Read</code> if the message was read before being deleted.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:flags name="Delivery_Reporting_Support_Flags"
+ value-prefix="Delivery_Reporting_Support_Flag" type="u">
+ <tp:docstring>
+ Flags indicating the level of support for delivery reporting on this
+ channel, as found on the
+ <tp:member-ref>DeliveryReportingSupport</tp:member-ref> property. Any
+ future flags added to this set will conform to the
+ convention that the presence of an extra flag implies that
+ more operations will succeed. Note that CMs may always provide more
+ reports than are requested in the
+ <tp:type>Message_Sending_Flags</tp:type> passed to
+ <tp:member-ref>SendMessage</tp:member-ref>.
+
+ <tp:rationale>
+ If senders want delivery reports, they should ask for them. If they
+ don't want delivery reports, they can just ignore them, so there's no
+ need to have capability discovery for what will happen if a delivery
+ report isn't requested.
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:flag suffix="Receive_Failures" value="1">
+ <tp:docstring>
+ Clients MAY expect to receive negative delivery reports if
+ Message_Sending_Flag_Report_Delivery is specified when sending.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Receive_Successes" value="2">
+ <tp:docstring>
+ Clients MAY expect to receive positive delivery reports if
+ Message_Sending_Flag_Report_Delivery is specified when sending.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Receive_Read" value="4">
+ <tp:added version="0.19.9"/>
+ <tp:docstring>
+ Clients MAY expect to receive <tp:type>Delivery_Status</tp:type>
+ <code>Read</code> reports if Message_Sending_Flag_Report_Read
+ is specified when sending.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Receive_Deleted" value="8">
+ <tp:added version="0.19.9"/>
+ <tp:docstring>
+ Clients MAY expect to receive <tp:type>Delivery_Status</tp:type>
+ <code>Deleted</code> reports if Message_Sending_Flag_Report_Deleted
+ is specified when sending.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <property name="DeliveryReportingSupport" access="read"
+ tp:type="Delivery_Reporting_Support_Flags" type="u"
+ tp:name-for-bindings="Delivery_Reporting_Support"
+ tp:immutable="yes">
+ <tp:docstring>
+ A bitfield indicating features supported by this channel.
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Password.xml b/qt4/spec/Channel_Interface_Password.xml
new file mode 100644
index 000000000..4e201ddf5
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Password.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Password" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>
+Copyright © 2005-2009 Collabora Limited
+Copyright © 2005-2009 Nokia Corporation
+Copyright © 2006 INdT
+ </tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Password">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:flags name="Channel_Password_Flags" value-prefix="Channel_Password_Flag" type="u">
+ <tp:flag suffix="Provide" value="8">
+ <tp:docstring>
+ The <tp:member-ref>ProvidePassword</tp:member-ref> method must be
+ called now for the user to join the channel
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+ <method name="GetPasswordFlags" tp:name-for-bindings="Get_Password_Flags">
+ <arg direction="out" type="u" tp:type="Channel_Password_Flags"
+ name="Password_Flags">
+ <tp:docstring>
+ An integer with the logical OR of all the flags set
+ (values of ChannelPasswordFlags)
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns the bitwise-OR of the flags relevant to the password on this
+ channel. The user interface can use this to present information about
+ which operations are currently valid.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+ <signal name="PasswordFlagsChanged"
+ tp:name-for-bindings="Password_Flags_Changed">
+ <arg name="Added" type="u" tp:type="Channel_Password_Flags">
+ <tp:docstring>
+ A bitwise OR of the flags which have been set
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="u" tp:type="Channel_Password_Flags">
+ <tp:docstring>
+ A bitwise OR of the flags which have been cleared
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the flags as returned by
+ <tp:member-ref>GetPasswordFlags</tp:member-ref> are changed.
+ The user interface should be updated as appropriate.
+ </tp:docstring>
+ </signal>
+ <method name="ProvidePassword" tp:name-for-bindings="Provide_Password">
+ <arg direction="in" name="Password" type="s">
+ <tp:docstring>
+ The password
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="b" name="Correct">
+ <tp:docstring>
+ A boolean indicating whether or not the password was correct
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Provide the password so that the channel can be joined. Must be
+ called with the correct password in order for channel joining to
+ proceed if the 'provide' password flag is set.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for channels that may have a password set that users need
+ to provide before being able to join, or may be able to view or change
+ once they have joined the channel.</p>
+
+ <p>The <tp:member-ref>GetPasswordFlags</tp:member-ref> method and the
+ associated <tp:member-ref>PasswordFlagsChanged</tp:member-ref>
+ signal indicate whether the channel has a password, whether the user
+ must now provide it to join, and whether it can be viewed or changed
+ by the user.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Room.xml b/qt4/spec/Channel_Interface_Room.xml
new file mode 100644
index 000000000..ffdf4a96d
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Room.xml
@@ -0,0 +1,373 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Room"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:added version="0.19.11">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Different IM protocols use a variety of ways to name chat rooms. The
+ simplest example is perhaps IRC, where chat rooms have short,
+ persistent, human-readable string names, and are generally global
+ across the network. Skype chat rooms have persistent string names, so
+ you can leave and re-join a room, but these names are opaque unique
+ identifiers. MSN chat rooms are unnamed, and you can only join one by
+ being invited. And XMPP wins the coveted “most complicated chat rooms”
+ prize: chat rooms may be hosted by different servers with different DNS
+ names; normally they have human-readable names, except that all MUCs on
+ Google Talk's conference server have UUIDs as names, and <a
+ href="http://xmpp.org/extensions/xep-0045.html#createroom-unique"><acronym
+ title="XMPP Extension Protocol">XEP</acronym>-0045 §10.1.4
+ <q>Requesting a Unique Room Name</q></a> defines a protocol for
+ requesting a unique, opaque room name on the server.</p>
+
+ <p>This interface intends to support and differentiate these mechanisms
+ more clearly than the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ properties can alone. It initially contains a pair of properties used
+ to represent the human-readable parts of a
+ <tp:type>Room_Handle</tp:type>'s identifier, if any. The above examples
+ for different protocols are represented as follows:</p>
+
+ <ul>
+ <li>The IRC channel <tt>#telepathy</tt> on Freenode is represented by a
+ channel with properties
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = <code>Room</code>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ = <code>"#telepathy"</code>,
+ <tp:member-ref>RoomID</tp:member-ref> = <code>"#telepathy"</code>,
+ <tp:member-ref>Server</tp:member-ref> = <code>""</code>, indicating
+ that the room has a human-readable identifier, and is not confined to
+ a particular server on the network.
+
+ <tp:rationale>
+ Actually, IRC supports creating “local” channels specific to the
+ server they are created on. These channels have identifiers
+ starting with <tt>&amp;</tt> rather than <tt>#</tt>. These could be
+ represented by setting <tp:member-ref>Server</tp:member-ref>
+ appropriately.
+ </tp:rationale>
+ </li>
+
+ <li>A Skype group chat with opaque identifier <tt>0xdeadbeef</tt> has
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = <code>Room</code>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ = <code>"0xdeadbeef"</code>,
+ <tp:member-ref>RoomID</tp:member-ref> = <code>""</code>,
+ <tp:member-ref>Server</tp:member-ref> = <code>""</code>, indicating
+ that the room has an identifier but no human-readable name.
+ </li>
+
+ <li>An MSN group chat has
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = <code>None</code>,
+ <tp:member-ref>RoomID</tp:member-ref> = <code>""</code>,
+ <tp:member-ref>Server</tp:member-ref> = <code>""</code>, indicating
+ that the room has neither an identifier (so it cannot be re-joined
+ later) nor a human-readable name.
+ </li>
+
+ <li>A standard Jabber multi-user chat
+ <tt>jdev@conference.jabber.org</tt> has
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = <code>Room</code>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ = <code>"jdev@conference.jabber.org"</code>,
+ <tp:member-ref>RoomID</tp:member-ref> = <code>"jdev"</code>,
+ <tp:member-ref>Server</tp:member-ref> = <code>"conference.jabber.org"</code>.
+ </li>
+
+ <li>A Google Talk private MUC <tt>private-chat-11111x1x-11xx-111x-1111-111x1xx11x11@groupchat.google.com</tt> has
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = <code>Room</code>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ = <code>"private-chat-11111x1x-11xx-111x-1111-111x1xx11x11@groupchat.google.com"</code>,
+ <tp:member-ref>RoomID</tp:member-ref> = <code>""</code>,
+ <tp:member-ref>Server</tp:member-ref> =
+ <code>"groupchat.google.com"</code>, indicating that the room has a
+ persistent identifier, no human-readable name, and is hosted by a
+ particular server.
+ </li>
+
+ <li>Similarly, a XEP-0045 §10.1.4 uniquely-named room
+ <tt>lrcgsnthzvwm@conference.jabber.org</tt> has
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = <code>Room</code>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ = <code>"lrcgsnthzvwm@conference.jabber.org"</code>,
+ <tp:member-ref>RoomID</tp:member-ref> = <code>""</code>,
+ <tp:member-ref>Server</tp:member-ref> =
+ <code>"conference.jabber.org"</code>, indicating that the room has a
+ persistent identifier, no human-readable name, and is hosted by a
+ particular server.
+ </li>
+ </ul>
+
+ <h4>Requestable channel classes</h4>
+
+ <p>If the connection supports joining text chat rooms by unique
+ identifier, like Skype, it should advertise a
+ <tp:type>Requestable_Channel_Class</tp:type> matching:</p>
+
+ <blockquote>
+ <pre>
+( Fixed = { ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type"
+ >Text</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref>: Room,
+ },
+ Allowed = [ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetID</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandle</tp:dbus-ref>,
+ ]
+)</pre></blockquote>
+
+ <p>Channel requests must specify either <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetID</tp:dbus-ref> or <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandle</tp:dbus-ref>.</p>
+
+ <p>If, like IRC, the room identifiers are also human-readable, the
+ RCCs should also include RoomID in <var>Allowed_Properties</var>:</p>
+
+ <blockquote>
+ <pre>
+( Fixed = { ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type"
+ >Text</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref>: Room,
+ },
+ Allowed = [ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetID</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandle</tp:dbus-ref>,
+ ...<tp:member-ref>RoomID</tp:member-ref>
+ ]
+),
+
+( Fixed = { ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type"
+ >Text</tp:dbus-ref>
+ },
+ Allowed = [ ...<tp:member-ref>RoomID</tp:member-ref>,
+ ]
+)</pre></blockquote>
+
+ <p>Requests may specify the RoomID in place of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref> or
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ . Note how <tp:member-ref>RoomID</tp:member-ref> appears
+ in <var>Allowed_Properties</var> of a different RCC because
+ when <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref> is omitted (or is None), both
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandle</tp:dbus-ref> and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetID</tp:dbus-ref> must also be omitted.
+ <tp:member-ref>RoomID</tp:member-ref> is allowed in conjuction
+ with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref> or
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ in some situations, as explained below in the <em>Requesting room
+ channels</em> section.
+ </p>
+
+ <p>If rooms may be on different servers, <tp:member-ref>Server</tp:member-ref>
+ should also be included in the allowed properties, but
+ CMs MUST use a reasonable default
+ <tp:member-ref>Server</tp:member-ref> if not explicitly
+ specified in a channel request. The CM's default server MAY
+ be configurable by a connection parameter specified on a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.ConnectionManager"
+ >RequestConnection</tp:dbus-ref> call, similarly to how the
+ fallback conference server is specified on jabber connections in
+ gabble.</p>
+
+ <p>If the protocol supports unnamed rooms, <tp:member-ref>RoomID</tp:member-ref>
+ should be fixed to the empty string, and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ should be None:</p>
+
+ <blockquote>
+ <pre>
+( Fixed = { ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type"
+ >Text</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref>: None,
+ ...<tp:member-ref>RoomID</tp:member-ref>: "",
+ },
+ Allowed = [ ]
+)</pre></blockquote>
+
+ <h4>Requesting room channels</h4>
+
+ <p>When explicitly joining a room, the CM cannot know whether the room
+ ID is unique or not. As a result, if this is the case, adding an
+ empty string <tp:member-ref>RoomID</tp:member-ref> into the channel
+ request will ensure the CM knows. For example:</p>
+
+ <blockquote>
+ <pre>
+{ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>: Room,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>: "qwerasdfzxcv@conference.jabber.org",
+ ...<tp:member-ref>RoomID</tp:member-ref>: ""
+}</pre></blockquote>
+
+ <p>If <tp:member-ref>RoomID</tp:member-ref> features in
+ <var>Allowed_Properties</var> then the only value allowed in conjunction
+ with <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ or <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ is the empty string. Requests with conflicting
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>
+ and <tp:member-ref>RoomID</tp:member-ref> properties
+ will fail with InvalidArgument.</p>
+
+ <p>To create a XEP-0045 §10.1.4 uniquely-named room channel
+ on the conference.jabber.org server, then the following channel
+ request should be made:</p>
+
+ <blockquote>
+ <pre>
+{ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>,
+ ...<tp:member-ref>RoomID</tp:member-ref>: ""
+ ...<tp:member-ref>Server</tp:member-ref>: "conference.jabber.org"
+}</pre>
+ </blockquote>
+
+ <p>If everything is successful, then when the channel request is
+ satisfied, a new channel will appear with the following properties:</p>
+
+ <blockquote>
+ <pre>
+{ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>: Room,
+ ...<tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>: "kajsdhkajshdfjkshdfjkhs@conference.jabber.org",
+ ...<tp:member-ref>RoomID</tp:member-ref>: ""
+ ...<tp:member-ref>Server</tp:member-ref>: "conference.jabber.org"
+}</pre>
+ </blockquote>
+
+ <p>The CM will have received the unique room name (kajsdhkajshdfjkshdfjkhs)
+ and then created a room with such a name on the said server. The empty
+ <tp:member-ref>RoomID</tp:member-ref> property shows that the room name
+ is not human-readable.</p>
+
+ </tp:docstring>
+
+ <property name="RoomID" tp:name-for-bindings="Room_ID" type="s"
+ access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The human-readable identifier of a chat room. Note that if
+ non-empty, this property (and perhaps also
+ <tp:member-ref>Server</tp:member-ref>) should be sufficient in
+ a channel request to join the room. XMPP MUCs have a room name
+ concept which is more like a topic, except more
+ persistent. This D-Bus property is <strong>not</strong> this
+ XMPP room name, but the bit before the @ in the room jid.</p>
+
+ <p>This property cannot change during the lifetime of the channel. It
+ should appear in the <var>Allowed_Properties</var> of a
+ <tp:type>Requestable_Channel_Class</tp:type> for the connection if
+ rooms on this connection have human-readable names, and can be joined
+ by name.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Server" tp:name-for-bindings="Server" type="s"
+ access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For protocols with a concept of chatrooms on multiple servers with
+ different DNS names (like XMPP), the DNS name of the server hosting
+ this channel (for example, <tt>"conference.jabber.org"</tt> or
+ <tt>"groupchat.google.com"</tt>). For other protocols, the empty
+ string.</p>
+
+ <p>This property cannot change during the lifetime of the channel. It
+ should appear in the <var>Allowed_Properties</var> of a
+ <tp:type>Requestable_Channel_Class</tp:type> for the connection if
+ and only if non-empty values are supported.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:struct name="Room_Subject">
+ <tp:docstring>
+ A struct representing the subject of a room channel.
+ </tp:docstring>
+ <tp:member type="s" name="Subject">
+ <tp:docstring>
+ A human-readable description of the current subject of
+ conversation in the channel, similar to /topic in IRC.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Actor">
+ <tp:docstring>
+ A normalized contact ID representing who last modified the
+ subject, or the empty string if it is not known.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="x" tp:type="Unix_Timestamp64" name="Timestamp">
+ <tp:docstring>
+ A unix timestamp indicating when the subject was last modified.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="Subject" tp:name-for-bindings="Subject"
+ type="(ssx)" tp:type="Room_Subject" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The subject on the room such as the topic in an IRC channel,
+ or the room name in XMPP MUCs. In protocols which do not support
+ subjects (like MSN), this property should be ("", "", 0).</p>
+
+ <tp:rationale>This property replaces the subject, subject-contact, and
+ subject-timestamp Telepathy properties of Text channels, as Telepathy
+ properties are soon to be deprecated completely.</tp:rationale>
+
+ <p>This property may change during the lifetime of the channel and
+ MUST not be included in a channel request.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_SASL_Authentication.xml b/qt4/spec/Channel_Interface_SASL_Authentication.xml
new file mode 100644
index 000000000..7985a6bd5
--- /dev/null
+++ b/qt4/spec/Channel_Interface_SASL_Authentication.xml
@@ -0,0 +1,723 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_SASL_Authentication"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2010 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication">
+ <tp:added version="0.21.5">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel interface for SASL authentication,
+ as defined by
+ <a href="http://tools.ietf.org/html/rfc4422">RFC 4422</a>.
+ When this interface appears on a <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channel, it represents authentication with the server. In future,
+ it could also be used to authenticate with secondary services,
+ or even to authenticate end-to-end connections with contacts. As a result,
+ this interface does not REQUIRE <tp:dbus-ref namespace="ofdT.Channel.Type"
+ >ServerAuthentication</tp:dbus-ref> to allow for a potential future
+ Channel.Type.PeerAuthentication interface.</p>
+
+ <p>In any protocol that requires a password, the connection manager can
+ use this channel to let a user interface carry out a simple SASL-like
+ handshake with it, as a way to get the user's credentials
+ interactively. This can be used to connect to protocols that may
+ require a password, without requiring that the password is saved in
+ the <tp:dbus-ref
+ namespace="ofdT">Account.Parameters</tp:dbus-ref>.</p>
+
+ <p>In some protocols, such as XMPP, authentication with the server
+ is also carried out using SASL. In these protocols, a channel with this
+ interface can provide a simple 1:1 mapping of the SASL negotiations
+ taking place in the protocol, allowing more advanced clients to
+ perform authentication via SASL mechanisms not known to the
+ connection manager.</p>
+
+ <tp:rationale>
+ <p>By providing SASL directly when the protocol supports it, we can
+ use mechanisms like Kerberos or Google's <code>X-GOOGLE-TOKEN</code>
+ without specific support in the connection manager.</p>
+ </tp:rationale>
+
+ <p>For channels managed by a
+ <tp:dbus-ref namespace="ofdT">ChannelDispatcher</tp:dbus-ref>,
+ only the channel's <tp:dbus-ref
+ namespace="ofdT.Client">Handler</tp:dbus-ref> may call the
+ methods on this interface. Other clients MAY observe the
+ authentication process by watching its signals and properties.</p>
+
+ <tp:rationale>
+ <p>There can only be one Handler, which is a good fit for SASL's
+ 1-1 conversation between a client and a server.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:simple-type name="SASL_Mechanism" type="s"
+ array-name="SASL_Mechanism_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A SASL mechanism, as defined by
+ <a href="http://tools.ietf.org/html/rfc4422">RFC 4422</a>
+ and registered in
+ <a href="http://www.iana.org/assignments/sasl-mechanisms">the
+ IANA registry of SASL mechanisms</a>, or an unregistered
+ SASL mechanism such as <code>X-GOOGLE-TOKEN</code> used in the
+ same contexts.</p>
+
+ <p>As a special case, pseudo-mechanisms starting with
+ <code>X-TELEPATHY-</code> are defined by this specification.
+ Use of these pseudo-mechanisms indicates that the user's credentials
+ are to be passed to the connection manager, which will then use
+ them for authentication with the service, either by implementing
+ the client side of some SASL mechanisms itself or by using a
+ non-SASL protocol. The only such pseudo-mechanism currently
+ defined is <code>X-TELEPATHY-PASSWORD</code>.</p>
+
+ <p>The <code>X-TELEPATHY-PASSWORD</code> mechanism is extremely
+ simple:</p>
+
+ <ul>
+ <li>The client MUST call
+ <tp:member-ref>StartMechanismWithData</tp:member-ref>, with
+ Initial_Data set to the password encoded in UTF-8.
+ For simplicity, calling <tp:member-ref>StartMechanism</tp:member-ref>
+ followed by calling <tp:member-ref>Respond</tp:member-ref> is not
+ allowed in this mechanism.</li>
+
+ <li>The connection manager uses the password, together with
+ authentication details from the Connection parameters, to
+ authenticate itself to the server.</li>
+
+ <li>When the connection manager finishes its attempt to authenticate
+ to the server, the channel's state changes to
+ either SASL_Status_Server_Succeeded or
+ SASL_Status_Server_Failed as appropriate.</li>
+ </ul>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <property name="AvailableMechanisms"
+ tp:name-for-bindings="Available_Mechanisms"
+ type="as" tp:type="SASL_Mechanism[]"
+ access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The SASL mechanisms as offered by the server, plus any
+ pseudo-SASL mechanisms supported by the connection manager for
+ credentials transfer. For instance, in a protocol that
+ natively uses SASL (like XMPP), this might be
+ <code>[ "X-TELEPATHY-PASSWORD", "PLAIN", "DIGEST-MD5",
+ "SCRAM-SHA-1" ]</code>.</p>
+
+ <p>To make it possible to implement a very simple password-querying
+ user interface without knowledge of any particular SASL mechanism,
+ implementations of this interface MUST implement the
+ pseudo-mechanism <code>X-TELEPATHY-PASSWORD</code>, unless none
+ of the available mechanisms use a password at all.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="HasInitialData" tp:name-for-bindings="Has_Initial_Data"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, <tp:member-ref>StartMechanismWithData</tp:member-ref>
+ can be expected to work for SASL mechanisms not starting with
+ <code>X-TELEPATHY-</code> (this is the case in most, but not all,
+ protocols). If false, <tp:member-ref>StartMechanism</tp:member-ref>
+ must be used instead.</p>
+
+ <p>This property does not affect the <code>X-TELEPATHY-</code>
+ pseudo-mechanisms such as <code>X-TELEPATHY-PASSWORD</code>,
+ which can use
+ <tp:member-ref>StartMechanismWithData</tp:member-ref> regardless
+ of the value of this property.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CanTryAgain" tp:name-for-bindings="Can_Try_Again"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, <tp:member-ref>StartMechanism</tp:member-ref> and (if
+ supported) <tp:member-ref>StartMechanismWithData</tp:member-ref>
+ can be expected to work when in one of the Failed states. If
+ false, the only thing you can do after failure is to close the
+ channel.</p>
+
+ <tp:rationale>
+ <p>Retrying isn't required to work, although some protocols and
+ implementations allow it.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property type="u" tp:type="SASL_Status" access="read"
+ name="SASLStatus" tp:name-for-bindings="SASL_Status">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The current status of this channel.
+ Change notification is via the
+ <tp:member-ref>SASLStatusChanged</tp:member-ref> signal.
+ </tp:docstring>
+ </property>
+
+ <property type="s" tp:type="DBus_Error_Name" access="read"
+ name="SASLError" tp:name-for-bindings="SASL_Error">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The reason for the <tp:member-ref>SASLStatus</tp:member-ref>, or
+ an empty string if the state is neither
+ Server_Failed nor Client_Failed.</p>
+
+ <p>In particular, an ordinary authentication failure (as would
+ be produced for an incorrect password) SHOULD be represented by
+ <tp:error-ref>AuthenticationFailed</tp:error-ref>,
+ cancellation by the user's request SHOULD be represented
+ by <tp:error-ref>Cancelled</tp:error-ref>, and
+ cancellation by a local process due to inconsistent or invalid
+ challenges from the server SHOULD be represented by
+ <tp:error-ref>ServiceConfused</tp:error-ref>.</p>
+
+ <p>If this interface appears on a <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channel, and connection to the server fails with an authentication
+ failure, this error code SHOULD be copied into the
+ <tp:dbus-ref
+ namespace="ofdT">Connection.ConnectionError</tp:dbus-ref>
+ signal.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="SASLErrorDetails"
+ tp:name-for-bindings="SASL_Error_Details"
+ access="read" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <tp:member-ref>SASLError</tp:member-ref> is non-empty,
+ any additional information about the last
+ disconnection; otherwise, the empty map. The keys and values are
+ the same as for the second argument of
+ <tp:dbus-ref
+ namespace="ofdT">Connection.ConnectionError</tp:dbus-ref>.</p>
+
+ <p>If this interface appears on a <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channel, and connection to the server fails with an authentication
+ failure, these details SHOULD be copied into the
+ <tp:dbus-ref
+ namespace="ofdT">Connection.ConnectionError</tp:dbus-ref>
+ signal.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="AuthorizationIdentity"
+ tp:name-for-bindings="Authorization_Identity"
+ type="s" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The identity for which authorization is being attempted,
+ typically the 'account' from the <tp:dbus-ref
+ namespace="ofdT.ConnectionManager">RequestConnection</tp:dbus-ref>
+ parameters, normalized and formatted according to the
+ conventions used for SASL in this protocol.</p>
+
+ <tp:rationale>
+ <p>The normalization used for SASL might not be the same
+ normalization used elsewhere: for instance, in a protocol
+ with email-like identifiers such as XMPP or SIP, the user
+ "juliet@example.com" might have to authenticate to the
+ example.com server via SASL PLAIN as "juliet".</p>
+ </tp:rationale>
+
+ <p>This is usually achieved by using the authorization identity for
+ authentication, but an advanced Handler could offer the option
+ to authenticate under a different identity.</p>
+
+ <p>The terminology used here is that the authorization identity
+ is who you want to act as, and the authentication identity is
+ used to prove that you may do so. For instance, if Juliet is
+ authorized to access a role account, "sysadmin@example.com",
+ and act on its behalf, it might be possible to authenticate
+ as "juliet@example.com" with her own password, but request to
+ be authorized as "sysadmin@example.com" instead of her own
+ account. See
+ <a href="http://tools.ietf.org/html/rfc4422#section-3.4.1">RFC
+ 4422 §3.4.1</a> for more details.</p>
+
+ <tp:rationale>
+ <p>In SASL the authorization identity is normally guessed from
+ the authentication identity, but the information available
+ to the connection manager is the identity for which
+ authorization is required, such as the desired JID in XMPP,
+ so that's what we signal to UIs; it's up to the UI to
+ choose whether to authenticate as the authorization identity
+ or some other identity.</p>
+
+ <p>As a concrete example, the "sysadmin" XMPP account mentioned
+ above would have <code>{ 'account': 'sysadmin@example.com' }</code>
+ in its Parameters, and this property would also be
+ 'sysadmin@example.com'. A simple Handler would
+ merely prompt for sysadmin@example.com's password,
+ and use that JID as both the authorization and authentication
+ identity, which might result in SASL PLAIN authentication with the
+ initial response
+ '\000sysadmin@example.com\000root'.</p>
+
+ <p>A more advanced Handler might also ask for an authentication
+ identity, defaulting to 'sysadmin@example.com'; if Juliet
+ provided authentication identity 'juliet@example.com' and
+ password 'romeo', the Handler might perform SASL PLAIN
+ authentication using the initial response
+ 'sysadmin@example.com\000juliet@example.com\000romeo'.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="DefaultUsername"
+ tp:name-for-bindings="Default_Username"
+ type="s" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The default username for use with SASL mechanisms that deal
+ with a "simple username" (as defined in <a
+ href="http://tools.ietf.org/html/rfc4422">RFC 4422</a>). If
+ such a SASL mechanism is in use, clients SHOULD default to
+ using the DefaultUsername; also, if the client uses
+ the DefaultUsername, it SHOULD assume that the authorization
+ identity <tp:member-ref>AuthorizationIdentity</tp:member-ref>
+ will be derived from it by the server.</p>
+
+ <tp:rationale>
+ <p>In XMPP, <a href="http://trac.tools.ietf.org/wg/xmpp/trac/ticket/49">
+ servers typically expect</a> "user@example.com" to
+ authenticate with username "user"; this was a SHOULD in <a
+ href="http://tools.ietf.org/html/rfc3920">RFC 3920</a>.</p>
+
+ <p><a
+ href="http://tools.ietf.org/html/draft-ietf-xmpp-3920bis-19">3920bis</a>
+ weakens that SHOULD to "in the absence of local information
+ provided by the server, an XMPP client SHOULD assume that
+ the authentication identity for such a SASL mechanism is the
+ combination of a user name and password, where the simple
+ user name is the localpart of the user's JID".</p>
+ </tp:rationale>
+
+ <p>For example, in the simple case, if the user connects with
+ <tp:dbus-ref
+ namespace="ofdT.ConnectionManager">RequestConnection</tp:dbus-ref>({
+ account: "<tt>user@example.com</tt>" }) and use PLAIN with
+ password "password", he or she should authenticate like so:
+ "<tt>\0user\0password</tt>" and the channel will look like
+ this:</p>
+
+<blockquote><pre>{ "...<tp:member-ref>DefaultUsername</tp:member-ref>": "user",
+ "...<tp:member-ref>AuthorizationIdentity</tp:member-ref>": "user@example.com }
+</pre></blockquote>
+
+ <p>In the complex case, if the same user is using his or her
+ sysadmin powers to log in as the "announcements" role address,
+ he or she would connect with <tp:dbus-ref
+ namespace="ofdT.ConnectionManager">RequestConnection</tp:dbus-ref>({
+ account: "<tt>announcements@example.com</tt>" }) and the SASL
+ channel would look like this:</p>
+
+<blockquote><pre>{ "...<tp:member-ref>DefaultUsername</tp:member-ref>": "announcements",
+ "...<tp:member-ref>AuthorizationIdentity</tp:member-ref>": "announcements@example.com }
+</pre></blockquote>
+
+ <p>A sufficiently elaborate UI could give the opportunity
+ to override the username from "announcements" to "user".
+ The user's simple username is still "user", and the password is
+ still "password", but this time he or she is trying to authorize
+ to act as <tt>announcements@example.com</tt>, so the UI would
+ have to perform SASL PLAIN with this string:
+ "<tt>announcements@example.com\0user\0password</tt>", where
+ "announcements@example.com" is the
+ <tp:member-ref>AuthorizationIdentity</tp:member-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="DefaultRealm" tp:name-for-bindings="Default_Realm"
+ type="s" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The default realm (as defined in
+ <a href="http://tools.ietf.org/html/rfc2831#section-2.1.1">RFC
+ 2831</a>) to use for authentication, if the server does not
+ supply one.</p>
+
+ <tp:rationale>
+ <p>The server is not required to provide a realm; if it doesn't,
+ the client is expected to ask the user or provide a sensible
+ default, typically the requested DNS name of the server.
+ In some implementations of <code>DIGEST-MD5</code>, the
+ server does not specify a realm, but expects that the client
+ will choose a particular default, and authentication will
+ fail if the client's default is different. Connection
+ managers for protocols where this occurs are more easily able
+ to work around these implementations than a generic client
+ would be.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="MaySaveResponse" tp:name-for-bindings="May_Save_Response"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Whether or not the client can save the authentication response and
+ re-use it to automate future authentication challenges.</p>
+
+ <p>If this property is <code>False</code>, the client SHOULD NOT attempt
+ to cache the authentication response in its own keyring.</p>
+
+ <p>If this property is not specified, it should be treated as if it were
+ <code>True</code>.</p>
+
+ <tp:rationale>Some protocols or services may have terms and conditions
+ that prohibit caching a user's credentials.</tp:rationale>
+
+ </tp:docstring>
+ </property>
+
+
+ <method name="StartMechanism" tp:name-for-bindings="Start_Mechanism">
+ <arg direction="in" name="Mechanism" type="s" tp:type="SASL_Mechanism">
+ <tp:docstring>
+ The chosen mechanism.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Start an authentication try using <var>Mechanism</var>, without
+ sending initial data (an "initial response" as defined in RFC
+ 4422).</p>
+
+ <tp:rationale>
+ <p>This method is appropriate for mechanisms where the client
+ cannot send anything until it receives a challenge from the
+ server, such as
+ <code><a href="http://tools.ietf.org/html/rfc2831">DIGEST-MD5</a></code>
+ in "initial authentication" mode.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The channel is not in a state where starting authentication makes
+ sense (i.e. SASL_Status_Not_Started, or (if
+ <tp:member-ref>CanTryAgain</tp:member-ref> is true)
+ SASL_Status_Server_Failed or
+ SASL_Status_Client_Failed). You should call
+ <tp:member-ref>AbortSASL</tp:member-ref> and wait for
+ SASL_Status_Client_Failed before starting another attempt.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The server or connection manager doesn't implement the given
+ SASL mechanism. Choose a SASL mechanism from
+ <tp:member-ref>AvailableMechanisms</tp:member-ref>, or abort
+ authentication if none of them are suitable.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="StartMechanismWithData"
+ tp:name-for-bindings="Start_Mechanism_With_Data">
+ <arg direction="in" name="Mechanism" type="s" tp:type="SASL_Mechanism">
+ <tp:docstring>
+ The chosen mechanism.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Initial_Data" type="ay">
+ <tp:docstring>
+ Initial data (an "initial response" in RFC 4422's terminology) to send
+ with the mechanism.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Start an authentication try using <var>Mechanism</var>, and send
+ <var>Initial_Data</var> as the "initial response" defined in
+ <a href="http://tools.ietf.org/html/rfc4422#section-3.3">RFC 4422
+ §3.3</a>.</p>
+
+ <tp:rationale>
+ <p>This method is appropriate for mechanisms where the client may
+ send data first, such as <code>PLAIN</code>, or must send data
+ first, such as
+ <code><a href="http://tools.ietf.org/html/rfc2831">DIGEST-MD5</a></code>
+ in "subsequent authentication" mode.</p>
+
+ <p>Having two methods allows any mechanism where it makes a difference
+ to distinguish between the absence of an initial response
+ (<tp:member-ref>StartMechanism</tp:member-ref>) and a zero-byte
+ initial response (StartMechanismWithData, with Initial_Data
+ empty).</p>
+ </tp:rationale>
+
+ <p>If the <tp:member-ref>HasInitialData</tp:member-ref>
+ property is false, this indicates that the underlying protocol
+ does not make it possible to send initial data. In such protocols,
+ this method may only be used for the <code>X-TELEPATHY-</code>
+ pseudo-mechanisms (such as <code>X-TELEPATHY-PASSWORD</code>),
+ and will fail if used with an ordinary SASL mechanism.</p>
+
+ <tp:rationale>
+ <p>For instance, the IRC SASL extension implemented in Charybdis and
+ Atheme does not support initial data - the first message in the
+ exchange only carries the mechanism. This is significant if using
+ <code><a href="http://tools.ietf.org/html/rfc2831">DIGEST-MD5</a></code>,
+ which cannot be used in the faster "subsequent authentication"
+ mode on a protocol not supporting initial data.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The channel is not in a state where starting authentication makes
+ sense (i.e. SASL_Status_Not_Started, or (if
+ <tp:member-ref>CanTryAgain</tp:member-ref> is true)
+ SASL_Status_Server_Failed or
+ SASL_Status_Client_Failed). You should call
+ <tp:member-ref>AbortSASL</tp:member-ref> and wait for
+ SASL_Status_Client_Failed before starting another attempt.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The server or connection manager doesn't implement the given
+ SASL mechanism (choose one from
+ <tp:member-ref>AvailableMechanisms</tp:member-ref>, or abort
+ authentication if none of them are suitable), or doesn't allow
+ initial data to be sent (as indicated by
+ <tp:member-ref>HasInitialData</tp:member-ref>; call
+ <tp:member-ref>StartMechanism</tp:member-ref> instead).
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Respond" tp:name-for-bindings="Respond">
+ <arg direction="in" name="Response_Data" type="ay">
+ <tp:docstring>
+ The response data.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Send a response to the the last challenge received via
+ <tp:member-ref>NewChallenge</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ Either the state is not In_Progress, or no challenge has been
+ received yet, or you have already responded to the last challenge.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AcceptSASL" tp:name-for-bindings="Accept_SASL">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If the channel's status is SASL_Status_Server_Succeeded,
+ this method confirms successful authentication and advances
+ the status of the channel to SASL_Status_Succeeded.</p>
+
+ <p>If the channel's status is SASL_Status_In_Progress, calling this
+ method indicates that the last
+ <tp:member-ref>NewChallenge</tp:member-ref> signal was in fact
+ additional data sent after a successful SASL negotiation, and
+ declares that from the client's point of view, authentication
+ was successful. This advances the state of the channel to
+ SASL_Status_Client_Accepted.</p>
+
+ <p>In mechanisms where the server authenticates itself to the client,
+ calling this method indicates that the client considers this to have
+ been successful. In the case of <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channels, this means that the connection manager MAY continue to
+ connect, and MAY advance the <tp:dbus-ref
+ namespace="ofdT">Connection.Status</tp:dbus-ref> to Connected.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ Either the state is neither In_Progress nor Server_Succeeded, or no
+ challenge has been received yet, or you have already responded to
+ the last challenge.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AbortSASL" tp:name-for-bindings="Abort_SASL">
+ <arg direction="in" name="Reason" type="u" tp:type="SASL_Abort_Reason">
+ <tp:docstring>
+ Reason for abort.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Debug_Message" type="s">
+ <tp:docstring>
+ Debug message for abort.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Abort the current authentication try.</p>
+
+ <p>If the current status is SASL_Status_Server_Failed or
+ SASL_Status_Client_Failed, this method returns successfully, but has
+ no further effect. If the current status is SASL_Status_Succeeded
+ or SASL_Status_Client_Accepted then NotAvailable is raised.
+ Otherwise, it changes the channel's state to
+ SASL_Status_Client_Failed, with an appropriate error name and
+ reason code.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The current state is either Succeeded or Client_Accepted.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="SASLStatusChanged" tp:name-for-bindings="SASL_Status_Changed">
+ <tp:docstring>
+ Emitted when the status of the channel changes.
+ </tp:docstring>
+ <arg type="u" tp:type="SASL_Status" name="Status">
+ <tp:docstring>
+ The new value of <tp:member-ref>SASLStatus</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ <arg type="s" tp:type="DBus_Error_Name" name="Reason">
+ <tp:docstring>
+ The new value of <tp:member-ref>SASLError</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ <arg type="a{sv}" tp:type="String_Variant_Map" name="Details">
+ <tp:docstring>
+ The new value of <tp:member-ref>SASLErrorDetails</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="NewChallenge" tp:name-for-bindings="New_Challenge">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new challenge is received from the server, or when
+ a message indicating successful authentication and containing
+ additional data is received from the server.</p>
+
+ <p>When the channel's handler is ready to proceed, it should respond
+ to the challenge by calling <tp:member-ref>Respond</tp:member-ref>,
+ or respond to the additional data by calling
+ <tp:member-ref>AcceptSASL</tp:member-ref>. Alternatively, it may call
+ <tp:member-ref>AbortSASL</tp:member-ref> to abort authentication.</p>
+ </tp:docstring>
+ <arg name="Challenge_Data" type="ay">
+ <tp:docstring>
+ The challenge data or additional data from the server.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="SASL_Abort_Reason" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A reason why SASL authentication was aborted by the client.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Invalid_Challenge" value="0">
+ <tp:docstring>
+ The server sent an invalid challenge or data.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="User_Abort" value="1">
+ <tp:docstring>
+ The user aborted the authentication.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="SASL_Status" type="u" plural="SASL_Statuses">
+ <tp:enumvalue suffix="Not_Started" value="0">
+ <tp:docstring>
+ The initial state. The Handler SHOULD either
+ call <tp:member-ref>AbortSASL</tp:member-ref>, or connect to the
+ <tp:member-ref>NewChallenge</tp:member-ref> signal then call
+ <tp:member-ref>StartMechanism</tp:member-ref> or
+ <tp:member-ref>StartMechanismWithData</tp:member-ref>.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="In_Progress" value="1">
+ <tp:docstring>
+ The challenge/response exchange is in progress. The Handler SHOULD
+ call either <tp:member-ref>Respond</tp:member-ref> or
+ <tp:member-ref>AcceptSASL</tp:member-ref> exactly once per emission
+ of <tp:member-ref>NewChallenge</tp:member-ref>, or call
+ <tp:member-ref>AbortSASL</tp:member-ref> at any time.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Server_Succeeded" value="2">
+ <tp:docstring>
+ The server has indicated successful authentication, and the
+ connection manager is waiting for confirmation from the Handler.
+ The Handler must call either <tp:member-ref>AcceptSASL</tp:member-ref> or
+ <tp:member-ref>AbortSASL</tp:member-ref> to indicate whether it
+ considers authentication to have been successful.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Client_Accepted" value="3">
+ <tp:docstring>
+ The Handler has indicated successful authentication, and the
+ connection manager is waiting for confirmation from the server.
+ The state will progress to either Succeeded or Server_Failed when
+ confirmation is received.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Succeeded" value="4">
+ <tp:docstring>
+ Everyone is happy (the server sent success, and the client has called
+ <tp:member-ref>AcceptSASL</tp:member-ref>). Connection to the server
+ will proceed as soon as this state is reached. The Handler SHOULD
+ call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+ to close the channel.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Server_Failed" value="5">
+ <tp:docstring>
+ The server has indicated an authentication failure.
+ If <tp:member-ref>CanTryAgain</tp:member-ref> is true,
+ the client may try to authenticate again, by calling
+ <tp:member-ref>StartMechanism</tp:member-ref> or
+ <tp:member-ref>StartMechanismWithData</tp:member-ref> again.
+ Otherwise, it should give up completely, by calling <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+ on the channel.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Client_Failed" value="6">
+ <tp:docstring>
+ The client has indicated an authentication failure. The
+ possible actions are the same as for Server_Failed.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_SMS.xml b/qt4/spec/Channel_Interface_SMS.xml
new file mode 100644
index 000000000..4cfe3f2f8
--- /dev/null
+++ b/qt4/spec/Channel_Interface_SMS.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_SMS"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008–2010 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.SMS">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Type.Text"/>
+ <tp:added version='0.19.12'>Imported from
+ rtcom-telepathy-glib, with the unused properties removed and the
+ documentation tidied up.</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface contains SMS-specific properties for text
+ channels.</p>
+
+ <p>The presence of this interface on a channel does not imply that
+ messages will be delivered via SMS.</p>
+
+ <p>This interface MAY appear in the
+ <tp:dbus-ref namespace="ofdT.Channel">Interfaces</tp:dbus-ref> property
+ of channels where <tp:member-ref>SMSChannel</tp:member-ref> would be
+ immutable and false. It SHOULD appear on channels where
+ <tp:member-ref>SMSChannel</tp:member-ref> is immutable and true, and
+ also on channels where <tp:member-ref>SMSChannel</tp:member-ref> is
+ mutable (i.e. channels that might fall back to sending SMS at any
+ time, such as on MSN).</p>
+
+ <h4>Handler filters</h4>
+
+ <p>A handler for class 0 SMSes should advertise the following filter:</p>
+
+ <blockquote><code>
+{ ...<tp:dbus-ref namespace='ofdT.Channel'>ChannelType</tp:dbus-ref>:
+ ...<tp:dbus-ref namespace='ofdT.Channel.Type'>Text</tp:dbus-ref>,<br/>
+  ...<tp:dbus-ref namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>:
+ <tp:type>Handle_Type</tp:type>_Contact,<br/>
+  ...<tp:dbus-ref namespace='ofdT.Channel.Interface'>SMS.Flash</tp:dbus-ref>:
+ True,<br/>
+}</code></blockquote>
+
+ <p>It should also set its <tp:dbus-ref
+ namespace='ofdT.Client.Handler'>BypassApproval</tp:dbus-ref> property
+ to <code>True</code>, so that it is invoked immediately for new
+ channels.</p>
+ </tp:docstring>
+
+ <property name="Flash" tp:name-for-bindings="Flash"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <code>True</code>, then this channel is exclusively for
+ receiving class 0 SMSes (and no SMSes can be sent using <tp:dbus-ref
+ namespace='ofdT.Channel.Interface.Messages'>SendMessage</tp:dbus-ref>
+ on this channel). If <code>False</code>, no incoming class 0 SMSes
+ will appear on this channel.</p>
+
+ <p>This property is immutable (cannot change), and therefore SHOULD
+ appear wherever immutable properties are reported, e.g. <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests"
+ >NewChannels</tp:dbus-ref> signals.</p>
+
+ <tp:rationale>
+ <p>Class 0 SMSes should be displayed immediately to the user, and
+ need not be saved to the device memory unless the user explicitly
+ chooses to do so. This is unlike “normal”, class 1 SMSes, which
+ must be stored, but need not be shown immediately in their entirity
+ to the user.</p>
+
+ <p>Separating class 0 SMSes into their own channel with this
+ immutable property allows them to be dispatched to a different
+ <tp:dbus-ref namespace='ofdT.Client'>Handler</tp:dbus-ref>—which
+ would include this property in its <tp:dbus-ref
+ namespace='ofdT.Client.Handler'
+ >HandlerChannelFilter</tp:dbus-ref>—avoiding the normal Text
+ channel handler having to decide for each message whether it should
+ be displayed to the user immediately or handled normally.</p>
+
+ <p>Currently, no mechanism is defined for <em>sending</em> class 0
+ SMSes. It seems reasonable to support specifying the class of an
+ outgoing SMS in its header <tp:type>Message_Part</tp:type>, rather
+ than requiring the UI to request a special channel for such SMSes;
+ hence, we define here that channels with Flash set to
+ <code>True</code> are read-only.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="SMSChannel"
+ tp:name-for-bindings="SMS_Channel"
+ type="b"
+ access="read" tp:requestable="yes" tp:immutable="sometimes">
+ <tp:added version="0.21.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If TRUE, messages sent and received on this channel are transmitted
+ via SMS.</p>
+
+ <p>If this property is included in the channel request, the
+ Connection Manager MUST return an appropriate channel (i.e. if TRUE
+ the channel must be for SMSes, if FALSE it must not), or else fail
+ to provide the requested channel with the
+ <tp:error-ref>NotCapable</tp:error-ref>
+ error.</p>
+
+ <p>For example, to explicitly request an SMS channel to a contact.
+ You might construct a channel request like:</p>
+
+ <blockquote><pre>{
+ Channel.Type: Channel.Type.Text,
+ Channel.TargetHandleType: Handle_Type_Contact,
+ Channel.TargetID: escher.cat,
+ Channel.Interface.SMS.SMSChannel: True,
+}</pre></blockquote>
+
+ <tp:rationale>
+ Some protocols allow us to send SMSes to a remote contact, without
+ knowing the phone number to which those SMSes will be sent. This
+ provides a mechanism to request such channels.
+ </tp:rationale>
+
+ <p>If this property is not included in the channel request, the
+ Connection Manager MAY return an SMS channel if that is the most
+ appropriate medium (i.e. if the channel target is a phone
+ number).</p>
+
+ <tp:rationale>
+ To some types of identifiers (i.e. phone numbers) it only makes
+ sense to return an SMS channel, this is what happens currently with
+ telepathy-ring. We don't want to break this behaviour when we are
+ not explicit about the type of channel we want. Alternatively, for
+ protocols where there is an SMS fallback for IM messages, it's
+ possible that we don't care what sort of channel we get, and simply
+ want notification of the transport.
+ </tp:rationale>
+
+ <p>Some protocols have a fallback to deliver IM messages via SMS.
+ On these protocols, the Connection Manager SHOULD set the property
+ value as appropriate, and notify its change with
+ <tp:member-ref>SMSChannelChanged</tp:member-ref>.</p>
+
+ <tp:rationale>
+ Protocols such as MSN can fall back to delivering IM messages via
+ SMS. Where possible we want clients to be able to inform the user
+ that their messages are going to be redirected to the remote
+ contact's phone.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="SMSChannelChanged"
+ tp:name-for-bindings="SMS_Channel_Changed">
+ <tp:added version="0.21.7"/>
+ <arg name="SMSChannel" type="b">
+ <tp:docstring>
+ The new value for <tp:member-ref>SMSChannel</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ This signal indicates a change in the
+ <tp:member-ref>SMSChannel</tp:member-ref> property.
+ </tp:docstring>
+ </signal>
+ </interface>
+</node>
diff --git a/qt4/spec/Channel_Interface_Securable.xml b/qt4/spec/Channel_Interface_Securable.xml
new file mode 100644
index 000000000..d9d971394
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Securable.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Securable"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2010 Collabora Ltd.</tp:copyright>
+
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Securable">
+ <tp:added version="0.21.5">as stable API</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface exists to expose security information about
+ <tp:dbus-ref namespace="ofdT">Channel</tp:dbus-ref>s. The two
+ properties are sometimes immutable and can be used to make
+ decisions on how cautious to be about transferring sensitive
+ data. The special case of <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channels is one example of where the two properties are
+ immutable.</p>
+
+ <p>For example, clients MAY use these properties to decide
+ whether the <code>PLAIN</code> mechanism is acceptable for a
+ <tp:dbus-ref
+ namespace="ofdT.Channel.Interface">SASLAuthentication</tp:dbus-ref>
+ channel.</p>
+ </tp:docstring>
+
+ <property name="Encrypted"
+ tp:name-for-bindings="Encrypted" type="b"
+ access="read" tp:immutable="sometimes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if this channel occurs over an encrypted
+ connection. This <strong>does not</strong> imply that steps
+ have been taken to avoid man-in-the-middle attacks.</p>
+
+ <tp:rationale>
+ <p>For future support for <a
+ href="http://tools.ietf.org/html/rfc5056">RFC 5056 Channel
+ Binding</a> it is desirable to be able to use some SASL
+ mechanisms over an encrypted connection to an unverified peer,
+ which can prove that it is the desired destination during
+ the SASL negotiation.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Verified"
+ tp:name-for-bindings="Verified" type="b"
+ access="read" tp:immutable="sometimes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if this channel occurs over a connection that is
+ protected against tampering, and has been verified to be with
+ the desired destination: for instance, one where TLS was
+ previously negotiated, and the TLS certificate has been
+ verified against a configured certificate authority or
+ accepted by the user.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Service_Point.xml b/qt4/spec/Channel_Interface_Service_Point.xml
new file mode 100644
index 000000000..787397b20
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Service_Point.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Service_Point" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2010 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2005-2010 Collabora Ltd </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.ServicePoint">
+ <tp:added version="0.19.7">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for channels
+ that can indicate when/if they are connected to some form
+ of service point. For example, when
+ dialing 9-1-1 in the US, a GSM modem/network will recognize that as
+ an emergency call, and inform higher levels of the stack that the
+ call is being handled by an emergency service. In this example,
+ the call is handled by a Public Safety Answering Point (PSAP) which is labeled
+ as "urn:service:sos". Other networks and protocols may handle this
+ differently while still using this interface.</p>
+
+ <p>Note that while the majority of examples given in this
+ documentation are for GSM calls, they could just as easily be
+ SIP calls, GSM SMS's, etc.</p>
+ </tp:docstring>
+
+ <property name="InitialServicePoint" tp:name-for-bindings="Initial_Service_Point"
+ type="(us)" tp:type="Service_Point" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This property is used to indicate that the channel target is a
+ well-known service point. Please note that the CM (or lower layers
+ of the stack or network) may forward the connection to other other
+ service points, which the CM SHOULD indicate via
+ <tp:member-ref>ServicePointChanged</tp:member-ref>
+ signal.</p>
+
+ <p>This property SHOULD be set for channel requests that are
+ specifically targeting service points.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CurrentServicePoint" tp:name-for-bindings="Current_Service_Point"
+ type="(us)" tp:type="Service_Point" access="read">
+ <tp:docstring>
+ The service point that the channel is connected to. If the channel is
+ not connected to a service point, the CM MUST set the
+ <tp:type>Service_Point_Type</tp:type> field to None; for instance,
+ this will be the case for ordinary calls.
+ </tp:docstring>
+ </property>
+
+ <signal name="ServicePointChanged" tp:name-for-bindings="Service_Point_Changed">
+ <tp:docstring>
+ <p>Emitted when a channel changes the service point that it's connected to. This
+ might be a new call being connected to a service, a call connected to
+ a service being routed to a different service
+ (ie, an emergency call being routed from a generic emergency PSAP to
+ a poison control PSAP), or any number of other things.</p>
+
+ <p>Note that this should be emitted as soon as the CM has been notified
+ of the switch, and has updated its internal state. The CM MAY still
+ be in the process of connecting to the new service point.</p>
+ </tp:docstring>
+
+ <arg name="Service_Point" type="(us)" tp:type="Service_Point">
+ <tp:docstring>
+ The new service point that is being used.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Splittable.xml b/qt4/spec/Channel_Interface_Splittable.xml
new file mode 100644
index 000000000..760c13406
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Splittable.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Splittable"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Channel.Interface.Splittable.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for channels that can be made conceptually part of a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Conference</tp:dbus-ref>, and can then be detached from that
+ conference.</p>
+
+ <tp:rationale>
+ <p>This interface addresses part of freedesktop.org <a
+ href="http://bugs.freedesktop.org/show_bug.cgi?id=24906">bug
+ #24906</a> (GSM-compatible conference calls). GSM is currently
+ the only protocol known to implement this; PBXs might implement
+ it too.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <method name="Split"
+ tp:name-for-bindings="Split">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that this channel is removed from any
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Conference</tp:dbus-ref> of which it is a part.</p>
+
+ <p>This implies that the media streams within the conference are put on
+ hold and the media streams within the member channel leaving the
+ conference are unheld.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ This channel isn't in a conference.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This channel is in a conference but can't currently be split away
+ from it.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
diff --git a/qt4/spec/Channel_Interface_Transfer.xml b/qt4/spec/Channel_Interface_Transfer.xml
new file mode 100644
index 000000000..02591c1d1
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Transfer.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Transfer" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Transfer"
+ tp:causes-havoc='not well-tested'>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <method name="Transfer">
+ <arg direction="in" name="Member" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the member to transfer
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Destination" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the destination contact
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that the given channel member instead connects to a different
+ contact ID.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+ <tp:docstring>
+ An interface for channels where you may request that one of the members
+ connects to somewhere else instead.
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Interface_Tube.xml b/qt4/spec/Channel_Interface_Tube.xml
new file mode 100644
index 000000000..858a15dd9
--- /dev/null
+++ b/qt4/spec/Channel_Interface_Tube.xml
@@ -0,0 +1,258 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Interface_Tube" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Interface.Tube">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A <i>tube</i> is a mechanism for arbitrary data transfer between
+ two or more IM users, used to allow applications on the users'
+ systems to communicate without having to establish network
+ connections themselves. Currently, two types of tube exist:
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Channel.Type.DBusTube</tp:dbus-ref> and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Channel.Type.StreamTube</tp:dbus-ref>. This interface contains
+ the properties, signals and methods common to both types of tube;
+ you can only create channels of a specific tube type, not of this
+ type. A tube channel contains exactly one tube; if you need several
+ tubes, you have to create several tube channels.</p>
+
+ <p>Tube channels can be requested for <tp:type>Handle_Type</tp:type>
+ Contact (for 1-1 communication) or Room (to communicate with others in
+ the room simultaneously).</p>
+
+ <p>As an exception to the usual handling of capabilities, connection managers
+ for protocols with capability discovery (such as XMPP) SHOULD advertise the
+ capability representing each Tube type that they support
+ (<tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.Type.DBusTube</tp:dbus-ref> and/or
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.Type.StreamTube</tp:dbus-ref>)
+ even if no client has indicated via
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref>
+ that such a tube is supported. They SHOULD also allow clients to offer tubes with any
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.StreamTube">Service</tp:dbus-ref> or
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.DBusTube">ServiceName</tp:dbus-ref>
+ to any contact which supports the corresponding tube capability.</p>
+
+ <tp:rationale>
+ <p>This lowers the barrier to entry for those writing new tube
+ applications, and preserves interoperability with older versions of
+ the Telepathy stack which did not support rich capabilities.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <property name="Parameters" type="a{sv}" tp:type="String_Variant_Map"
+ access="read" tp:name-for-bindings="Parameters">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Each tube has a dictionary of arbitrary parameters. Parameters are
+ commonly used to bootstrap legacy protocols where you can't
+ negotiate parameters in-band. The allowable keys,
+ types and values are defined by the service, but connection managers
+ must support the value being a string (D-Bus type <tt>'s'</tt>),
+ array of bytes (D-Bus type <tt>'ay'</tt>), unsigned integer (D-Bus
+ type <tt>'u'</tt>), integer (D-Bus type <tt>'i'</tt>) and boolean
+ (D-Bus type <tt>'b'</tt>).</p>
+
+ <p>When the tube is offered, the parameters are transmitted with the
+ offer and appear as a property of the incoming tube for other
+ participants.</p>
+
+ <p>For example, a stream tube for <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.StreamTube">Service</tp:dbus-ref>
+ <tt>"smb"</tt> (<cite>Server Message Block over TCP/IP</cite>) might
+ use the following properties, as defined in <a
+ href="http://www.dns-sd.org/ServiceTypes.html">DNS SRV (RFC 2782)
+ Service Types</a>:</p>
+
+ <pre>
+{ 'u': 'some-username',
+ 'p': 'top-secret-password',
+ 'path': '/etc/passwd',
+}</pre>
+
+ <p>When requesting a tube with
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>,
+ this property MUST NOT be included in the request; instead, it is set
+ when <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamTube.Offer</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">DBusTube.Offer</tp:dbus-ref>
+ (as appropriate) is called. Its value is undefined until the tube is
+ offered; once set, its value MUST NOT change.</p>
+
+ <p>When receiving an incoming tube, this property is immutable and so advertised in the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="State" type="u" tp:type="Tube_Channel_State" access="read"
+ tp:name-for-bindings="State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>State of the tube in this channel.</p>
+
+ <p>When requesting a tube with
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>,
+ this property MUST NOT be included in the request.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Tube_Channel_State" type="u">
+ <tp:enumvalue suffix="Local_Pending" value="0">
+ <tp:docstring>
+ The initiator offered the tube. The tube is waiting to be
+ accepted/closed locally. If the client accepts the tube, the tube's
+ state will be Open.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Remote_Pending" value="1">
+ <tp:docstring>
+ The tube is waiting to be accepted/closed remotely. If the
+ recipient accepts the tube, the tube's state will be Open.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Open" value="2">
+ <tp:docstring>
+ The initiator offered the tube and the recipient accepted it. The
+ tube is open for traffic. The tube's state stays in this state until
+ it is closed.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Not_Offered" value="3">
+ <tp:docstring>
+ The tube channel has been requested but the tube is not yet offered.
+ The client should offer the tube to the recipient and the tube's
+ state will be Remote_Pending. The method used to offer the tube
+ depends on the tube type.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <signal name="TubeChannelStateChanged"
+ tp:name-for-bindings="Tube_Channel_State_Changed">
+ <tp:docstring>
+ Emitted when the state of the tube channel changes. Valid state
+ transitions are documented with <tp:type>Tube_Channel_State</tp:type>.
+ </tp:docstring>
+ <arg name="State" type="u" tp:type="Tube_Channel_State">
+ <tp:docstring>
+ The new state of the tube.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Socket_Address_Type" type="u">
+ <tp:enumvalue suffix="Unix" value="0">
+ <tp:docstring>
+ A Unix socket. The address variant contains a byte-array, signature 'ay',
+ containing the path of the socket.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Abstract_Unix" value="1">
+ <tp:docstring>
+ An abstract Unix socket. The address variant contains a byte-array,
+ signature 'ay', containing the path of the socket including the
+ leading null byte.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="IPv4" value="2">
+ <tp:docstring>
+ An IPv4 socket. The address variant contains a Socket_Address_IPv4,
+ i.e. a structure with signature (sq)
+ in which the string is an IPv4 dotted-quad address literal
+ (and must not be a DNS name), while the 16-bit unsigned integer is
+ the port number.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="IPv6" value="3">
+ <tp:docstring>
+ An IPv6 socket. The address variant contains a Socket_Address_IPv6,
+ i.e. a structure with signature (sq)
+ in which the string is an IPv6 address literal as specified in
+ RFC2373 (and must not be a DNS name), while the 16-bit unsigned
+ integer is the port number.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ </tp:enum>
+
+ <tp:enum name="Socket_Access_Control" type="u"
+ array-name="Socket_Access_Control_List">
+ <tp:enumvalue suffix="Localhost" value="0">
+ <tp:docstring>
+ The IP or Unix socket can be accessed by any local user (e.g.
+ a Unix socket that accepts all local connections, or an IP socket
+ listening on 127.0.0.1 (or ::1) or rejecting connections not from
+ that address). The associated variant must be ignored.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Port" value="1">
+ <tp:docstring>
+ May only be used on IP sockets. The associated variant must contain
+ a struct Socket_Address_IPv4 (or Socket_Address_IPv6)
+ containing the string form of an IP address of the appropriate
+ version, and a port number. The socket can only be accessed if the
+ connecting process has that address and port number; all other
+ connections will be rejected.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Netmask" value="2">
+ <tp:deprecated version="0.17.25">This has never been implemented.
+ If you want to share a service to your whole LAN, Telepathy is
+ not the way to do it.</tp:deprecated>
+ <tp:docstring>
+ May only be used on IP sockets. The associated variant must contain
+ a struct Socket_Netmask_IPv4 (or Socket_Netmask_IPv6) with
+ signature (sy), containing the string form of an
+ IP address of the appropriate version, and a prefix length "n".
+ The socket can only be accessed if the first n bits of the
+ connecting address match the first n bits of the given address.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Credentials" value="3">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>May only be used on UNIX sockets.
+ The connecting process must send a byte when
+ it first connects, which is not considered to be part of the data
+ stream. If the operating system uses sendmsg() with SCM_CREDS or
+ SCM_CREDENTIALS to pass credentials over sockets, the connecting
+ process must do so if possible; if not, it must still send the
+ byte.</p>
+
+ <p>The listening process will disconnect the connection unless it
+ can determine by OS-specific means that the connecting process
+ has the same user ID as the listening process.</p>
+
+ <p>The associated variant must be ignored.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ </interface>
+
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Request.xml b/qt4/spec/Channel_Request.xml
new file mode 100644
index 000000000..fbb67925e
--- /dev/null
+++ b/qt4/spec/Channel_Request.xml
@@ -0,0 +1,303 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Request"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008–2011 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008–2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ MA 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ChannelRequest">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel request is an object in the <tp:dbus-ref
+ namespace='ofdT'>ChannelDispatcher</tp:dbus-ref> representing
+ an ongoing request for some channels to be created or found. They are
+ created by methods such as <tp:dbus-ref
+ namespace='ofdT.ChannelDispatcher'>CreateChannel</tp:dbus-ref>. There
+ can be any number of ChannelRequest objects at the same time.</p>
+
+ <p>Its well-known bus name is the same as that of the ChannelDispatcher,
+ <code>"org.freedesktop.Telepathy.ChannelDispatcher"</code>.</p>
+
+ <tp:rationale>
+ <p>See
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelDispatcher.CreateChannel</tp:dbus-ref>
+ for rationale for ChannelRequest being a separate object.</p>
+ </tp:rationale>
+
+ <p>A channel request can be cancelled by any client (not just the one
+ that requested it). This means that the ChannelDispatcher will
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+ the resulting channel, or refrain from requesting it at all, rather
+ than dispatching it to a handler.</p>
+ </tp:docstring>
+
+ <property name="Account" tp:name-for-bindings="Account"
+ type="o" access="read">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ on which this request was made. This property cannot change.
+ </tp:docstring>
+ </property>
+
+ <property name="UserActionTime" tp:name-for-bindings="User_Action_Time"
+ type="x" tp:type="User_Action_Timestamp" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which user action occurred, or 0 if this channel
+ request is for some reason not involving user action.</p>
+
+ <p>This property is set when the channel request is created,
+ and can never change.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="PreferredHandler" tp:name-for-bindings="Preferred_Handler"
+ type="s" tp:type="DBus_Well_Known_Name" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Either the well-known bus name (starting with
+ <code>org.freedesktop.Telepathy.Client.</code>)
+ of the preferred handler for this
+ channel, or an empty string to indicate that any handler would be
+ acceptable.</p>
+
+ <p>This property is set when the channel request is created,
+ and can never change.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Requests" tp:name-for-bindings="Requests" type="aa{sv}"
+ tp:type="Qualified_Property_Value_Map[]"
+ access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of dictionaries containing desirable properties for
+ the channel or channels to be created.</p>
+
+ <tp:rationale>
+ <p>This is an array so that we could add a CreateChannels method in
+ future without redefining the API of ChannelRequest.</p>
+ </tp:rationale>
+
+ <p>This property is set when the channel request is created,
+ and can never change.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" access="read" tp:type="DBus_Interface[]">
+ <tp:docstring>
+ A list of the extra interfaces provided by this channel request.
+ This property cannot change.
+ </tp:docstring>
+ </property>
+
+ <method name="Proceed" tp:name-for-bindings="Proceed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Proceed with the channel request.</p>
+
+ <tp:rationale>
+ <p>The client that created this object calls this method
+ when it has connected signal handlers for
+ <tp:member-ref>Succeeded</tp:member-ref> and
+ <tp:member-ref>Failed</tp:member-ref>.</p>
+ </tp:rationale>
+
+ <p>Clients other than the client which created the ChannelRequest
+ MUST NOT call this method.</p>
+
+ <p>This method SHOULD return immediately; on success, the request
+ might still fail, but this will be indicated asynchronously
+ by the <tp:member-ref>Failed</tp:member-ref> signal.</p>
+
+ <p>Proceed cannot fail, unless clients have got the life-cycle
+ of a ChannelRequest seriously wrong (e.g. a client calls this
+ method twice, or a client that did not create the ChannelRequest
+ calls this method). If it fails, clients SHOULD assume that the
+ whole ChannelRequest has become useless.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This method has already been called, so it is no longer
+ available. Stop calling it.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Cancel" tp:name-for-bindings="Cancel">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Cancel the channel request. The precise effect depends on the
+ current progress of the request.</p>
+
+ <p>If the connection manager has not already been asked to create
+ a channel, then <tp:member-ref>Failed</tp:member-ref> is emitted
+ immediately, and the channel request is removed.</p>
+
+ <p>If the connection manager has already been asked to create a
+ channel but has not produced one yet (e.g. if <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ has been called, but has not yet returned), then the
+ ChannelDispatcher will remember that the request has been cancelled.
+ When the channel appears, it will be closed (if it was newly
+ created and can be closed), and will not be dispatched to a
+ handler.</p>
+
+ <p>If the connection manager has already returned a channel, but the
+ channel has not yet been dispatched to a handler
+ then the channel dispatcher will not dispatch that
+ channel to a handler. If the channel was newly created for this
+ request, the channel dispatcher will close it with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>;
+ otherwise, the channel dispatcher will ignore it. In either case,
+ <tp:member-ref>Failed</tp:member-ref> will be emitted when processing
+ has been completed.</p>
+
+ <p>If <tp:member-ref>Failed</tp:member-ref> is emitted in response to
+ this method, the error SHOULD be
+ <code>org.freedesktop.Telepathy.Error.Cancelled</code>.</p>
+
+ <p>If the channel has already been dispatched to a handler, then
+ it's too late to call this method, and the channel request will
+ no longer exist.</p>
+ </tp:docstring>
+ </method>
+
+ <signal name="Failed" tp:name-for-bindings="Failed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The channel request has failed. It is no longer present,
+ and further methods must not be called on it.</p>
+ </tp:docstring>
+
+ <arg name="Error" type="s" tp:type="DBus_Error_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of a D-Bus error. This can come from various sources,
+ including the error raised by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>,
+ or an error generated
+ to represent failure to establish the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Message" type="s">
+ <tp:docstring>
+ If the first argument of the D-Bus error message was a string,
+ that string. Otherwise, an empty string.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="Succeeded" tp:name-for-bindings="Succeeded">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The channel request has succeeded. It is no longer present,
+ and further methods must not be called on it.</p>
+ </tp:docstring>
+ </signal>
+
+ <property name="Hints" tp:name-for-bindings="Hints"
+ type="a{sv}" access="read">
+ <tp:added version="0.21.5"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary of metadata provided by the channel
+ requester, which the handler and other clients MAY choose to
+ interpret. Currently no standard keys are defined; clients MAY
+ choose to use platform-specific keys for their own purposes, but MUST
+ ignore unknown keys and MUST cope with expected keys being
+ missing. Clients SHOULD namespace hint names by having them
+ start with a reversed domain name, in the same way as D-Bus
+ interface names.</p>
+
+ <tp:rationale>This property might be used to pass a contact ID for a
+ telephone number shared between two contacts from the address book to
+ the call UI, so that if you try to call “Mum”, the call UI knows this
+ rather than having to guess or show “Calling Mum or Dad”. The format
+ of these contact IDs would be platform-specific, so we leave the
+ definition of the dictionary entry up to the platform in question.
+ But third-party channel requesters might not include the contact ID,
+ so the call UI has to be able to deal with it not being
+ there.</tp:rationale>
+
+ <p>The channel dispatcher does not currently interpret any of these
+ hints: they are solely for communication between cooperating
+ clients. If hints that do affect the channel dispatcher are added in
+ future, their names will start with an appropriate reversed domain
+ name (e.g. <code>org.freedesktop.Telepathy</code> for hints defined
+ by this specification, or an appropriate vendor name for third-party
+ plugins).</p>
+
+ <p>This property may be set when the channel request is created, and
+ can never change. Since it is immutable, it SHOULD be included in the
+ dictionary of properties passed to <tp:dbus-ref
+ namespace="ofdT.Client.Interface.Requests">AddRequest</tp:dbus-ref>
+ by the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="SucceededWithChannel" tp:name-for-bindings="Succeeded_With_Channel">
+ <tp:added version="0.21.5"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Variant of the <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">Succeeded</tp:dbus-ref> signal
+ allowing to get the channel which has been created.</p>
+
+ <p>This signal MUST be emitted if the
+ <tp:dbus-ref namespace="ofdT">ChannelDispatcher</tp:dbus-ref>'s
+ <tp:dbus-ref
+ namespace="ofdT.ChannelDispatcher">SupportsRequestHints</tp:dbus-ref>
+ property is true. If supported, it MUST be emitted before
+ the <tp:member-ref>Succeeded</tp:member-ref> signal.</p>
+ </tp:docstring>
+
+ <arg name="Connection" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The Connection owning the channel.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Connection_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A subset of the Connection's properties, currently unused.
+ This parameter may be used in future.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channel" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The channel which has been created.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channel_Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same immutable properties of the Channel that would appear
+ in a <tp:dbus-ref namespace="ofdT.Connection.Interface.Requests"
+ >NewChannels</tp:dbus-ref> signal.</p>
+ </tp:docstring>
+ </arg>
+
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Request_Future.xml b/qt4/spec/Channel_Request_Future.xml
new file mode 100644
index 000000000..d75c7e0ce
--- /dev/null
+++ b/qt4/spec/Channel_Request_Future.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Request_Future"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.ChannelRequest.FUTURE"
+ tp:causes-havoc="a staging area for future Channel functionality">
+
+ <tp:requires interface="org.freedesktop.Telepathy.ChannelRequest"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface contains functionality which we intend to incorporate
+ into the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ interface in future. It should be considered to
+ be conceptually part of the core ChannelRequest interface, but without
+ API or ABI guarantees.</p>
+ </tp:docstring>
+
+ <property name="Hints" tp:name-for-bindings="Hints"
+ type="a{sv}" access="read">
+ <tp:added version="0.19.12">(as draft)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary of metadata provided by the channel
+ requester, which the handler and other clients MAY choose to
+ interpret. Currently no standard keys are defined; clients MAY
+ choose to use platform-specific keys for their own purposes, but MUST
+ ignore unknown keys and MUST cope with expected keys being
+ missing.</p>
+
+ <tp:rationale>This property might be used to pass a contact ID for a
+ telephone number shared between two contacts from the address book to
+ the call UI, so that if you try to call “Mum”, the call UI knows this
+ rather than having to guess or show “Calling Mum or Dad”. The format
+ of these contact IDs would be platform-specific, so we leave the
+ definition of the dictionary entry up to the platform in question.
+ But third-party channel requesters might not include the contact ID,
+ so the call UI has to be able to deal with it not being
+ there.</tp:rationale>
+
+ <p>The channel dispatcher will not interpret these hints: they are
+ solely for communication between cooperating clients.</p>
+
+ <tp:rationale>
+ <p>Any extra parameters that do affect the channel dispatcher should
+ be designed separately.</p>
+ </tp:rationale>
+
+ <p>This property may be set when the channel request is created, and
+ can never change. Since it is immutable, it SHOULD be included in the
+ dictionary of properties passed to <tp:dbus-ref
+ namespace="ofdT.Client.Interface.Requests">AddRequest</tp:dbus-ref>
+ by the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="SucceededWithChannel" tp:name-for-bindings="Succeeded_With_Channel">
+ <tp:added version="0.19.12">(as draft)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Variant of the <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">Succeeded</tp:dbus-ref> signal
+ allowing to get the channel which has been created.</p>
+ </tp:docstring>
+
+ <arg name="Connection" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The Connection owning the channel.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channel" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The channel which has been created.</p>
+ </tp:docstring>
+ </arg>
+
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Call.xml b/qt4/spec/Channel_Type_Call.xml
new file mode 100644
index 000000000..045d41693
--- /dev/null
+++ b/qt4/spec/Channel_Type_Call.xml
@@ -0,0 +1,1429 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Call" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009-2010 Nokia Corporation</tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.0">(draft 1)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for making audio and video calls. Call
+ channels supersede the old <tp:dbus-ref
+ namespace="ofdT.Channel.Type">StreamedMedia</tp:dbus-ref>
+ channel type. Call channels are much more flexible than its
+ predecessor and allow more than two participants.</p>
+
+ <p>Handlers are advised against executing all the media
+ signalling, codec and candidate negotiation themselves but
+ instead use a helper library such as <a
+ href="http://telepathy.freedesktop.org/doc/telepathy-farstream/">telepathy-farstream</a>
+ which when given a new Call channel will set up the
+ transports and codecs and create GStreamer pads which
+ can be added to the handler UI. This is useful as it means
+ the handler does not have to worry how exactly the
+ connection between the call participants is being made.</p>
+
+ <p>The <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>
+ properties in a Call channel refer to the contact that the
+ user initially called, or which contact initially called the
+ user. Even in a conference call, where there are multiple
+ contacts in the call, these properties refer to the
+ initial contact, who might have left the conference since
+ then. As a result, handlers should not rely on these
+ properties.</p>
+
+ <h4>Contents</h4>
+
+ <p><tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>
+ objects represent the actual media that forms the Call (for
+ example an audio content and a video content). Calls always
+ have one or more Content objects associated with them. As a
+ result, a new Call channel request MUST have either
+ <tp:member-ref>InitialAudio</tp:member-ref>=True, or
+ <tp:member-ref>InitialVideo</tp:member-ref>=True, or both,
+ as the Requestable Channel Classes will document.</p>
+
+ <p><tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> objects have
+ one or more stream associated with them. More information on
+ these streams and how to maniuplate them can be found on the
+ <tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>
+ interface page.</p>
+
+ <h4>Outgoing calls</h4>
+
+ <p>To make an audio-only call to a contact <tt>foo@example.com</tt>
+ handlers should call:</p>
+
+ <blockquote>
+ <pre>
+<tp:dbus-ref namespace="ofdT.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>({
+ ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>: 'foo@example.com',
+ ...<tp:member-ref>InitialAudio</tp:member-ref>: True,
+})</pre></blockquote>
+
+ <p>As always, <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref> may be used
+ in place of
+ <tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>
+ if the contact's handle is already known. To make an audio
+ and video call, the handler should also specify
+ <tp:member-ref>InitialVideo</tp:member-ref> The
+ connection manager SHOULD return a channel whose immutable
+ properties contain the local user as the <tp:dbus-ref
+ namespace="ofdT.Channel">InitiatorHandle</tp:dbus-ref>, the
+ remote contact as the <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref> =
+ <code>True</code> (indicating the call is outgoing).</p>
+
+ <p>After a new Call channel is requested, the
+ <tp:member-ref>CallState</tp:member-ref> property will be
+ <tp:type>Call_State</tp:type>_Pending_Initiator. As the local
+ user is the initiator, the call must be accepted by the handler
+ by calling the <tp:member-ref>Accept</tp:member-ref> method.
+ At this point, <tp:member-ref>CallState</tp:member-ref> changes
+ to <tp:type>Call_State</tp:type>_Pending_Receiver which signifies
+ that the call is ringing, waiting for the remote contact to
+ accept the call. All changes to
+ <tp:member-ref>CallState</tp:member-ref> property are signalled
+ using the <tp:member-ref>CallStateChanged</tp:member-ref>
+ signal.</p>
+
+ <p>When the call is accepted by the remote contact, the
+ <tp:member-ref>CallStateChanged</tp:member-ref> signal fires
+ again to show that <tp:member-ref>CallState</tp:member-ref> =
+ <tp:type>Call_State</tp:type>_Accepted.</p>
+
+ <p>At this point <a
+ href="http://telepathy.freedesktop.org/doc/telepathy-farstream/">telepathy-farstream</a>
+ will signal that a pad is available for the handler to show
+ in the user interface.</p>
+
+ <h5>Missed calls</h5>
+
+ <p>If the remote contact does not accept the call in time, then
+ the call can be terminated by the server. Note that this only
+ happens in some protocols. Most XMPP clients, for example, do
+ not do this and rely on the call initiator terminating the call.
+ A missed call is shown in a Call channel by the
+ <tp:member-ref>CallState</tp:member-ref> property changing to
+ <tp:type>Call_State</tp:type>_Ended, and the
+ <tp:member-ref>CallStateReason</tp:member-ref> property changing
+ to (remote contact,
+ <tp:type>Call_State_Change_Reason</tp:type>_No_Answer, "").</p>
+
+ <h5>Rejected calls</h5>
+
+ <p>If the remote contact decides he or she does not feel like
+ talking to the local user, he or she can reject his or her
+ incoming call. This will be shown in the Call channel by
+ <tp:member-ref>CallState</tp:member-ref> changing to
+ <tp:type>Call_State</tp:type>_Ended and the
+ <tp:member-ref>CallStateReason</tp:member-ref> property
+ changing to (remote contact,
+ <tp:type>Call_State</tp:type>_Change_Reason_User_Requested,
+ "org.freedesktop.Telepathy.Error.Rejected").</p>
+
+ <h4>Incoming calls</h4>
+
+ <p>When an incoming call occurs, something like the following
+ <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal will occur:</p>
+
+ <blockquote>
+ <pre>
+<tp:dbus-ref namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref>([
+ /org/freedesktop/Telepathy/Connection/foo/bar/foo_40bar_2ecom/CallChannel,
+ {
+ ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetID</tp:dbus-ref>: 'foo@example.com',
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandle</tp:dbus-ref>: 42,
+ ...<tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref>: False,
+ ...<tp:member-ref>InitialAudio</tp:member-ref>: True,
+ ...<tp:member-ref>InitialVideo</tp:member-ref>: True,
+ ...<tp:member-ref>InitialAudioName</tp:member-ref>: "audio",
+ ...<tp:member-ref>InitialVideoName</tp:member-ref>: "video",
+ ...<tp:member-ref>MutableContents</tp:member-ref>: True,
+ }])</pre></blockquote>
+
+ <p>The <tp:member-ref>InitialAudio</tp:member-ref> and
+ <tp:member-ref>InitialVideo</tp:member-ref> properties show that
+ the call has been started with two contents: one for audio
+ streaming and one for video streaming. The
+ <tp:member-ref>InitialAudioName</tp:member-ref> and
+ <tp:member-ref>InitialVideoName</tp:member-ref> properties also
+ show that the aforementioned audio and video contents have names
+ "audio" and "video".</p>
+
+ <p>Once the handler has notified the local user that there is an
+ incoming call waiting for acceptance, the handler should call
+ <tp:member-ref>SetRinging</tp:member-ref> to let the CM know.
+ The new channel should also be given to telepathy-farstream to
+ work out how the two participants will connect together.
+ telepathy-farstream will call the appropriate methods on the call's
+ <tp:dbus-ref namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref>s
+ to negotiate codecs and transports.</p>
+
+ <p>To pick up the call, the handler should call
+ <tp:member-ref>Accept</tp:member-ref>. The
+ <tp:member-ref>CallState</tp:member-ref> property changes to
+ <tp:type>Call_State</tp:type>_Accepted and once media is
+ being transferred, telepathy-farstream will notify the
+ handler of a new pad to be shown to the local user in the
+ UI</p>
+
+ <p>To reject the call, the handler should call the
+ <tp:member-ref>Hangup</tp:member-ref> method. The
+ <tp:member-ref>CallState</tp:member-ref> property will change to
+ <tp:type>Call_State</tp:type>_Ended and the
+ <tp:member-ref>CallStateReason</tp:member-ref> property will
+ change to (self handle,
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Rejected").</p>
+
+ <h4>Ongoing calls</h4>
+
+ <h5>Adding and removing contents</h5>
+
+ <p>When a call is open, new contents can be added as long as the
+ CM supports it. The
+ <tp:member-ref>MutableContents</tp:member-ref> property will let
+ the handler know whether further contents can be added or
+ existing contents removed. An example of this is starting a
+ voice call between a contact and then adding a video content.
+ To do this, the should call
+ <tp:member-ref>AddContent</tp:member-ref> like this:</p>
+
+ <blockquote>
+ <pre><tp:member-ref>AddContent</tp:member-ref>("video",
+ <tp:type>Media_Stream_Type</tp:type>_Video)</pre>
+ </blockquote>
+
+ <p>Assuming no errors, the new video content will be added to
+ the call. telepathy-farstream will pick up the new content and
+ perform the transport and codec negotiation automatically.
+ telpathy-farstream will signal when the video is ready to
+ show in the handler's user interface.</p>
+
+ <p>A similar method is used for removing contents from a call,
+ except that the <tp:dbus-ref
+ namespace="ofdT.Call.Content.DRAFT">Remove</tp:dbus-ref> method
+ is on the <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> object.</p>
+
+ <h5>Ending the call</h5>
+
+ <p>To end the call, the handler should call the
+ <tp:member-ref>Hangup</tp:member-ref> method. The
+ <tp:member-ref>CallState</tp:member-ref> property will change to
+ <tp:type>Call_State</tp:type>_Ended and
+ <tp:member-ref>CallStateReason</tp:member-ref> will change
+ to (self handle,
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Cancelled").</p>
+
+ <p>If the other participant hangs up first then the
+ <tp:member-ref>CallState</tp:member-ref> property will change to
+ <tp:type>Call_State</tp:type>_Ended and
+ <tp:member-ref>CallStateReason</tp:member-ref> will change
+ to (remote contact,
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Terminated").</p>
+
+ <h4>Multi-party calls</h4>
+
+ [TODO]
+
+ <h4>Call states</h4>
+
+ <p>There are many combinations of the
+ <tp:member-ref>CallState</tp:member-ref> and
+ <tp:member-ref>CallStateReason</tp:member-ref> properties which
+ mean different things. Here is a table to try to make these
+ meanings clearer:</p>
+
+ <table>
+ <tr>
+ <th rowspan="2"><tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref></th>
+ <th rowspan="2"><tp:member-ref>CallState</tp:member-ref></th>
+ <th colspan="3"><tp:member-ref>CallStateReason</tp:member-ref></th>
+ <th rowspan="2">Meaning</th>
+ </tr>
+ <tr>
+ <th>Actor</th>
+ <th>Reason</th>
+ <th>DBus_Reason</th>
+ </tr>
+ <!-- Pending_Initiator -->
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Pending_Initiator</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The outgoing call channel is waiting for the local user to call <tp:member-ref>Accept</tp:member-ref>.</td>
+ </tr>
+ <!-- Pending_Receiver -->
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Pending_Receiver</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The outgoing call is waiting for the remote contact to pick up the call.</td>
+ </tr>
+ <tr>
+ <td>False</td>
+ <td><tp:type>Call_State</tp:type>_Pending_Receiver</td>
+ <td>0</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_Unknown</td>
+ <td>""</td>
+ <td>The incoming call is waiting for the local user to call <tp:member-ref>Accept</tp:member-ref> on the call.</td>
+ </tr>
+ <!-- Accepted -->
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Accepted</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The remote contact accepted the outgoing call.</td>
+ </tr>
+ <tr>
+ <td>False</td>
+ <td><tp:type>Call_State</tp:type>_Accepted</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td>""</td>
+ <td>The local user accepted the incoming call.</td>
+ </tr>
+ <!-- Ended -->
+ <tr>
+ <td>True or False</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Self handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td><tp:error-ref>Cancelled</tp:error-ref></td>
+ <td>The local user hung up the incoming or outgoing call.</td>
+ </tr>
+ <tr>
+ <td>True or False</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td><tp:error-ref>Terminated</tp:error-ref></td>
+ <td>The remote contact hung up the incoming or outgoing call.</td>
+ </tr>
+ <tr>
+ <td>True</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_No_Answer</td>
+ <td>""</td>
+ <td>The outgoing call was not picked up and the call ended.</td>
+ </tr>
+ <tr>
+ <td>False</td>
+ <td><tp:type>Call_State</tp:type>_Ended</td>
+ <td>Remote contact handle</td>
+ <td><tp:type>Call_State_Change_Reason</tp:type>_User_Requested</td>
+ <td><tp:error-ref>PickedUpElsewhere</tp:error-ref></td>
+ <td>The incoming call was ended because it was picked up elsewhere.</td>
+ </tr>
+ </table>
+
+ <h4>Requestable channel classes</h4>
+
+ <p>The <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>
+ for <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channels
+ can be:</p>
+
+ <blockquote>
+ <pre>
+[( Fixed = { ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:member-ref>InitialVideo</tp:member-ref>: True
+ },
+ Allowed = [ ...<tp:member-ref>InitialVideoName</tp:member-ref>,
+ ...<tp:member-ref>InitialAudio</tp:member-ref>,
+ ...<tp:member-ref>InitialAudioName</tp:member-ref>
+ ]
+),
+( Fixed = { ...<tp:dbus-ref namespace="ofdT.Channel">ChannelType</tp:dbus-ref>: ...<tp:dbus-ref namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ ...<tp:member-ref>InitialAudio</tp:member-ref>: True
+ },
+ Allowed = [ ...<tp:member-ref>InitialAudioName</tp:member-ref>,
+ ...<tp:member-ref>InitialVideo</tp:member-ref>,
+ ...<tp:member-ref>InitialVideoName</tp:member-ref>
+ ]
+)]</pre></blockquote>
+
+ <p>Clients aren't allowed to make outgoing calls that have
+ neither initial audio nor initial video. Clearly, CMs
+ which don't support video should leave out the first class and
+ omit <tp:member-ref>InitialVideo</tp:member-ref> from the second
+ class, and vice versa for CMs without audio support.</p>
+
+ <p>Handlers should not close <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> channels
+ without first calling <tp:member-ref>Hangup</tp:member-ref> on
+ the channel. If a Call handler crashes, the <tp:dbus-ref
+ namespace="ofdT">ChannelDispatcher</tp:dbus-ref> will call
+ <tp:dbus-ref namespace="ofdT.Channel">Close</tp:dbus-ref> on the
+ channel which SHOULD also imply a call to
+ <tp:member-ref>Hangup</tp:member-ref>(<tp:type>Call_State_Change_Reason</tp:type>_User_Requested,
+ "org.freedesktop.Telepathy.Error.Terminated", "") before
+ actually closing the channel.</p>
+
+ </tp:docstring>
+
+ <method name="SetRinging" tp:name-for-bindings="Set_Ringing">
+ <tp:changed version="0.21.2">renamed from Ringing</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Indicate that the local user has been alerted about the incoming
+ call.</p>
+
+ <p>This method is only useful if the
+ channel's <tp:dbus-ref namespace="ofdT.Channel">Requested</tp:dbus-ref>
+ property is False, and
+ the <tp:member-ref>CallState</tp:member-ref> is
+ <tp:type>Call_State</tp:type>_Pending_Receiver (an incoming
+ call waiting on the local user to pick up). While this is
+ the case, this method SHOULD change the
+ <tp:member-ref>CallFlags</tp:member-ref> to include
+ <tp:type>Call_Flags</tp:type>_Locally_Ringing, and notify the
+ remote contact that the local user has been alerted (if the
+ protocol implements this); repeated calls to this method
+ SHOULD succeed, but have no further effect.</p>
+
+ <p>In all other states, this method SHOULD fail with the error
+ NotAvailable.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The call was <tp:dbus-ref namespace="ofdT.Channel"
+ >Requested</tp:dbus-ref>, so ringing does not make sense.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call is no longer in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For incoming calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver, accept the
+ incoming call; this changes the
+ <tp:member-ref>CallState</tp:member-ref> to
+ <tp:type>Call_State</tp:type>_Accepted.</p>
+
+ <p>For outgoing calls in state
+ <tp:type>Call_State</tp:type>_Pending_Initiator, actually
+ call the remote contact; this changes the
+ <tp:member-ref>CallState</tp:member-ref> to
+ <tp:type>Call_State</tp:type>_Pending_Receiver.</p>
+
+ <p>Otherwise, this method SHOULD fail with the error NotAvailable.</p>
+
+ <p>This method should be called exactly once per Call, by whatever
+ client (user interface) is handling the channel.</p>
+
+ <p>When this method is called, for each <tp:dbus-ref
+ namespace="ofdT.Call" >Content.DRAFT</tp:dbus-ref> whose
+ <tp:dbus-ref namespace="ofdT.Call.Content.DRAFT"
+ >Disposition</tp:dbus-ref> is
+ <tp:type>Call_Content_Disposition</tp:type>_Initial, any
+ streams where the <tp:dbus-ref
+ namespace="ofdT.Call.Stream.DRAFT">LocalSendingState</tp:dbus-ref>
+ is <tp:type>Sending_State</tp:type>_Pending_Send will be
+ moved to <tp:type>Sending_State</tp:type>_Sending as if
+ <tp:dbus-ref namespace="ofdT.Call.Stream.DRAFT"
+ >SetSending</tp:dbus-ref>(True) had been called.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call is not in one of the states where this method makes sense.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Hangup" tp:name-for-bindings="Hangup">
+ <tp:docstring>
+ Request that the call is ended. All contents will be removed
+ from the Call so that the
+ <tp:member-ref>Contents</tp:member-ref> property will be the
+ empty list.
+ </tp:docstring>
+
+ <arg direction="in" name="Reason"
+ type="u" tp:type="Call_State_Change_Reason">
+ <tp:docstring>
+ A generic hangup reason.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Detailed_Hangup_Reason"
+ type="s" tp:type="DBus_Error_Name">
+ <tp:docstring>
+ A more specific reason for the call hangup, if one is available, or
+ an empty string otherwise.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ A human-readable message to be sent to the remote contact(s).
+
+ <tp:rationale>
+ XMPP Jingle allows calls to be terminated with a human-readable
+ message.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The call has already been ended.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AddContent" tp:name-for-bindings="Add_Content">
+ <tp:docstring>
+ Request that a new <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> of type
+ Content_Type is added to the Call. Handlers should check the
+ value of the <tp:member-ref>MutableContents</tp:member-ref>
+ property before trying to add another content as it might not
+ be allowed.
+ </tp:docstring>
+ <arg direction="in" name="Content_Name" type="s">
+ <tp:docstring>
+ <p>The suggested name of the content to add.</p>
+
+ <tp:rationale>
+ The content name property should be meaningful, so should
+ be given a name which is significant to the user. The name
+ could be a localized "audio", "video" or perhaps include
+ some string identifying the source, such as a webcam
+ identifier.
+ </tp:rationale>
+
+ <p>If there is already a content with the same name as this
+ property then a sensible suffix should be added. For example,
+ if this argument is "audio" but a content of the same name
+ already exists, a sensible suffix such as " (1)" is appended
+ to name the new content "audio (1)". A further content with the
+ name "audio" would then be named "audio (2)".</p>
+
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Content_Type" type="u"
+ tp:type="Media_Stream_Type">
+ <tp:docstring>
+ The media stream type of the content to be added to the
+ call.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Content" type="o">
+ <tp:docstring>
+ Path to the newly-created <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Call.Content.DRAFT</tp:dbus-ref> object.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The media stream type given is invalid.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The media stream type requested is not implemented by the
+ CM.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable">
+ <tp:docstring>
+ The content type requested cannot be added to this
+ call. Examples of why this might be the case include
+ because a second video stream cannot be added, or a
+ content cannot be added when the content set isn't
+ mutable.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ContentAdded"
+ tp:name-for-bindings="Content_Added">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> is added to the call.</p>
+ </tp:docstring>
+ <arg name="Content" type="o">
+ <tp:docstring>
+ Path to the newly-created <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> object.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="ContentRemoved" tp:name-for-bindings="Content_Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> is removed from the call.</p>
+ </tp:docstring>
+ <arg name="Content" type="o">
+ <tp:docstring>
+ The <tp:dbus-ref namespace="ofdT.Call"
+ >Content.DRAFT</tp:dbus-ref> which was removed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Contents" type="ao" access="read"
+ tp:name-for-bindings="Contents">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The list of <tp:dbus-ref
+ namespace="ofdT.Call">Content.DRAFT</tp:dbus-ref> objects that
+ are part of this call. Change notification is via the
+ <tp:member-ref>ContentAdded</tp:member-ref> and
+ <tp:member-ref>ContentRemoved</tp:member-ref> signals.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum type="u" name="Call_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The state of a call, as a whole.</p>
+
+ <p>The allowed transitions are:</p>
+
+ <ul>
+ <li>Pending_Initiator → Pending_Receiver (for outgoing calls,
+ when <tp:member-ref>Accept</tp:member-ref> is called)</li>
+ <li>Pending_Receiver → Accepted (for incoming calls, when
+ <tp:member-ref>Accept</tp:member-ref> is called; for outgoing
+ calls to a contact, when the remote contact accepts the call;
+ for joining a conference call, when the local user successfully
+ joins the conference)</li>
+ <li>Accepted → Pending_Receiver (when transferred to another
+ contact)</li>
+ <li>any state → Ended (when the call is terminated normally, or
+ when an error occurs)</li>
+ </ul>
+
+ <p>Clients MAY consider unknown values from this enum to be an
+ error - additional values will not be defined after the Call
+ specification is declared to be stable.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value = "0">
+ <tp:docstring>
+ The call state is not known. This call state MUST NOT appear as a
+ value of the <tp:member-ref>CallState</tp:member-ref> property, but
+ MAY be used by client code to represent calls whose state is as yet
+ unknown.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending_Initiator" value = "1">
+ <tp:docstring>
+ The initiator of the call hasn't accepted the call yet. This state
+ only makes sense for outgoing calls, where it means that the local
+ user has not yet sent any signalling messages to the remote user(s),
+ and will not do so until <tp:member-ref>Accept</tp:member-ref> is
+ called.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending_Receiver" value = "2">
+ <tp:docstring>
+ The receiver (the contact being called) hasn't accepted the call yet.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Accepted" value = "3">
+ <tp:docstring>
+ The contact being called has accepted the call.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Ended" value = "4">
+ <tp:docstring>
+ The call has ended, either via normal termination or an error.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:flags name="Call_Flags" value-prefix="Call_Flag" type="u">
+ <tp:docstring>
+ A set of flags representing the status of the call as a whole,
+ providing more specific information than the
+ <tp:member-ref>CallState</tp:member-ref>. Many of these flags only make
+ sense in a particular state.
+ </tp:docstring>
+
+ <tp:flag suffix="Locally_Ringing" value="1">
+ <tp:docstring>
+ The local contact has been alerted about the call but has not
+ responded; if possible, the remote contact(s) have been informed of
+ this fact. This flag only makes sense on incoming calls in
+ state <tp:type>Call_State</tp:type>_Pending_Receiver. It SHOULD
+ be set when <tp:member-ref>SetRinging</tp:member-ref> is
+ called successfully, and unset when the state changes.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Queued" value="2">
+ <tp:docstring>
+ The contact is temporarily unavailable, and the call has been placed
+ in a queue (e.g. 182 Queued in SIP, or call-waiting in telephony).
+ This flag only makes sense on outgoing 1-1 calls in
+ state <tp:type>Call_State</tp:type>_Pending_Receiver. It SHOULD be
+ set or unset according to informational messages from other
+ contacts.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Locally_Held" value="4">
+ <tp:docstring>
+ The call has been put on hold by the local user, e.g. using
+ the <tp:dbus-ref namespace="ofdT.Channel.Interface"
+ >Hold</tp:dbus-ref> interface. This flag SHOULD only be set
+ if there is at least one Content, and all Contents are
+ locally held; it makes sense on calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver
+ or <tp:type>Call_State</tp:type>_Accepted.
+
+ <tp:rationale>
+ Otherwise, in transient situations where some but not all contents
+ are on hold, UIs would falsely indicate that the call as a whole
+ is on hold, which could lead to the user saying something they'll
+ regret, while under the impression that the other contacts can't
+ hear them!
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Forwarded" value="8">
+ <tp:docstring>
+ The initiator of the call originally called a contact other than the
+ current recipient of the call, but the call was then forwarded or
+ diverted. This flag only makes sense on outgoing calls, in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver or
+ <tp:type>Call_State</tp:type>_Accepted. It SHOULD be set or unset
+ according to informational messages from other contacts.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="In_Progress" value="16">
+ <tp:docstring>
+ Progress has been made in placing the outgoing call, but the
+ contact may not have been made aware of the call yet
+ (so the Ringing state is not appropriate). This corresponds to SIP's
+ status code 183 Session Progress, and could be used when the
+ outgoing call has reached a gateway, for instance.
+ This flag only makes sense on outgoing calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver, and SHOULD be set
+ or unset according to informational messages from servers, gateways
+ and other infrastructure.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Clearing" value="32">
+ <tp:docstring>
+ This flag only occurs when the CallState is Ended. The call with
+ this flag set has ended, but not all resources corresponding to the
+ call have been freed yet.
+
+ Depending on the protocol there might be some audible feedback while
+ the clearing flag is set.
+
+ <tp:rationale>
+ In calls following the ITU-T Q.931 standard there is a period of
+ time between the call ending and the underlying channel being
+ completely free for re-use.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Muted" value="64">
+ <tp:docstring>
+ The call has been muted by the local user, e.g. using the
+ <tp:dbus-ref namespace="ofdT.Call.Content.Interface"
+ >Mute.DRAFT</tp:dbus-ref> interface. This flag SHOULD only
+ be set if there is at least one Content, and all Contents
+ are locally muted; it makes sense on calls in state
+ <tp:type>Call_State</tp:type>_Pending_Receiver or
+ <tp:type>Call_State</tp:type>_Accepted.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <property name="CallStateDetails"
+ tp:name-for-bindings="Call_State_Details" type="a{sv}" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map used to provide optional extensible details for the
+ <tp:member-ref>CallState</tp:member-ref>,
+ <tp:member-ref>CallFlags</tp:member-ref> and/or
+ <tp:member-ref>CallStateReason</tp:member-ref>.</p>
+
+ <p>Well-known keys and their corresponding value types include:</p>
+
+ <dl>
+ <dt>hangup-message - s</dt>
+ <dd>An optional human-readable message sent when the call was ended,
+ corresponding to the Message argument to the
+ <tp:member-ref>Hangup</tp:member-ref> method. This is only
+ applicable when the call state is <tp:type>Call_State</tp:type>_Ended.
+ <tp:rationale>
+ XMPP Jingle can send such messages.
+ </tp:rationale>
+ </dd>
+
+ <dt>queue-message - s</dt>
+ <dd>An optional human-readable message sent when the local contact
+ is being held in a queue. This is only applicable when
+ <tp:type>Call_Flags</tp:type>_Queued is in the call flags.
+ <tp:rationale>
+ SIP 182 notifications can have human-readable messages attached.
+ </tp:rationale>
+ </dd>
+
+ <dt>debug-message - s</dt>
+ <dd>A message giving further details of any error indicated by the
+ <tp:member-ref>CallStateReason</tp:member-ref>. This will not
+ normally be localized or suitable for display to users, and is only
+ applicable when the call state is
+ <tp:type>Call_State</tp:type>_Ended.</dd>
+ </dl>
+ </tp:docstring>
+ </property>
+
+ <property name="CallState" type="u" access="read"
+ tp:name-for-bindings="Call_State" tp:type="Call_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current high-level state of this call. The
+ <tp:member-ref>CallFlags</tp:member-ref> provide additional
+ information, and the <tp:member-ref>CallStateReason</tp:member-ref>
+ and <tp:member-ref>CallStateDetails</tp:member-ref> explain the
+ reason for the current values for those properties.</p>
+
+ <p>Note that when in a conference call, this property is
+ purely to show your state in joining the call. The receiver
+ (or remote contact) in this context is the conference server
+ itself. The property does not change when other call members'
+ states change.</p>
+
+ <p>Clients MAY consider unknown values in this property to be an
+ error.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="CallFlags" type="u" access="read"
+ tp:name-for-bindings="Call_Flags" tp:type="Call_Flags">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Flags representing the status of the call as a whole,
+ providing more specific information than the
+ <tp:member-ref>CallState</tp:member-ref>.</p>
+
+ <p>Clients are expected to ignore unknown flags in this property,
+ without error.</p>
+
+ <p>When an ongoing call is active and not on hold or has any
+ other problems, this property will be 0.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Call_State_Change_Reason" type="u">
+ <tp:docstring>
+ A simple representation of the reason for a change in the call's
+ state, which may be used by simple clients, or used as a fallback
+ when the DBus_Reason member of a <tp:type>Call_State_Reason</tp:type>
+ struct is not understood.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ We just don't know. Unknown values of this enum SHOULD also be
+ treated like this.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="User_Requested" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change was requested by the contact indicated by the Actor
+ member of a <tp:type>Call_State_Reason</tp:type> struct.</p>
+
+ <p>If the Actor is the local user, the DBus_Reason SHOULD be the
+ empty string.</p>
+
+ <p>If the Actor is a remote user, the DBus_Reason SHOULD be the empty
+ string if the call was terminated normally, but MAY be a non-empty
+ error name to indicate error-like call termination reasons (call
+ rejected as busy, kicked from a conference by a moderator, etc.).</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Forwarded" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The call was forwarded. If known, the handle of the contact
+ the call was forwarded to will be indicated by the Actor member
+ of a <tp:type>Call_State_Reason</tp:type> struct.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="No_Answer" value="3">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The <tp:member-ref>CallState</tp:member-ref> changed from
+ <tp:type>Call_State</tp:type>_Pending_Receiver to
+ <tp:type>Call_State</tp:type>_Ended because the initiator
+ ended the call before the receiver accepted it. With an
+ incoming call this state change reason signifies a missed
+ call.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Call_State_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A description of the reason for a change to the
+ <tp:member-ref>CallState</tp:member-ref> and/or
+ <tp:member-ref>CallFlags</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <tp:member type="u" tp:type="Contact_Handle" name="Actor">
+ <tp:docstring>
+ The contact responsible for the change, or 0 if no contact was
+ responsible.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" tp:type="Call_State_Change_Reason" name="Reason">
+ <tp:docstring>
+ The reason, chosen from a limited set of possibilities defined by
+ the Telepathy specification. If
+ <tp:type>Call_State_Change_Reason</tp:type>_User_Requested then
+ the Actor member will dictate whether it was the local user or
+ a remote contact responsible.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="s" tp:type="DBus_Error_Name" name="DBus_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A specific reason for the change, which may be a D-Bus error in
+ the Telepathy namespace, a D-Bus error in any other namespace
+ (for implementation-specific errors), or the empty string to
+ indicate that the state change was not an error.</p>
+
+ <p>This SHOULD be an empty string for changes to any state other
+ than Ended.</p>
+
+ <p>The errors Cancelled and Terminated SHOULD NOT be used here;
+ an empty string SHOULD be used instead.</p>
+
+ <tp:rationale>
+ <p>Those error names are used to indicate normal call
+ termination by the local user or another user, respectively,
+ in contexts where a D-Bus error name must appear.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="CallStateReason" tp:name-for-bindings="Call_State_Reason"
+ type="(uus)" access="read" tp:type="Call_State_Reason">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The reason for the last change to the
+ <tp:member-ref>CallState</tp:member-ref> and/or
+ <tp:member-ref>CallFlags</tp:member-ref>. The
+ <tp:member-ref>CallStateDetails</tp:member-ref> MAY provide additional
+ information.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="CallStateChanged"
+ tp:name-for-bindings="Call_State_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the state of the call as a whole changes.</p>
+
+ <p>This signal is emitted for any change in the properties
+ corresponding to its arguments, even if the other properties
+ referenced remain unchanged.</p>
+ </tp:docstring>
+
+ <arg name="Call_State" type="u" tp:type="Call_State">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallState</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_Flags" type="u" tp:type="Call_Flags">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallFlags</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_State_Reason" type="(uus)" tp:type="Call_State_Reason">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallStateReason</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Call_State_Details" type="a{sv}">
+ <tp:docstring>
+ The new value of the <tp:member-ref>CallStateDetails</tp:member-ref>
+ property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="HardwareStreaming" tp:name-for-bindings="Hardware_Streaming"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If this property is True, all of the media streaming is done by some
+ mechanism outside the scope of Telepathy.</p>
+
+ <tp:rationale>
+ <p>A connection manager might be intended for a specialized hardware
+ device, which will take care of the audio streaming (e.g.
+ telepathy-yafono, which uses GSM hardware which does the actual
+ audio streaming for the call).</p>
+ </tp:rationale>
+
+ <p>If this is False, the handler is responsible for doing the actual
+ media streaming for at least some contents itself. Those contents
+ will have the <tp:dbus-ref namespace="ofdT.Call.Content.Interface"
+ >Media.DRAFT</tp:dbus-ref> interface, to communicate the necessary
+ information to a streaming implementation. Connection managers SHOULD
+ operate like this, if possible.</p>
+
+ <tp:rationale>
+ <p>Many connection managers (such as telepathy-gabble) only do the
+ call signalling, and expect the client to do the actual streaming
+ using something like
+ <a href="http://farsight.freedesktop.org/">Farsight</a>, to improve
+ latency and allow better UI integration.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:flags type="u" name="Call_Member_Flags" value-prefix="Call_Member_Flag">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A set of flags representing the status of a remote contact in a
+ call.</p>
+
+ <p>It is protocol- and client-specific whether a particular contact
+ will ever have a particular flag set on them, and Telepathy clients
+ SHOULD NOT assume that a flag will ever be set.</p>
+
+ <tp:rationale>
+ <p>180 Ringing in SIP, and its equivalent in XMPP, are optional
+ informational messages, and implementations are not required
+ to send them. The same applies to the messages used to indicate
+ hold state.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:flag suffix="Ringing" value = "1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The remote contact's client has told us that the contact has been
+ alerted about the call but has not responded.</p>
+
+ <tp:rationale>
+ <p>This is a flag per member, not a flag for the call as a whole,
+ because in Muji conference calls, you could invite someone and
+ have their state be "ringing" for a while.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Held" value = "2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The call member has put this call on hold.</p>
+
+ <tp:rationale>
+ <p>This is a flag per member, not a flag for the call as a whole,
+ because in conference calls, any member could put the conference
+ on hold.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Conference_Host" value="4">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ This contact has merged this call into a conference. Note that GSM
+ provides a notification when the remote party merges a call into a
+ conference, but not when it is split out again; thus, this flag can
+ only indicate that the call has been part of a conference at some
+ point. If a GSM connection manager receives a notification that a
+ call has been merged into a conference a second time, it SHOULD
+ represent this by clearing and immediately re-setting this flag on
+ the remote contact.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:mapping name="Call_Member_Map" array-name="Call_Member_Map_List">
+ <tp:docstring>A mapping from handles to their current state in the call.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Handle" name="key"/>
+ <tp:member type="u" tp:type="Call_Member_Flags" name="Flag"/>
+ </tp:mapping>
+
+ <signal name="CallMembersChanged"
+ tp:name-for-bindings="Call_Members_Changed">
+ <tp:docstring>
+ Emitted when the <tp:member-ref>CallMembers</tp:member-ref> property
+ changes in any way, either because contacts have been added to the
+ call, contacts have been removed from the call, or contacts' flags
+ have changed.
+ </tp:docstring>
+
+ <arg name="Flags_Changed" type="a{uu}" tp:type="Call_Member_Map">
+ <tp:docstring>
+ A map from members of the call to their new call member flags,
+ including at least the members who have been added to
+ <tp:member-ref>CallMembers</tp:member-ref>, and the members whose
+ flags have changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ A list of members who have left the call, i.e. keys to be removed
+ from <tp:member-ref>CallMembers</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CallMembers" tp:name-for-bindings="Call_Members"
+ type="a{uu}" access="read" tp:type="Call_Member_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A mapping from the remote contacts that are part of this call to flags
+ describing their status. This mapping never has the local user's handle
+ as a key.</p>
+
+ <p>When the call ends, this property should be an empty list,
+ and notified with
+ <tp:member-ref>CallMembersChanged</tp:member-ref></p>
+
+ <p>If the Call implements
+ <tp:dbus-ref namespace="ofdT.Channel.Interface"
+ >Group</tp:dbus-ref> and the Group members are
+ channel-specific handles, then this call SHOULD also use
+ channel-specific handles.</p>
+
+ <p>Anonymous members are exposed as channel-specific handles
+ with no owner.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialTransport" tp:name-for-bindings="Initial_Transport"
+ type="u" tp:type="Stream_Transport_Type" access="read"
+ tp:requestable="yes" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If set on a requested channel, this indicates the transport that
+ should be used for this call. Where not applicable, this property
+ is defined to be <tp:type>Stream_Transport_Type</tp:type>_Unknown,
+ in particular, on CMs with hardware streaming.</p>
+
+ <tp:rationale>
+ When implementing a voip gateway one wants the outgoing leg of the
+ gatewayed to have the same transport as the incoming leg. This
+ property allows the gateway to request a Call with the right
+ transport from the CM.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialAudio" tp:name-for-bindings="Initial_Audio"
+ type="b" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If set to True in a channel request that will create a new channel,
+ the connection manager should immediately attempt to establish an
+ audio stream to the remote contact, making it unnecessary for the
+ client to call <tp:dbus-ref
+ namespace="ofdT.Channel.Type.Call.DRAFT">AddContent</tp:dbus-ref>.</p>
+
+ <p>If this property, or InitialVideo, is passed to EnsureChannel
+ (as opposed to CreateChannel), the connection manager SHOULD ignore
+ these properties when checking whether it can return an existing
+ channel as suitable; these properties only become significant when
+ the connection manager has decided to create a new channel.</p>
+
+ <p>If True on a requested channel, this indicates that the audio
+ stream has already been requested and the client does not need to
+ call RequestStreams, although it MAY still do so.</p>
+
+ <p>If True on an unrequested (incoming) channel, this indicates that
+ the remote contact initially requested an audio stream; this does
+ not imply that that audio stream is still active (as indicated by
+ <tp:dbus-ref namespace="ofdT.Channel.Type.Call.DRAFT"
+ >Contents</tp:dbus-ref>).</p>
+
+ <p>The name of this new content can be decided by using the
+ <tp:member-ref>InitialAudioName</tp:member-ref> property.</p>
+
+ <p>Connection managers that support the <tp:dbus-ref
+ namespace="ofdT.Connection.Interface">ContactCapabilities</tp:dbus-ref>
+ interface SHOULD represent the capabilities of receiving audio
+ and/or video calls by including a channel class in
+ a contact's capabilities with ChannelType = Call
+ in the fixed properties dictionary, and InitialAudio and/or
+ InitialVideo in the allowed properties list. Clients wishing to
+ discover whether a particular contact is likely to be able to
+ receive audio and/or video calls SHOULD use this information.</p>
+
+ <tp:rationale>
+ <p>Not all clients support video calls, and it would also be
+ possible (although unlikely) to have a client which could only
+ stream video, not audio.</p>
+ </tp:rationale>
+
+ <p>Clients that are willing to receive audio and/or video calls
+ SHOULD include the following among their channel classes if
+ calling <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref>
+ (clients of a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>
+ SHOULD instead arrange for the ChannelDispatcher to do this,
+ by including the filters in their <tp:dbus-ref
+ namespace="ofdT.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ properties):</p>
+
+ <ul>
+ <li>{ ChannelType = Call }</li>
+ <li>{ ChannelType = Call, InitialAudio = True }
+ if receiving calls with audio is supported</li>
+ <li>{ ChannelType = Call, InitialVideo = True }
+ if receiving calls with video is supported</li>
+ </ul>
+
+ <tp:rationale>
+ <p>Connection managers for protocols with capability discovery,
+ like XMPP, need this information to advertise the appropriate
+ capabilities for their protocol.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialVideo" tp:name-for-bindings="Initial_Video"
+ type="b" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same as <tp:member-ref>InitialAudio</tp:member-ref>, but for
+ a video stream. This property is immutable (cannot change).</p>
+
+ <p>In particular, note that if this property is False, this does not
+ imply that an active video stream has not been added, only that no
+ video stream was active at the time the channel appeared.</p>
+
+ <p>This property is the correct way to discover whether connection
+ managers, contacts etc. support video calls; it appears in
+ capabilities structures in the same way as InitialAudio.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialAudioName" tp:name-for-bindings="Initial_Audio_Name"
+ type="s" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <tp:member-ref>InitialAudio</tp:member-ref> is set to
+ True, then this property will name the intial audio content
+ with the value of this property.</p>
+
+ <tp:rationale>
+ <p>Content names are meant to be significant, but if no name
+ can be given to initial audio content, then its name cannot
+ be meaningful or even localized.</p>
+ </tp:rationale>
+
+ <p>If this property is empty or missing from the channel
+ request and InitialAudio is True, then the CM must come up
+ with a sensible for the content, such as "audio".</p>
+
+ <p>If the protocol has no concept of stream names then this
+ property will not show up in the allowed properties list of
+ the Requestable Channel Classes for call channels.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialVideoName" tp:name-for-bindings="Initial_Video_Name"
+ type="s" access="read" tp:immutable="yes" tp:requestable="yes">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same as
+ <tp:member-ref>InitialAudioName</tp:member-ref>, but for a
+ video stream created by setting
+ <tp:member-ref>InitialVideo</tp:member-ref> to True. This
+ property is immutable and so cannot change.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="MutableContents" tp:name-for-bindings="Mutable_Contents"
+ type="b" access="read" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If True, a stream of a different content type can be added
+ after the Channel has been requested </p>
+
+ <p>If this property is missing, clients SHOULD assume that it is False,
+ and thus that the channel's streams cannot be changed once the call
+ has started.</p>
+
+ <p>If this property isn't present in the "allowed" set in any of the
+ Call entries contact capabilities, then user interfaces MAY choose to
+ show a separate "call" option for each class of call.</p>
+
+ <tp:rationale>
+ <p>For example, once an audio-only Google Talk call has started,
+ it is not possible to add a video stream; both audio and video
+ must be requested at the start of the call if video is desired.
+ User interfaces may use this pseudo-capability as a hint to
+ display separate "Audio call" and "Video call" buttons, rather
+ than a single "Call" button with the option to add and remove
+ video once the call has started for contacts without this flag.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:hct name="audio">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This client supports audio calls.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="video">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This client supports video calls.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="gtalk-p2p">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_GTalk_P2P.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="ice">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_ICE.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="wlm-2009">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_WLM_2009.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="shm">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client can implement streaming for streams whose <tp:dbus-ref
+ namespace="ofdT.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref>
+ property is <tp:type>Stream_Transport_Type</tp:type>_SHM.</p>
+ </tp:docstring>
+ </tp:hct>
+
+ <tp:hct name="video/h264" is-family="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The client supports media streaming with H264 (etc.).</p>
+
+ <p>This handler capability token is a one of a family
+ of similar tokens: for any other audio or video codec whose MIME
+ type is audio/<em>subtype</em> or video/<em>subtype</em>, a handler
+ capability token of this form may exist (the subtype MUST appear
+ in lower case in this context). Clients MAY support more
+ codecs than they explicitly advertise support for; clients SHOULD
+ explicitly advertise support for their preferred codec(s), and
+ for codecs like H264 that are, in practice, significant in codec
+ negotiation.</p>
+
+ <tp:rationale>
+ <p>For instance, the XMPP capability used by the Google Video
+ Chat web client to determine whether a client is compatible
+ with it requires support for H264 video, so an XMPP
+ connection manager that supports this version of Jingle should
+ not advertise the Google Video Chat capability unless there
+ is at least one installed client that declares that it supports
+ <code>video/h264</code> on Call channels.</p>
+ </tp:rationale>
+
+ <p>For example, a client could advertise support for audio and video
+ calls using Speex, Theora and H264 by having five handler capability
+ tokens in its <tp:dbus-ref
+ namespace="ofdT.Client.Handler">Capabilities</tp:dbus-ref>
+ property:</p>
+
+ <ul>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio/speex</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/theora</code></li>
+ <li><code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/h264</code></li>
+ </ul>
+
+ <p>Clients MAY have media signalling abilities without explicitly
+ supporting any particular codec, and connection managers SHOULD
+ support this usage.</p>
+
+ <tp:rationale>
+ <p>This is necessary to support gatewaying between two Telepathy
+ connections, in which case the available codecs might not be
+ known to the gatewaying process.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:hct>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Contact_List.xml b/qt4/spec/Channel_Type_Contact_List.xml
new file mode 100644
index 000000000..5a0d33362
--- /dev/null
+++ b/qt4/spec/Channel_Type_Contact_List.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Contact_List" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.ContactList">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Interface.Group"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for representing a list of people on the server which is
+ not used for communication. This is intended for use with the interface
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.Group</tp:dbus-ref>
+ for managing buddy lists and privacy lists
+ on the server. This channel type has no methods because all of the
+ functionality it represents is available via the group interface.</p>
+
+ <p>There are currently two types of contact list:
+ HANDLE_TYPE_LIST is a &quot;magic&quot; server-defined list, and
+ HANDLE_TYPE_GROUP is a user-defined contact group.</p>
+
+ <p>For server-defined lists like the subscribe list, singleton instances
+ of this channel type should be created by the connection manager at
+ connection time if the list exists on the server, or may be requested
+ by using the appropriate handle. These handles can be obtained using
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">RequestHandles</tp:dbus-ref>
+ with a <tp:type>Handle_Type</tp:type> of HANDLE_TYPE_LIST and one of the
+ following identifiers:</p>
+
+ <ul>
+ <li>subscribe - the group of contacts for whom you receive presence</li>
+ <li>publish - the group of contacts who may receive your presence</li>
+ <li>hide - a group of contacts who are on the publish list but are temporarily disallowed from receiving your presence</li>
+ <li>allow - a group of contacts who may send you messages</li>
+ <li>deny - a group of contacts who may not send you messages</li>
+ <li>stored - on protocols where the user's contacts are stored, this
+ contact list contains all stored contacts regardless of subscription
+ status.</li>
+ </ul>
+
+ <p>A contact can be in several server-defined lists. All lists are optional
+ to implement. If <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">RequestHandles</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">RequestChannel</tp:dbus-ref>
+ for a particular contact list raises an error, this indicates that the
+ connection manager makes no particular statement about the list's contents;
+ clients MUST NOT consider this to be fatal.</p>
+
+ <p>If a client wants to list all of a user's contacts, it is appropriate to
+ use the union of the subscribe, publish and stored lists, including the
+ local and remote pending members.</p>
+
+ <p>For example in XMPP, contacts who have the subscription type "none",
+ "from", "to" and "both" can be respectively in the lists:</p>
+
+ <ul>
+ <li>"none": stored</li>
+ <li>"from": stored and publish</li>
+ <li>"to": stored and subscribe</li>
+ <li>"both": stored, publish and subscribe</li>
+ </ul>
+
+ <p>These contact list channels may not be closed.</p>
+
+ <p>For user-defined contact groups, instances of this channel type should
+ be created by the connection manager at connection time for each group
+ that exists on the server. New, empty groups can be created by calling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">RequestHandles</tp:dbus-ref>
+ with a <tp:type>Handle_Type</tp:type> of HANDLE_TYPE_GROUP and with the
+ name set to the human-readable UTF-8 name of the group.</p>
+
+ <p>User-defined groups may be deleted by calling <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref> on the
+ channel, but only if
+ the group is already empty. Closing a channel to a non-empty group is
+ not allowed; its members must be set to the empty set first.</p>
+
+ <p>On some protocols (e.g. XMPP) empty groups are not represented on the
+ server, so disconnecting from the server and reconnecting might cause
+ empty groups to vanish.</p>
+ </tp:docstring>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Contact_Search.xml b/qt4/spec/Channel_Type_Contact_Search.xml
new file mode 100644
index 000000000..fefa77a26
--- /dev/null
+++ b/qt4/spec/Channel_Type_Contact_Search.xml
@@ -0,0 +1,462 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Contact_Search" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2009 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright © 2005-2009 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.ContactSearch">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:added version="0.19.10">
+ as stable API. Changes from draft 2:
+ <tp:type>Contact_Search_Result_Map</tp:type> keys are now identifiers
+ rather than handles; consequently, the values need not include
+ <tt>x-telepathy-identifier</tt>.
+ </tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for searching server-stored user directories. A new
+ channel should be requested by a client for each search attempt, and
+ closed when the search is completed or the required result has been
+ found. Channels of this type should have <tp:dbus-ref
+ namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>
+ <code>None</code> (and hence <tp:dbus-ref
+ namespace='ofdT.Channel'>TargetHandle</tp:dbus-ref> <code>0</code> and
+ <tp:dbus-ref namespace='ofdT.Channel'>TargetID</tp:dbus-ref>
+ <code>""</code>). Requests for channels of this type need only
+ optionally specify the <tp:member-ref>Server</tp:member-ref> property
+ (if it is an allowed property in the connection's <tp:dbus-ref
+ namespace='ofdT.Connection.Interface.Requests'>RequestableChannelClasses</tp:dbus-ref>).</p>
+
+ <p>Before searching, the
+ <tp:member-ref>AvailableSearchKeys</tp:member-ref> property should be
+ inspected to determine the valid search keys which can be provided to
+ the <tp:member-ref>Search</tp:member-ref> method. A search request is
+ then started by providing some of these terms to the Search method, and
+ the <tp:member-ref>SearchState</tp:member-ref> will change from
+ <code>Not_Started</code> to <code>In_Progress</code>. As results are
+ returned by the server, the
+ <tp:member-ref>SearchResultReceived</tp:member-ref> signal is emitted
+ for each contact found; when the search is complete, the search state
+ will be set to <code>Completed</code>. If the search fails after Search
+ has been called, the state will change to <code>Failed</code>. A
+ running search can be cancelled by calling
+ <tp:member-ref>Stop</tp:member-ref>.</p>
+
+ <p>If the protocol supports limiting the number of results returned by a
+ search and subsequently requesting more results, after
+ <tp:member-ref>Limit</tp:member-ref> results have been received the
+ search state will be set to <code>More_Available</code>. Clients may
+ call <tp:member-ref>More</tp:member-ref> to request another
+ <tp:member-ref>Limit</tp:member-ref> results. If allowed by the
+ connection manager, clients may specify the "page size" by specifying
+ <tp:member-ref>Limit</tp:member-ref> when calling
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>.
+ </p>
+
+ <p>The client should call the channel's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref>
+ method when it is finished with the channel.</p>
+
+ <p>Each channel can only be used for a single search; a new channel
+ should be requested for each subsequent search. Connection managers
+ MUST support multiple ContactSearch channels being open at once (even
+ to the same server, if applicable).</p>
+
+ <p>It does not make sense to request this channel type using <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">EnsureChannel</tp:dbus-ref>;
+ clients SHOULD request channels of this type using
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>
+ instead.</p>
+
+ <tp:rationale>
+ <p>A contact search channel that is already in use for a different
+ search isn't useful.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:enum name="Channel_Contact_Search_State" type="u">
+ <tp:enumvalue suffix="Not_Started" value="0">
+ <tp:docstring>The search has not started</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="In_Progress" value="1">
+ <tp:docstring>The search is in progress</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="More_Available" value="2">
+ <tp:docstring>The search has paused, but more results can be retrieved
+ by calling <tp:member-ref>More</tp:member-ref>.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Completed" value="3">
+ <tp:docstring>The search has been completed</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Failed" value="4">
+ <tp:docstring>The search has failed</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="SearchState" tp:name-for-bindings="Search_State"
+ access="read" type="u" tp:type="Channel_Contact_Search_State">
+ <tp:docstring>
+ The current state of this search channel object. Change notification
+ is via <tp:member-ref>SearchStateChanged</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <signal name="SearchStateChanged"
+ tp:name-for-bindings="Search_State_Changed">
+ <arg name="State" type="u" tp:type="Channel_Contact_Search_State">
+ <tp:docstring>The new search state</tp:docstring>
+ </arg>
+ <arg name="Error" type="s" tp:type="DBus_Error_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ If the new state is <code>Failed</code>, the name of a D-Bus error
+ describing what went wrong. Otherwise, the empty string.
+ </tp:docstring>
+ </arg>
+ <arg name="Details" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Additional information about the state transition, which may
+ include the following well-known keys:</p>
+
+ <dl>
+ <dt>debug-message (s)</dt>
+ <dd>Debugging information on the change, corresponding to the
+ message part of a D-Bus error message, which SHOULD NOT be
+ displayed to users under normal circumstances</dd>
+ </dl>
+
+ <tp:rationale>
+ <p>This argument allows for future extensions. For instance,
+ if moving to state <code>Failed</code> because the server
+ rejected one of our search terms, we could define a key
+ that indicates which terms were invalid.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the <tp:member-ref>SearchState</tp:member-ref> property
+ changes. The implementation MUST NOT make transitions other than the
+ following:</p>
+
+ <ul>
+ <li><code>Not_Started</code> → <code>In_Progress</code></li>
+ <li><code>In_Progress</code> → <code>More_Available</code></li>
+ <li><code>More_Available</code> → <code>In_Progress</code></li>
+ <li><code>In_Progress</code> → <code>Completed</code></li>
+ <li><code>In_Progress</code> → <code>Failed</code></li>
+ </ul>
+ </tp:docstring>
+ </signal>
+
+ <tp:simple-type name="Contact_Search_Key" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Any of the following search keys, with the indicated result for
+ the search:</p>
+
+ <dl>
+ <dt>The empty string</dt>
+ <dd>Search for the search term in some implementation-dependent
+ set of fields, using an implementation-dependent algorithm
+ (e.g. searching for each word mentioned)
+ <tp:rationale>
+ The "one big search box" approach to searching, as is familiar
+ from Google. The Sametime plugin to Pidgin appears to search in
+ this way.
+ </tp:rationale>
+ </dd>
+
+ <dt>A <tp:type>VCard_Field</tp:type></dt>
+ <dd>Search for the search term in fields matching that name (for
+ instance, <code>nickname</code> would search nicknames, and
+ <code>tel</code> would search any available phone number,
+ regardless of its work/home/mobile/... status).</dd>
+
+ <dt>A <tp:type>VCard_Field</tp:type> followed by
+ "<code>;</code>" and a
+ <tp:type>VCard_Type_Parameter</tp:type> of the form
+ "<code>type=...</code>"</dt>
+ <dd>Search for the search term in fields of that name and type
+ only (for instance, <code>tel;type=mobile</code>).</dd>
+
+ <dt><code>x-telepathy-identifier</code></dt>
+ <dd>Search for contacts whose identifier in the IM protocol
+ matches the search term (e.g. contains it as a substring)
+ <tp:rationale>
+ Otherwise, starting a search by identifier would require the UI
+ to know the vCard field name corresponding to identifiers in
+ this protocol, which might be non-standard (like
+ <code>x-jabber</code>) or not exist at all.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>x-gender</code></dt>
+ <dd>For the search term "male" or "female", search only for contacts
+ listed as male or female, respectively. The results for other
+ search terms are undefined; it is likely that contacts with
+ unspecified gender will only be matched if this search key
+ is omitted from the request.
+ <tp:rationale>
+ Examples in XEP-0055 suggest this usage, and at least Gadu-Gadu
+ also supports limiting search results by gender.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>x-n-family</code></dt>
+ <dd>Search for the search term in contacts' family names
+ (the first component of the vCard field <code>n</code>).
+ <tp:rationale>
+ Gadu-Gadu and TOC seem to support this mode of searching.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>x-n-given</code></dt>
+ <dd>Search for the search term in contacts' given names
+ (the second component of the vCard field <code>n</code>).
+ <tp:rationale>
+ As for <code>x-n-family</code>.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>x-online</code></dt>
+ <dd>For the search term "yes", search only for contacts who are
+ currently online. The results for other search terms are undefined.
+ <tp:rationale>Gadu-Gadu appears to support this.</tp:rationale>
+ </dd>
+
+ <dt><code>x-adr-locality</code></dt>
+ <dd>Search for the search term as a locality or city (the fourth
+ component of the vCard field <code>adr</code>).
+ <tp:rationale>
+ Gadu-Gadu and TOC appear to support this.
+ </tp:rationale>
+ </dd>
+ </dl>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <property name="Limit" type="u" access="read" tp:name-for-bindings="Limit"
+ tp:immutable='yes' tp:requestable='sometimes'>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If supported by the protocol, the maximum number of results that
+ should be returned, where <code>0</code> represents no limit. If the
+ protocol does not support limiting results, this should be
+ <code>0</code>.</p>
+
+ <p>For example, if the terms passed to
+ <tp:member-ref>Search</tp:member-ref> match <i>Antonius</i>,
+ <i>Bridget</i> and <i>Charles</i> and this property is
+ <code>2</code>, the search service SHOULD only return <i>Antonius</i>
+ and <i>Bridget</i>.</p>
+
+ <p>This property SHOULD be requestable if and only if the protocol
+ supports specifying a limit; implementations SHOULD use
+ <code>0</code> as the default if possible, or a protocol-specific
+ sensible default otherwise.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="AvailableSearchKeys" type="as" access="read"
+ tp:name-for-bindings="Available_Search_Keys" tp:immutable='yes'>
+ <tp:docstring>
+ The set of search keys supported by this channel. Example values
+ include [""] (for protocols where several address fields are
+ implicitly searched) or ["x-n-given", "x-n-family", "nickname",
+ "email"] (for XMPP XEP-0055, without extensibility via Data Forms).
+
+ <tp:rationale>
+ It can be in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal for round-trip reduction.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:mapping name="Contact_Search_Map">
+ <tp:docstring>A map from search keys to search terms.</tp:docstring>
+ <tp:member name="Key" type="s" tp:type="Contact_Search_Key">
+ <tp:docstring>
+ The search key to match against
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Term" type="s">
+ <tp:docstring>
+ The term or terms to be searched for in the search key; depending on
+ the protocol and the server implementation, this may be matched by
+ exact or approximate equality, substring matching, word matching
+ or any other matching algorithm
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <method name="Search" tp:name-for-bindings="Search">
+ <arg direction="in" name="Terms"
+ type="a{ss}" tp:type="Contact_Search_Map">
+ <tp:docstring>
+ A dictionary mapping search key names to the desired values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Send a request to start a search for contacts on this connection. This
+ may only be called while the <tp:member-ref>SearchState</tp:member-ref>
+ is Not_Started; a valid search request will cause the
+ <tp:member-ref>SearchStateChanged</tp:member-ref> signal to be emitted
+ with the state In_Progress.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The <tp:member-ref>SearchState</tp:member-ref> is no longer
+ Not_Started, so this method is no longer available.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The search terms included something this connection manager cannot
+ search for.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="More" tp:name-for-bindings="More">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Request that a search in <tp:member-ref>SearchState</tp:member-ref>
+ <code>More_Available</code> move back to state <code>In_Progress</code>
+ and continue listing up to <tp:member-ref>Limit</tp:member-ref> more results.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The <tp:member-ref>SearchState</tp:member-ref> is not
+ <code>More_Available</code>.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Stop" tp:name-for-bindings="Stop">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Stop the current search. This may not be called while the
+ <tp:member-ref>SearchState</tp:member-ref> is Not_Started. If called
+ while the SearchState is In_Progress,
+ <tp:member-ref>SearchStateChanged</tp:member-ref> will be emitted,
+ with the state Failed and the error
+ <code>org.freedesktop.Telepathy.Error.<tp:error-ref>Cancelled</tp:error-ref></code>.</p>
+
+ <p>Calling this method on a search in state Completed or Failed
+ succeeds, but has no effect.</p>
+
+ <tp:rationale>
+ <p>Specifying Stop to succeed when the search has finished means that
+ clients who call Stop just before receiving
+ <tp:member-ref>SearchStateChanged</tp:member-ref> don't have to
+ handle a useless error.</p>
+ </tp:rationale>
+
+ <p>Depending on the protocol, the connection manager may not be
+ able to prevent the server from sending further results after this
+ method returns; if this is the case, it MUST ignore any further
+ results.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The <tp:member-ref>SearchState</tp:member-ref> is Not_Started, so
+ this method is not yet available.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:mapping name="Contact_Search_Result_Map">
+ <tp:docstring>A map from contact identifier to search result, emitted in
+ the <tp:member-ref>SearchResultReceived</tp:member-ref>
+ signal.</tp:docstring>
+
+ <tp:member name="Contact_Identifier" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The identifier of a contact matching the search terms.
+
+ <tp:rationale>
+ This is an identifier rather than a handle in case we make handles
+ immortal; see <a
+ href="https://bugs.freedesktop.org/show_bug.cgi?id=23155">fd.o#23155</a>
+ and <a
+ href="https://bugs.freedesktop.org/show_bug.cgi?id=13347#c5">fd.o#13347
+ comment 5</a>.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Info" type="a(sasas)" tp:type="Contact_Info_Field[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of fields representing information about this
+ contact, in the same format used in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">ContactInfo</tp:dbus-ref>
+ interface. It is possible that a separate call to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.ContactInfo">RequestContactInfo</tp:dbus-ref>
+ would return more information than this signal provides.</p>
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <signal name="SearchResultReceived"
+ tp:name-for-bindings="Search_Result_Received">
+ <arg name="Result" type="a{sa(sasas)}" tp:type="Contact_Search_Result_Map">
+ <tp:docstring>A mapping from contact identifier to an array of fields
+ representing information about this contact.</tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Emitted when a some search results are received from the server.
+ This signal can be fired arbitrarily many times so clients MUST NOT
+ assume they'll get only one signal.
+ </tp:docstring>
+ </signal>
+
+ <property name="Server" tp:name-for-bindings="Server"
+ type="s" access="read" tp:requestable='sometimes' tp:immutable='yes'>
+ <tp:docstring>
+ <p>For protocols which support searching for contacts on multiple
+ servers with different DNS names (like XMPP), the DNS name of the
+ server being searched by this channel, e.g.
+ "characters.shakespeare.lit". Otherwise, the empty string.</p>
+
+ <tp:rationale>
+ <p>XEP 0055 defines a mechanism for XMPP clients to search services
+ of their choice for contacts, such as users.jabber.org (the "Jabber
+ User Directory").</p>
+ </tp:rationale>
+
+ <p>This property SHOULD be requestable if and only if the
+ protocol supports querying multiple different servers;
+ implementations SHOULD use a sensible default if possible if this
+ property is not specified in a channel request.</p>
+
+ <tp:rationale>
+ <p>This allows a client to perform searches on a protocol it knows
+ nothing about without requiring the user to guess a valid server's
+ hostname.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_DBus_Tube.xml b/qt4/spec/Channel_Type_DBus_Tube.xml
new file mode 100644
index 000000000..961576319
--- /dev/null
+++ b/qt4/spec/Channel_Type_DBus_Tube.xml
@@ -0,0 +1,189 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_DBus_Tube" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.DBusTube">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Interface.Tube"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A D-Bus tube is an ordered reliable transport, for transporting D-Bus
+ traffic.</p>
+
+ <p>For each D-Bus tube, the connection manager listens on a D-Bus
+ server address, as detailed in the D-Bus specification. On this
+ address, it emulates a bus upon which each tube participant appears
+ as an endpoint.</p>
+
+ <p>The objects and interfaces which are expected to exist on the
+ emulated bus depend on the well-known name; typically, either the
+ participant who initiated the tube is expected to export the same
+ objects/interfaces that would be exported by a service of that name
+ on a bus, or all participants are expected to export those
+ objects/interfaces.</p>
+
+ <p>In a multi-user context (Handle_Type_Room) the tube behaves
+ like the D-Bus bus daemon, so participants can send each other
+ private messages, or can send broadcast messages which are
+ received by everyone in the tube (including themselves).
+ Each participant has a D-Bus unique name; connection managers
+ MUST prevent participants from sending messages with the wrong
+ sender unique name, and SHOULD attempt to avoid participants
+ receiving messages not intended for them.</p>
+
+ <p>In a 1-1 context (Handle_Type_Contact) the tube behaves like
+ a peer-to-peer D-Bus connection - arbitrary D-Bus messages with
+ any sender and/or destination can be sent by each participant,
+ and each participant receives all messages sent by the other
+ participant.</p>
+
+ </tp:docstring>
+
+ <method name="Offer" tp:name-for-bindings="Offer">
+ <tp:docstring>
+ Offers a D-Bus tube providing the service specified.
+ </tp:docstring>
+ <arg direction="in" name="parameters" type="a{sv}"
+ tp:type="String_Variant_Map">
+ <tp:docstring>
+ The dictionary of arbitrary
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface.Tube">Parameters</tp:dbus-ref>
+ to send with the tube offer.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="access_control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The access control the connection manager applies to the D-Bus socket.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="address" type="s">
+ <tp:docstring>
+ The string describing the address of the private bus. The client
+ SHOULD NOT attempt to connect to the address until the tube is open.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The contact associated with this channel doesn't have tubes
+ capabilities.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <tp:docstring>
+ Accept a D-Bus tube that's in the "local pending" state. The
+ connection manager will attempt to open the tube. The tube remains in
+ the "local pending" state until the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Tube">TubeChannelStateChanged</tp:dbus-ref>
+ signal is emitted.
+ </tp:docstring>
+ <arg direction="in" name="access_control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The access control the connection manager applies to the D-Bus socket.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="address" type="s">
+ <tp:docstring>
+ The string describing the address of the private bus. The client
+ SHOULD NOT attempt to connect to the address until the tube is open.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="DBusNamesChanged" tp:name-for-bindings="DBus_Names_Changed">
+ <tp:docstring>
+ Emitted on a multi-user (i.e. Handle_Type_Room) D-Bus tube when a
+ participant opens or closes the tube. This provides change
+ notification for the <tp:member-ref>DBusNames</tp:member-ref> property.
+ </tp:docstring>
+ <arg name="Added" type="a{us}" tp:type="DBus_Tube_Participants">
+ <tp:docstring>
+ Array of handles and D-Bus names of new participants.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ Array of handles of former participants.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="ServiceName" type="s" access="read"
+ tp:name-for-bindings="Service_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A string representing the service name that will be used over the
+ tube. It SHOULD be a well-known D-Bus service name, of the form
+ <tt>com.example.ServiceName</tt>.</p>
+ <p>When the tube is offered, the service name is transmitted to the
+ other end.</p>
+ <p>When requesting a channel with
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>,
+ this property MUST be included in the request.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="DBusNames" tp:name-for-bindings="DBus_Names"
+ access="read" type="a{us}" tp:type="DBus_Tube_Participants">
+ <tp:docstring>
+ For a multi-user (i.e. Handle_Type_Room) D-Bus tube, a mapping
+ between contact handles and their unique bus names on this tube.
+ For a peer-to-peer (i.e. Handle_Type_Contact) D-Bus tube, the empty
+ dictionary. Change notification is via
+ <tp:member-ref>DBusNamesChanged</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <tp:mapping name="DBus_Tube_Participants">
+ <tp:docstring>Represents the participants in a multi-user D-Bus tube, as
+ used by the <tp:member-ref>DBusNames</tp:member-ref> property and the
+ <tp:member-ref>DBusNamesChanged</tp:member-ref> signal.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle">
+ <tp:docstring>
+ The handle of a participant in this D-Bus tube.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" tp:type="DBus_Unique_Name" name="Unique_Name">
+ <tp:docstring>
+ That participant's unique name.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="SupportedAccessControls" type="au"
+ tp:type="Socket_Access_Control[]" access="read"
+ tp:name-for-bindings="Supported_Access_Controls">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of the access control types that are supported with this channel.
+ Note that only Socket_Access_Control_Localhost and
+ Socket_Access_Control_Credentials can be used with D-Bus tubes.</p>
+
+ <p>When requesting a channel with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>,
+ this property MUST NOT be included in the request.</p>
+
+ </tp:docstring>
+ </property>
+
+ </interface>
+
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_File_Transfer.xml b/qt4/spec/Channel_Type_File_Transfer.xml
new file mode 100644
index 000000000..6963d2133
--- /dev/null
+++ b/qt4/spec/Channel_Type_File_Transfer.xml
@@ -0,0 +1,580 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_File_Transfer" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>
+ Copyright © 2008-2009 Collabora Limited
+ </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.FileTransfer">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:added version="0.17.18">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for transferring files. The
+ transmission of data between contacts is achieved by reading from
+ or writing to a socket. The type of the socket (local Unix, IPv4,
+ etc.) is decided on when the file transfer is offered or accepted.</p>
+
+ <p>A socket approach is used to make the transfer less dependent on both
+ client and connection manager knowing the same protocols. As an example,
+ when browsing an SMB share in a file manager, one selects "Send file"
+ and chooses a contact. Instead of passing a URL which would then require
+ the connection manager to connect to the SMB share itself, the client
+ passes a stream from which the connection manager reads, requiring no
+ further connection to the share. It also allows connection managers to
+ be more restricted in their access to the system, allowing tighter
+ security policies with eg SELinux, or more flexible deployments which
+ cross user or system boundaries.</p>
+
+ <p>The Telepathy client should connect to the socket or address that
+ the connection manager has set up and provided back to the clients
+ through the two methods.</p>
+
+ <ul><li>In order to send a file, one should request a FileTransfer
+ channel for a contact, including at least the mandatory properties
+ (<tp:member-ref>Filename</tp:member-ref>,
+ <tp:member-ref>Size</tp:member-ref> and <tp:member-ref>ContentType</tp:member-ref>).
+ Then, one should
+ call <tp:member-ref>ProvideFile</tp:member-ref> to configure the socket that
+ will be used to transfer the file.</li>
+
+ <li>In order to receive an incoming file transfer, one should call
+ <tp:member-ref>AcceptFile</tp:member-ref> and then wait until the state
+ changes to Open. When the receiver wants to resume a transfer, the Offset
+ argument should be should be set to a non-zero value when calling
+ <tp:member-ref>AcceptFile</tp:member-ref>.</li>
+
+ <li>Once the offset has been negotiated, the
+ <tp:member-ref>InitialOffsetDefined</tp:member-ref> signal
+ is emitted and the <tp:member-ref>InitialOffset</tp:member-ref> property
+ is defined. The <tp:member-ref>InitialOffsetDefined</tp:member-ref>
+ signal is emitted before channel becomes Open.
+ The receiver MUST check the value of
+ <tp:member-ref>InitialOffset</tp:member-ref> for a difference in offset
+ from the requested value in AcceptFile.</li>
+
+ <li>When the state changes to Open, Clients can start the transfer of the
+ file using the offset previously announced.
+ </li></ul>
+
+ <p>If something goes wrong with the transfer,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.Close</tp:dbus-ref>
+ should be called on the channel.</p>
+
+ <p>The File channel type may be requested for handles of type
+ HANDLE_TYPE_CONTACT. If the channel is requested for any other
+ handle type then the behaviour is undefined.</p>
+
+ <p>Connection managers SHOULD NOT advertise support for file transfer to
+ other contacts unless it has been indicated by a call to
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref>.
+ </p>
+ <tp:rationale>
+ <p>People would send us files, and it would always fail. That would be silly.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <property name="State" type="u" tp:type="File_Transfer_State"
+ access="read" tp:name-for-bindings="State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The state of the file transfer as described by the
+ File_Transfer_State enum.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="ContentType" type="s" access="read"
+ tp:name-for-bindings="Content_Type">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The file's MIME type. This cannot change once the channel has
+ been created.</p>
+
+ <p>This property is mandatory when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method. Protocols which do not have a content-type property with file
+ transfers should set this value to application/octet-stream.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Filename" type="s" access="read"
+ tp:name-for-bindings="Filename">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the file on the sender's side. This is therefore given
+ as a suggested filename for the receiver. This cannot change
+ once the channel has been created.</p>
+
+ <p>This property should be the basename of the file being sent. For example,
+ if the sender sends the file /home/user/monkey.pdf then this property should
+ be set to monkey.pdf.</p>
+
+ <p>This property is mandatory when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method. This property cannot be empty and MUST be set to a sensible value.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Size" type="t" access="read"
+ tp:name-for-bindings="Size">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The size of the file. If this property is set, then the file
+ transfer is guaranteed to be this size. This cannot change once
+ the channel has been created.</p>
+
+ <p>When you are creating a channel with this property, its value
+ MUST be accurate and in bytes. However, when receiving a file, this
+ property still MUST be in bytes but might not be entirely accurate
+ to the byte.</p>
+
+ <p>This property is mandatory when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method. If this information isn't provided in the protocol, connection managers MUST set it
+ to UINT64_MAX.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="ContentHashType" type="u" tp:type="File_Hash_Type"
+ access="read" tp:name-for-bindings="Content_Hash_Type">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The type of the <tp:member-ref>ContentHash</tp:member-ref> property.</p>
+
+ <p>This property is optional when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method. However, if you wish to include the <tp:member-ref>ContentHash</tp:member-ref>
+ property you MUST also include this property. If you omit this property from a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method call then its value will be assumed to be File_Hash_Type_None.</p>
+
+ <p>For each supported hash type, implementations SHOULD include an entry
+ in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>
+ with this property fixed to that hash type. If the protocol supports
+ offering a file without a content hash, implementations SHOULD list
+ this property in Allowed in a requestable channel class, mapping hash
+ types they don't understand to None.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="ContentHash" type="s" access="read"
+ tp:name-for-bindings="Content_Hash">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Hash of the contents of the file transfer, of type described
+ in the value of the <tp:member-ref>ContentHashType</tp:member-ref>
+ property.</p>
+
+ <p>This property is optional when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method. Its value MUST correspond to the appropriate type of the
+ <tp:member-ref>ContentHashType</tp:member-ref> property. If the
+ ContentHashType property is not set, or set to File_Hash_Type_None,
+ then this property will not even be looked at.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Description" type="s" access="read"
+ tp:name-for-bindings="Description">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Description of the file transfer. This cannot change once the
+ channel has been created.</p>
+
+ <p>This property is optional when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method. If this property was not provided by the remote party, connection managers MUST set it to
+ the empty string.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Date" type="x" access="read"
+ tp:type="Unix_Timestamp64" tp:name-for-bindings="Date">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The last modification time of the file being transferred. This
+ cannot change once the channel has been created</p>
+
+ <p>This property is optional when requesting the channel with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>
+ method.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="AvailableSocketTypes" type="a{uau}"
+ tp:type="Supported_Socket_Map" access="read"
+ tp:name-for-bindings="Available_Socket_Types">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A mapping from address types (members of Socket_Address_Type) to
+ arrays of access-control type (members of Socket_Access_Control)
+ that the connection manager supports for sockets with that
+ address type. For simplicity, if a CM supports offering a
+ particular type of file transfer, it is assumed to support accepting
+ it. Connection Managers MUST support at least Socket_Address_Type_IPv4.</p>
+
+ <p>A typical value for a host without IPv6 support:</p>
+
+ <pre>
+ {
+ Socket_Address_Type_IPv4:
+ [Socket_Access_Control_Localhost, Socket_Access_Control_Port,
+ Socket_Access_Control_Netmask],
+ Socket_Address_Type_Unix:
+ [Socket_Access_Control_Localhost, Socket_Access_Control_Credentials]
+ }
+ </pre>
+ </tp:docstring>
+ </property>
+
+ <property name="TransferredBytes" type="t" access="read"
+ tp:name-for-bindings="Transferred_Bytes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The number of bytes that have been transferred at the time of
+ requesting the property. This will be updated as the file transfer
+ continues.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialOffset" type="t" access="read"
+ tp:name-for-bindings="Initial_Offset">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The offset in bytes from where the file should be sent. This MUST
+ be respected by both the receiver and the sender after the state
+ becomes Open, but before any data is sent or received. Until the
+ <tp:member-ref>InitialOffsetDefined</tp:member-ref> signal
+ is emitted, this property is undefined.</p>
+
+ <p>Before setting the <tp:member-ref>State</tp:member-ref> property to
+ Open, the connection manager MUST set the InitialOffset property,
+ possibly to 0.</p>
+
+ <p>This property MUST NOT change after the state of the transfer has
+ changed to Open.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="URI" type="s" access="readwrite"
+ tp:name-for-bindings="URI" tp:immutable="sometimes" tp:requestable="yes">
+ <tp:added version="0.21.9"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For outgoing file transfers, this requestable property allows the channel
+ requester to inform observers (and the handler, if it is not the requester
+ itself) of the URI of the file being transferred. Note that the
+ connection manager SHOULD NOT read this file directly; the handler
+ streams the file into the CM through the socket negotiated using
+ <tp:member-ref>ProvideFile</tp:member-ref>.</p>
+
+ <p>On outgoing file transfers, this property MUST NOT change after the channel
+ is requested.</p>
+
+ <p>For incoming file transfers, this property MAY be set by the channel
+ handler before calling <tp:member-ref>AcceptFile</tp:member-ref> to
+ inform observers where the incoming file will be saved.
+ Setting this property once <tp:member-ref>AcceptFile</tp:member-ref>
+ has been called MUST fail. Once this property has been set
+ <tp:member-ref>URIDefined</tp:member-ref> is emitted.</p>
+
+ <p>If set, this URI SHOULD generally point to a file on the local system, as
+ defined by <a href='http://www.apps.ietf.org/rfc/rfc1738.html#sec-3.10'>
+ RFC 1738 §3.10</a>; that is, it should be of the form
+ <tt>file:///path/to/file</tt> or <tt>file://localhost/path/to/file</tt>.
+ For outgoing files, this URI MAY use a different scheme, such as
+ <tt>http:</tt>, if a remote resource is being transferred
+ to a contact.</p>
+
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="File_Transfer_State" type="u">
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ An invalid state type used as a null value. This value MUST NOT
+ appear in the State property.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Pending" value="1">
+ <tp:docstring>
+ The file transfer is waiting to be accepted/closed by the receiver.
+ The receiver has to call <tp:member-ref>AcceptFile</tp:member-ref>,
+ then wait for the state to change to Open and check the offset value.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Accepted" value="2">
+ <tp:docstring>
+ The receiver has accepted the transfer. The sender now has to
+ call <tp:member-ref>ProvideFile</tp:member-ref> to actually start the transfer.
+ The receiver should now wait for the state to change to Open
+ and check the offset value.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Open" value="3">
+ <tp:docstring>
+ The file transfer is open for traffic.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Completed" value="4">
+ <tp:docstring>
+ The file transfer has been completed successfully.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Cancelled" value="5">
+ <tp:docstring>
+ The file transfer has been cancelled.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="File_Transfer_State_Change_Reason" type="u">
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ No reason was specified.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Requested" value="1">
+ <tp:docstring>
+ The change in state was requested.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Local_Stopped" value="2">
+ <tp:docstring>
+ The file transfer was cancelled by the local user.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Remote_Stopped" value="3">
+ <tp:docstring>
+ The file transfer was cancelled by the remote user.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Local_Error" value="4">
+ <tp:docstring>
+ The file transfer was cancelled because of a local error.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Remote_Error" value="5">
+ <tp:docstring>
+ The file transfer was cancelled because of a remote error.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="File_Hash_Type" type="u">
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ No hash.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="MD5" value="1">
+ <tp:docstring>
+ MD5 digest as a string of 32 ASCII hex digits.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="SHA1" value="2">
+ <tp:docstring>
+ SHA1 digest as a string of ASCII hex digits.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="SHA256" value="3">
+ <tp:docstring>
+ SHA256 digest as a string of ASCII hex digits.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <method name="AcceptFile" tp:name-for-bindings="Accept_File">
+ <tp:docstring>
+ Accept a file transfer that's in the Pending state. The file
+ transfer's state becomes Accepted after this method is called.
+ At this point the client can connect to the socket. CM MUST emit
+ <tp:member-ref>InitialOffsetDefined</tp:member-ref> and change
+ the state to Open before writing to the socket.
+ Then <tp:member-ref>InitialOffset</tp:member-ref> should be respected in case
+ its value differs from the offset that was specified as an argument
+ to AcceptFile.
+ </tp:docstring>
+ <arg direction="in" name="Address_Type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of address the connection manager should listen on.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The type of access control the connection manager should apply to
+ the socket.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control_Param" type="v">
+ <tp:docstring>
+ A parameter for the access control type, to be interpreted as
+ specified in the documentation for the Socket_Access_Control enum.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Offset" type="t">
+ <tp:docstring>
+ The desired offset in bytes where the file transfer should start.
+ The offset is taken from the beginning of the file. Specifying an
+ offset of zero will start the transfer from the beginning of the
+ file. The offset that is actually given in the
+ <tp:member-ref>InitialOffset</tp:member-ref> property can differ
+ from this argument where the requested offset is not supported.
+ (For example, some protocols do not support offsets at all so
+ the InitialOffset property will always be 0.)
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Address" type="v">
+ <tp:docstring>
+ The address on which the connection manager will listen for
+ connections for this file transfer.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The given address type or access-control mechanism is not supported.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:docstring>
+ Your address type, access control, access control parameter,
+ offset, or a combination of all four is invalid.
+ </tp:docstring>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The file transfer is not in the Pending state, there isn't
+ or there is a local error with acquiring a socket.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="ProvideFile" tp:name-for-bindings="Provide_File">
+ <tp:docstring>
+ Provide the file for an outgoing file transfer which has been offered.
+ Opens a socket that the client can use to provide a file to the connection manager.
+ The channel MUST have been requested, and will change state
+ to Open when this method is called if its state was Accepted.
+ </tp:docstring>
+ <arg direction="in" name="Address_Type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of address the connection manager should listen on.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The type of access control the connection manager should apply to
+ the socket.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control_Param" type="v">
+ <tp:docstring>
+ A parameter for the access control type, to be interpreted as
+ specified in the documentation for the Socket_Access_Control enum.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Address" type="v">
+ <tp:docstring>
+ The address on which the connection manager will listen for
+ connections for this file transfer.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The given address type or access-control mechanism is not supported.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:docstring>
+ Your address type, access control, access control parameter, or
+ a combination of all three is invalid.
+ </tp:docstring>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ Channel is not an outgoing transfer, ProvideFile has already been called,
+ or there was a local error acquiring the socket.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="FileTransferStateChanged"
+ tp:name-for-bindings="File_Transfer_State_Changed">
+ <tp:docstring>
+ Emitted when the state of a file transfer changes.
+ </tp:docstring>
+ <arg name="State" type="u" tp:type="File_Transfer_State">
+ <tp:docstring>
+ The new state of the file transfer; see the File_Transfer_State enumeration.
+ </tp:docstring>
+ </arg>
+ <arg name="Reason" type="u" tp:type="File_Transfer_State_Change_Reason">
+ <tp:docstring>
+ The reason for the state change; see the File_Transfer_State_Change_Reason
+ enumeration.
+ The value will always be File_Transfer_State_Change_Reason_None, except
+ when changing state to cancelled.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="TransferredBytesChanged"
+ tp:name-for-bindings="Transferred_Bytes_Changed">
+ <tp:docstring>
+ Emitted when the number of transferred bytes changes. This will not be
+ signalled with every single byte change. Instead, the most frequent
+ this signal will be emitted is once a second. This should be
+ sufficient, and the <tp:member-ref>TransferredBytes</tp:member-ref>
+ property SHOULD NOT be polled.
+ </tp:docstring>
+ <arg name="Count" type="t">
+ <tp:docstring>
+ The number of already transferred bytes.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="InitialOffsetDefined"
+ tp:name-for-bindings="Initial_Offset_Defined">
+ <tp:docstring>
+ Emitted when the value of the <tp:member-ref>InitialOffset</tp:member-ref>
+ property has been negotiated. This signal MUST be emitted before the channel
+ becomes Open and clients have to use this offset when transferring the
+ file.
+ </tp:docstring>
+ <arg name="InitialOffset" type="t">
+ <tp:docstring>
+ The value of the <tp:member-ref>InitialOffset</tp:member-ref> property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="URIDefined"
+ tp:name-for-bindings="URI_Defined">
+ <tp:added version="0.21.9"/>
+ <tp:docstring>
+ Emitted when the value of the <tp:member-ref>URI</tp:member-ref>
+ property has been set. This signal MUST only be emitted on
+ incoming file transfers, and only if the handler sets the
+ <tp:member-ref>URI</tp:member-ref> property before
+ accepting the file.
+ </tp:docstring>
+ <arg name="URI" type="s">
+ <tp:docstring>
+ The value of the <tp:member-ref>URI</tp:member-ref> property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Room_List.xml b/qt4/spec/Channel_Type_Room_List.xml
new file mode 100644
index 000000000..98f745822
--- /dev/null
+++ b/qt4/spec/Channel_Type_Room_List.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Room_List" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2009 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright © 2005-2009 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.RoomList">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+
+ <tp:struct name="Room_Info" array-name="Room_Info_List">
+ <tp:member type="u" tp:type="Room_Handle" name="Handle"/>
+ <tp:member type="s" tp:type="DBus_Interface" name="Channel_Type"/>
+ <tp:member type="a{sv}" tp:type="String_Variant_Map" name="Info"/>
+ </tp:struct>
+
+ <property name="Server" tp:name-for-bindings="Server"
+ type="s" access="read">
+ <tp:docstring>
+ <p>For protocols with a concept of chatrooms on multiple servers
+ with different DNS names (like XMPP), the DNS name of the server
+ whose rooms are listed by this channel, e.g. "conference.jabber.org".
+ Otherwise, the empty string.</p>
+
+ <p>This property cannot change during the lifetime of the channel.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="GetListingRooms" tp:name-for-bindings="Get_Listing_Rooms">
+ <arg direction="out" type="b" name="In_Progress">
+ <tp:docstring>
+ A boolean indicating if room listing is in progress
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Check to see if there is already a room list request in progress
+ on this channel.
+ </tp:docstring>
+ </method>
+
+ <signal name="GotRooms" tp:name-for-bindings="Got_Rooms">
+ <arg name="Rooms" type="a(usa{sv})" tp:type="Room_Info[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structs containing:
+ <ul>
+ <li>an integer room handle</li>
+ <li>a string representing the D-Bus interface name of the channel type</li>
+ <li>a dictionary mapping string keys to variant boxed information</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when information about rooms on the server becomes available.
+ The array contains the room handle (as can be passed to the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">RequestChannel</tp:dbus-ref>
+ method with HANDLE_TYPE_ROOM), the channel
+ type, and a dictionary containing further information about the
+ room as available. The following well-known keys and types are
+ recommended for use where appropriate:</p>
+
+ <dl>
+ <dt>handle-name (s)</dt>
+ <dd>The identifier of the room (as would be returned by
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection">InspectHandles</tp:dbus-ref>)</dd>
+
+ <dt>name (s)</dt>
+ <dd>The human-readable name of the room if different from the handle</dd>
+
+ <dt>description (s)</dt>
+ <dd>A description of the room's overall purpose</dd>
+
+ <dt>subject (s)</dt>
+ <dd>The current subject of conversation in the room (as would
+ be returned by getting the string part of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ >Subject</tp:dbus-ref> property)</dd>
+
+ <dt>members (u)</dt>
+ <dd>The number of members in the room</dd>
+
+ <dt>password (b)</dt>
+ <dd>True if the room requires a password to enter</dd>
+
+ <dt>invite-only (b)</dt>
+ <dd>True if you cannot join the room, but must be invited</dd>
+
+ <dt>room-id (s)</dt>
+ <dd>The human-readable identifier of a chat room (as would be
+ returned by getting the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ >RoomID</tp:dbus-ref> property)</dd>
+
+ <dt>server (s)</dt>
+ <dd>The DNS name of the server hosting these channels (as would be
+ returned by getting the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ >Server</tp:dbus-ref> property)</dd>
+ </dl>
+ </tp:docstring>
+ </signal>
+ <method name="ListRooms" tp:name-for-bindings="List_Rooms">
+ <tp:docstring>
+ Request the list of rooms from the server. The
+ <tp:member-ref>ListingRooms</tp:member-ref> (True) signal should be
+ emitted when this request is being processed,
+ <tp:member-ref>GotRooms</tp:member-ref> when any room information is
+ received, and <tp:member-ref>ListingRooms</tp:member-ref> (False) when
+ the request is complete.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+ <method name="StopListing" tp:name-for-bindings="Stop_Listing">
+ <tp:docstring>
+ Stop the room listing if it's in progress, but don't close the channel.
+ The <tp:member-ref>ListingRooms</tp:member-ref> (False) signal should
+ be emitted when the listing stops.
+ </tp:docstring>
+ </method>
+ <signal name="ListingRooms" tp:name-for-bindings="Listing_Rooms">
+ <arg name="Listing" type="b">
+ <tp:docstring>A boolean indicating if room listing is in progress</tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted to indicate whether or not room listing request is currently
+ in progress.
+ </tp:docstring>
+ </signal>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for listing named channels available on the server. Once the
+ <tp:member-ref>ListRooms</tp:member-ref> method is called, it emits signals for rooms present on the
+ server, until you <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref> this
+ channel. In some cases, it may not be possible
+ to stop the deluge of information from the server. This channel should be
+ closed when the room information is no longer being displayed, so that the
+ room handles can be freed.</p>
+
+ <p>This channel type may be implemented as a singleton on some protocols, so
+ clients should be prepared for the eventuality that they are given a
+ channel that is already in the middle of listing channels. The
+ <tp:member-ref>ListingRooms</tp:member-ref> signal, or
+ <tp:member-ref>GetListingRooms</tp:member-ref> method, can be used to check
+ this.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Server_Authentication.xml b/qt4/spec/Channel_Type_Server_Authentication.xml
new file mode 100644
index 000000000..76599aa35
--- /dev/null
+++ b/qt4/spec/Channel_Type_Server_Authentication.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Server_Authentication" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2010 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.ServerAuthentication">
+ <tp:added version="0.21.5">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The type for a channel representing an authentication step with the
+ server. The actual authentication functionality is implemented by
+ the additional interface named in
+ <tp:member-ref>AuthenticationMethod</tp:member-ref>,
+ such as <tp:dbus-ref namespace="ofdT"
+ >Channel.Interface.SASLAuthentication</tp:dbus-ref>.</p>
+
+ <p>Future authentication steps also supported by this channel type might
+ include solving a captcha and/or agreeing to an EULA or terms-of-use
+ document; each of these would be represented by a channel with this
+ type, but a different
+ <tp:member-ref>AuthenticationMethod</tp:member-ref>.</p>
+
+ <p>Channels of this type will normally be be signalled and dispatched
+ while the <tp:dbus-ref namespace="ofdT">Connection</tp:dbus-ref>
+ owning them is in the CONNECTING state. They MAY also appear on a
+ Connection in the CONNECTED state, for instance if periodic
+ re-authentication is required.</p>
+
+ <p>Normally, only one channel of this type will
+ exist on a given Connection; if there is more than one, the handler
+ must complete authentication with each of them in turn.</p>
+
+ <p>Channels of this type cannot be requested with methods such as
+ <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>.
+ They always have <tp:dbus-ref
+ namespace="ofdT.Channel">Requested</tp:dbus-ref> = False,
+ <tp:dbus-ref
+ namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref> = None
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ = 0.</p>
+
+ <p>While it is CONNECTING, the Connection MUST NOT proceed with
+ connection, or signal
+ <tp:dbus-ref namespace="ofdT.Connection">StatusChanged</tp:dbus-ref>
+ to the CONNECTED state, until each channel of this type has either
+ been accepted as having a positive result (for instance, on SASL
+ channels this is done with the <tp:dbus-ref
+ namespace="ofdT.Channel.Interface.SASLAuthentication"
+ >AcceptSASL</tp:dbus-ref> method), or closed with the <tp:dbus-ref
+ namespace="ofdT.Channel">Close</tp:dbus-ref> method.</p>
+
+ <tp:rationale>
+ <p>ServerAuthentication channels normally represent the client
+ authenticating itself to the server, but can also be used for the
+ server to authenticate itself to the client (i.e. prove that it is
+ in fact the desired server and not an imposter). Until the
+ authentication handler has confirmed this, connection should not
+ continue.</p>
+ </tp:rationale>
+
+ <p>If a channel of this type is closed with the <tp:dbus-ref
+ namespace="ofdT.Channel">Close</tp:dbus-ref> method before
+ authentication has succeeded, this indicates that the Handler has
+ given up its attempts to authenticate or that no Handler is
+ available.</p>
+
+ <p>If this occurs, the connection manager MAY attempt to continue
+ connection (for instance, performing SASL authentication by using any
+ credentials passed to <tp:dbus-ref
+ namespace="ofdT.ConnectionManager">RequestConnection</tp:dbus-ref>,
+ for instance from the <tp:dbus-ref
+ namespace="ofdT">Account.Parameters</tp:dbus-ref>). If this fails
+ or has already been tried, the <tp:dbus-ref
+ namespace="ofdT">Connection</tp:dbus-ref> will
+ disconnect.</p>
+
+ <tp:rationale>
+ <p>In particular, the <tp:dbus-ref
+ namespace="ofdT">ChannelDispatcher</tp:dbus-ref> will close the
+ channel if it cannot find a handler.</p>
+ </tp:rationale>
+
+ <p>When the connection is done with the channel and it is no
+ longer needed, it is left open until either the connection state
+ turns to DISCONNECTED or the handler closes the channel. The
+ channel SHOULD NOT close itself once finished with.</p>
+ </tp:docstring>
+
+ <property name="AuthenticationMethod"
+ tp:name-for-bindings="Authentication_Method"
+ type="s" tp:type="DBus_Interface" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This property defines the method used for the authentication step
+ represented by this channel, which MUST be one of this channel's
+ <tp:dbus-ref namespace="ofdT.Channel">Interfaces</tp:dbus-ref>.</p>
+
+ <p>The initially-defined interface that can be used here is
+ <tp:dbus-ref namespace="ofdT"
+ >Channel.Interface.SASLAuthentication</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Server_TLS_Connection.xml b/qt4/spec/Channel_Type_Server_TLS_Connection.xml
new file mode 100644
index 000000000..97efd1b36
--- /dev/null
+++ b/qt4/spec/Channel_Type_Server_TLS_Connection.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Server_TLS_Connection"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2010 Collabora Limited </tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection">
+ <tp:added version="0.19.13">(as stable API)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type that carries a TLS certificate between a server
+ and a client connecting to it.</p>
+ <p>Channels of this kind always have <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Requested</tp:dbus-ref> = False,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = None and <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ = 0, and cannot be requested with methods such as <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>.
+ Also, they SHOULD be dispatched while the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ owning them is in the CONNECTING state.</p>
+ <p>In this case, handlers SHOULD accept or reject the certificate, using
+ the relevant methods on the provided object, or MAY just <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Close</tp:dbus-ref> the channel before doing so, to fall
+ back to a non-interactive verification process done inside the CM.</p>
+ <p>For example, channels of this kind can pop up while a client is
+ connecting to an XMPP server.</p>
+ </tp:docstring>
+
+ <property name="ServerCertificate" type="o" access="read"
+ tp:name-for-bindings="Server_Certificate"
+ tp:immutable='yeah'>
+ <tp:docstring>
+ <p>A <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Authentication">TLSCertificate</tp:dbus-ref>
+ containing the certificate chain as sent by the server,
+ and other relevant information.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Hostname" type="s" access="read"
+ tp:name-for-bindings="Hostname"
+ tp:immutable='sharks'>
+ <tp:added version="0.19.12"/>
+ <tp:docstring>
+ <p>The hostname or domain that the user expects to connect to. Clients
+ SHOULD use the <tp:member-ref>ReferenceIdentities</tp:member-ref>
+ property to verify the identity of the certificate. Clients MAY display
+ this hostname to the user as the expected identity. Clients SHOULD use
+ this property to lookup pinned certificates or other user preferences
+ for the connection.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="ReferenceIdentities" type="as" access="read"
+ tp:name-for-bindings="Reference_Identities"
+ tp:immutable='plz'>
+ <tp:added version="0.21.10">
+ If this property is not present, clients SHOULD use the
+ <tp:member-ref>Hostname</tp:member-ref> property as the reference
+ identity to validate server certificates against.
+ </tp:added>
+
+ <tp:docstring>
+ <p>The identities of the server we expect
+ <tp:member-ref>ServerCertificate</tp:member-ref> to certify; clients
+ SHOULD verify that <tp:member-ref>ServerCertificate</tp:member-ref>
+ matches one of these identities when checking its validity.</p>
+
+ <p>This property MUST NOT be the empty list; it MUST
+ contain the value of the <tp:member-ref>Hostname</tp:member-ref>
+ property. All other identities included in this property MUST be derived from
+ explicit user input or choices, such as <tp:dbus-ref
+ namespace='ofdT.Account'>Parameters</tp:dbus-ref> passed to
+ <tp:dbus-ref
+ namespace='ofdT.ConnectionManager'>RequestConnection</tp:dbus-ref>.</p>
+
+ <tp:rationale>
+ <p>The primary use for this property is for XMPP services hosted by
+ <a href='http://www.google.com/apps/intl/en/business/gmail.html'>Google
+ Apps</a>. When connecting to Google Talk using an
+ <tt>@gmail.com</tt> JID, the server correctly presents a
+ certificate for <tt>gmail.com</tt>; however, for domains hosted via
+ Google Apps, a certificate for <tt>talk.google.com</tt> is
+ offered, due to unresolved technical limitations.</p>
+
+ <p>If the user has explicitly chosen to create a <q>Google Talk</q>
+ account, then trusting a certificate for <tt>talk.google.com</tt>
+ is reasonable. To handle this case, the connection manager may add
+ the values of any or all of the <tt>server</tt>,
+ <tt>fallback-server</tt> and <tt>extra-identities</tt> parameters;
+ the Google Talk account creation user interface may set these
+ parameters appropriately, or the user may set them for accounts
+ with other services.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Stream_Tube.xml b/qt4/spec/Channel_Type_Stream_Tube.xml
new file mode 100644
index 000000000..63e7b2f50
--- /dev/null
+++ b/qt4/spec/Channel_Type_Stream_Tube.xml
@@ -0,0 +1,292 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Stream_Tube" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.StreamTube">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Interface.Tube"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A stream tube is a transport for ordered, reliable data transfer,
+ similar to SOCK_STREAM sockets.</p>
+
+ <p>When offering a stream tube, the initiating client creates a local
+ listening socket and offers it to the recipient client using the
+ <tp:member-ref>Offer</tp:member-ref> method. When a
+ recipient accepts a stream tube using the
+ <tp:member-ref>Accept</tp:member-ref> method, the
+ recipient's connection manager creates a new local listening socket.
+ Each time the recipient's client connects to this socket, the
+ initiator's connection manager proxies this connection to the
+ originally offered socket.</p>
+
+ </tp:docstring>
+
+ <method name="Offer" tp:name-for-bindings="Offer">
+ <tp:docstring>
+ Offer a stream tube exporting the local socket specified.
+ </tp:docstring>
+ <arg direction="in" name="address_type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of the listening address of the local service, as a member of
+ Socket_Address_Type.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="address" type="v">
+ <tp:docstring>
+ The listening address of the local service, as indicated by the
+ address_type.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="access_control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The access control the local service applies to the local socket,
+ specified so the connection manager can behave appropriately
+ when it connects.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="parameters" type="a{sv}"
+ tp:type="String_Variant_Map">
+ <tp:docstring>
+ The dictionary of arbitrary
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface.Tube">Parameters</tp:dbus-ref>
+ to send with the tube offer.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The contact associated with this channel doesn't have tube
+ capabilities.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The connection manager doesn't support the given address type
+ or access-control type.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Accept" tp:name-for-bindings="Accept">
+ <tp:docstring>
+ Accept a stream tube that's in the "local pending" state. The
+ connection manager will attempt to open the tube. The tube remains in
+ the "local pending" state until the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Tube">TubeChannelStateChanged</tp:dbus-ref>
+ signal is emitted.
+ </tp:docstring>
+ <arg direction="in" name="address_type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of address the connection manager should listen on.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="access_control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The type of access control the connection manager should apply to
+ the socket.</p>
+
+ <p>Note that if you plan to establish more than one connection
+ through the tube, the Socket_Access_Control_Port access control
+ can't be used as you can't connect more than once from the same
+ port.</p>
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="access_control_param" type="v">
+ <tp:docstring>
+ A parameter for the access control type, to be interpreted as
+ specified in the documentation for the Socket_Access_Control enum.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="address" type="v">
+ <tp:docstring>
+ The address on which the connection manager will listen for
+ connections to this tube. The client should not attempt to connect
+ to the address until the tube is open.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The access_control_param is invalid with the given access_control.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The given address type or access-control mechanism is not supported.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="NewRemoteConnection"
+ tp:name-for-bindings="New_Remote_Connection">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted each time a participant opens a new connection to its
+ socket.</p>
+
+ <p>This signal is only fired on the offering side.</p>
+ </tp:docstring>
+ <arg name="Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the participant who opened the new connection
+ </tp:docstring>
+ </arg>
+ <arg name="Connection_Param" type="v">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A parameter which can be used by the listening process to identify
+ the connection. Note that this parameter has a meaningful value
+ only in the Socket_Access_Control_Port and
+ Socket_Access_Control_Credentials cases. If a different
+ Socket_Access_Control has been chosen when offering the tube, this
+ parameter should be ignored.</p>
+
+ <p>In the Socket_Access_Control_Port case, the variant
+ contains a struct Socket_Address_IPv4 (or Socket_Address_IPv6)
+ containing the address from which the CM is connected to the client
+ application.</p>
+
+ <p>In the Socket_Access_Control_Credentials case, the variant
+ contains the byte (D-Bus signature 'y') that has been sent with
+ the credentials.</p>
+ </tp:docstring>
+ </arg>
+ <arg name="Connection_ID" type="u" tp:type="Stream_Tube_Connection_ID">
+ <tp:docstring>
+ The unique ID associated with this connection. This ID will be used
+ to identifiy the connection when reporting errors with
+ <tp:member-ref>ConnectionClosed</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="NewLocalConnection"
+ tp:name-for-bindings="New_Local_Connection">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the tube application connects to the CM's socket.</p>
+
+ <p>This signal is only fired on the accepting side.</p>
+ </tp:docstring>
+ <arg name="Connection_ID" type="u" tp:type="Stream_Tube_Connection_ID">
+ <tp:docstring>
+ The unique ID associated with this connection. This ID will be used
+ to identifiy the connection when reporting errors with
+ <tp:member-ref>ConnectionClosed</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="ConnectionClosed"
+ tp:name-for-bindings="Connection_Closed"
+ tp:type="Stream_Tube_Connection_Closed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a connection has been closed.</p>
+ </tp:docstring>
+ <arg name="Connection_ID" type="u" tp:type="Stream_Tube_Connection_ID">
+ <tp:docstring>
+ The ID of the connection.
+ </tp:docstring>
+ </arg>
+ <arg name="Error" type="s" tp:type="DBus_Error_Name">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of a D-Bus error describing the error that occurred.</p>
+
+ <p>The following errors can be used:</p>
+ <ul>
+ <li><code>org.freedesktop.Telepathy.Error.Cancelled</code>:
+ user closed the socket or the tube.</li>
+ <li><code>org.freedesktop.Telepathy.Error.ConnectionLost</code>:
+ the bytestream relaying connection's data has been broken.</li>
+ <li><code>org.freedesktop.Telepathy.Error.ConnectionRefused</code>:
+ the tube offer refused the connection.</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <arg name="Message" type="s">
+ <tp:docstring>
+ A debug message.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Service" type="s" access="read"
+ tp:name-for-bindings="Service">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p> A string representing the service name that will be used over the
+ tube. It should be a well-known TCP service name as defined by
+ <a href="http://www.iana.org/assignments/port-numbers">
+ http://www.iana.org/assignments/port-numbers</a> or
+ <a href="http://www.dns-sd.org/ServiceTypes.html">
+ http://www.dns-sd.org/ServiceTypes.html</a>, for instance
+ "rsync" or "daap".</p>
+ <p>When the tube is offered, the service name is transmitted to the
+ other end.</p>
+ <p>When requesting a channel with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>,
+ this property MUST be included in the request.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="SupportedSocketTypes" type="a{uau}"
+ tp:type="Supported_Socket_Map" access="read"
+ tp:name-for-bindings="Supported_Socket_Types">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A mapping from address types (members of Socket_Address_Type) to
+ arrays of access-control type (members of Socket_Access_Control)
+ that the connection manager supports for stream tubes with that
+ address type. For simplicity, if a CM supports offering a
+ particular type of tube, it is assumed to support accepting it.</p>
+
+ <p>A typical value for a host without IPv6 support:</p>
+
+ <pre>
+ {
+ Socket_Address_Type_IPv4:
+ [Socket_Access_Control_Localhost, Socket_Access_Control_Port,
+ Socket_Access_Control_Netmask],
+ Socket_Address_Type_Unix:
+ [Socket_Access_Control_Localhost, Socket_Access_Control_Credentials]
+ }
+ </pre>
+
+ <p>Connection Managers MUST support at least IPv4 with the localhost
+ access control.</p>
+
+ <p>When requesting a channel with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Requests.CreateChannel</tp:dbus-ref>,
+ this property MUST NOT be included in the request.</p>
+
+ </tp:docstring>
+ </property>
+
+ <tp:simple-type name="Stream_Tube_Connection_ID" type="u">
+ <tp:docstring>An identifier for a stream tube connection.
+ These are defined with the
+ <tp:member-ref>NewLocalConnection</tp:member-ref> or
+ <tp:member-ref>NewRemoteConnection</tp:member-ref> signals
+ and are used by <tp:member-ref>ConnectionClosed</tp:member-ref>
+ to identify the closed connection.
+ </tp:docstring>
+ </tp:simple-type>
+
+ </interface>
+
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Streamed_Media.xml b/qt4/spec/Channel_Type_Streamed_Media.xml
new file mode 100644
index 000000000..aa2b90345
--- /dev/null
+++ b/qt4/spec/Channel_Type_Streamed_Media.xml
@@ -0,0 +1,853 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Streamed_Media" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2009 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright © 2005-2009 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.StreamedMedia">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Channel.Interface.Group"/>
+
+ <tp:enum name="Media_Stream_Type" type="u"
+ array-name="Media_Stream_Type_List">
+ <tp:enumvalue suffix="Audio" value="0">
+ <tp:docstring>An audio stream</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Video" value="1">
+ <tp:docstring>A video stream</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Media_Stream_State" type="u">
+ <tp:enumvalue suffix="Disconnected" value="0">
+ <tp:docstring>The stream is disconnected.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Connecting" value="1">
+ <tp:docstring>The stream is trying to connect.</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Connected" value="2">
+ <tp:docstring>The stream is connected.</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Media_Stream_Direction" type="u">
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>Media are not being sent or received</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Send" value="1">
+ <tp:docstring>Media are being sent, but not received</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Receive" value="2">
+ <tp:docstring>Media are being received, but not sent</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Bidirectional" value="3">
+ <tp:docstring>Media are being sent and received</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:flags name="Media_Stream_Pending_Send" value-prefix="Media_Stream_Pending" type="u">
+ <tp:flag suffix="Local_Send" value="1">
+ <tp:docstring>
+ The local user has been asked to send media by the remote user.
+ Call <tp:member-ref>RequestStreamDirection</tp:member-ref> to
+ indicate whether or not this is acceptable.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Remote_Send" value="2">
+ <tp:docstring>
+ The remote user has been asked to send media by the local user.
+ The <tp:member-ref>StreamDirectionChanged</tp:member-ref> signal
+ will be emitted when the remote user accepts or rejects this
+ change.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:struct name="Media_Stream_Info" array-name="Media_Stream_Info_List">
+ <tp:member type="u" tp:type="Stream_ID" name="Identifier"/>
+ <tp:member type="u" tp:type="Contact_Handle" name="Contact"/>
+ <tp:member type="u" tp:type="Media_Stream_Type" name="Type"/>
+ <tp:member type="u" tp:type="Media_Stream_State" name="State"/>
+ <tp:member type="u" tp:type="Media_Stream_Direction" name="Direction"/>
+ <tp:member type="u" tp:type="Media_Stream_Pending_Send"
+ name="Pending_Send_Flags"/>
+ </tp:struct>
+
+ <tp:simple-type name="Stream_ID" type="u"
+ array-name="Stream_ID_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An unsigned integer identifying a stream within a channel.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <method name="ListStreams" tp:name-for-bindings="List_Streams">
+ <arg direction="out" type="a(uuuuuu)" tp:type="Media_Stream_Info[]"
+ name="Streams">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structs containing:
+ <ul>
+ <li>the stream identifier</li>
+ <li>the contact handle who the stream is with (or 0 if the stream
+ represents more than a single member)</li>
+ <li>the type of the stream</li>
+ <li>the current stream state</li>
+ <li>the current direction of the stream</li>
+ <li>the current pending send flags</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns an array of structs representing the streams currently active
+ within this channel. Each stream is identified by an unsigned integer
+ which is unique for each stream within the channel.
+ </tp:docstring>
+ </method>
+
+ <method name="RemoveStreams" tp:name-for-bindings="Remove_Streams">
+ <arg direction="in" name="Streams" type="au" tp:type="Stream_ID[]">
+ <tp:docstring>
+ An array of stream identifiers (as defined in
+ <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the given streams are removed. If all streams are
+ removed, the channel MAY close.</p>
+
+ <p>Clients SHOULD NOT attempt to terminate calls by removing all the
+ streams; instead, clients SHOULD terminate calls by removing the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Group.SelfHandle</tp:dbus-ref>
+ from the channel, using either
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">RemoveMembers</tp:dbus-ref>
+ or
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">RemoveMembersWithReason</tp:dbus-ref>.
+ </p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ A stream identifier is unknown
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestStreamDirection"
+ tp:name-for-bindings="Request_Stream_Direction">
+ <arg direction="in" name="Stream_ID" type="u">
+ <tp:docstring>
+ The stream identifier (as defined in
+ <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Stream_Direction" type="u" tp:type="Media_Stream_Direction">
+ <tp:docstring>
+ The desired stream direction (a value of MediaStreamDirection)
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request a change in the direction of an existing stream. In particular,
+ this might be useful to stop sending media of a particular type,
+ or inform the peer that you are no longer using media that is being
+ sent to you.</p>
+
+ <p>Depending on the protocol, streams which are no longer sending in
+ either direction should be removed and a
+ <tp:member-ref>StreamRemoved</tp:member-ref> signal emitted.
+ Some direction changes can be enforced locally (for example,
+ BIDIRECTIONAL -&gt; RECEIVE can be achieved by merely stopping sending),
+ others may not be possible on some protocols, and some need agreement
+ from the remote end. In this case, the MEDIA_STREAM_PENDING_REMOTE_SEND
+ flag will be set in the
+ <tp:member-ref>StreamDirectionChanged</tp:member-ref> signal, and the
+ signal
+ emitted again without the flag to indicate the resulting direction when
+ the remote end has accepted or rejected the change.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ A stream identifier is unknown
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The requested direction is not available on this stream
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestStreams" tp:name-for-bindings="Request_Streams">
+ <arg direction="in" name="Contact_Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ A contact handle with whom to establish the streams
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Types" type="au" tp:type="Media_Stream_Type[]">
+ <tp:docstring>
+ An array of stream types (values of MediaStreamType)
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a(uuuuuu)" tp:type="Media_Stream_Info[]"
+ name="Streams">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structs (in the same order as the given stream types)
+ containing:
+ <ul>
+ <li>the stream identifier</li>
+ <li>the contact handle who the stream is with (or 0 if the stream
+ represents more than a single member)</li>
+ <li>the type of the stream</li>
+ <li>the current stream state</li>
+ <li>the current direction of the stream</li>
+ <li>the current pending send flags</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that streams be established to exchange the given types of
+ media with the given member. In general this will try and establish a
+ bidirectional stream, but on some protocols it may not be possible to
+ indicate to the peer that you would like to receive media, so a
+ send-only stream will be created initially. In the cases where the
+ stream requires remote agreement (eg you wish to receive media from
+ them), the <tp:member-ref>StreamDirectionChanged</tp:member-ref> signal
+ will be emitted with the
+ MEDIA_STREAM_PENDING_REMOTE_SEND flag set, and the signal emitted again
+ with the flag cleared when the remote end has replied.</p>
+
+ <p>If streams of the requested types already exist, calling this
+ method results in the creation of additional streams. Accordingly,
+ clients wishing to have exactly one audio stream or exactly one
+ video stream SHOULD check for the current streams using
+ <tp:member-ref>ListStreams</tp:member-ref> before calling this
+ method.</p>
+ </tp:docstring>
+ <tp:changed version="0.17.2">
+ <p>It is valid to use a handle which is neither
+ a current nor pending member in this channel's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Group</tp:dbus-ref>
+ interface. If
+ so, that handle will be added to the remote-pending set only when
+ an attempt has actually been made to contact them. For further
+ call-state notification, use the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">CallState</tp:dbus-ref>
+ interface, if
+ supported. This usage was not allowed in spec versions below
+ 0.17.2.</p>
+ </tp:changed>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ A stream type given is invalid.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ A stream type given is not implemented by the connection manager.
+ Since 0.17.23, connection managers SHOULD raise this error
+ in preference to InvalidArgument.
+ <tp:rationale>
+ Connection managers can't know whether an unknown number
+ is a valid stream type that was introduced in a later spec
+ version.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ That contact's client does not implement one of the given stream
+ types. For this method, clients SHOULD consider this error and
+ NotCapable to be equivalent.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable">
+ <tp:docstring>
+ That contact's client does not implement one of the given stream
+ types. Since 0.17.23, connection managers SHOULD raise
+ this in preference to NotAvailable.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="StreamAdded" tp:name-for-bindings="Stream_Added">
+ <arg name="Stream_ID" type="u">
+ <tp:docstring>
+ The stream identifier (as defined in
+ <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+ <arg name="Contact_Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact handle who the stream is with (or 0 if it
+ represents more than a single member)
+ </tp:docstring>
+ </arg>
+ <arg name="Stream_Type" type="u" tp:type="Media_Stream_Type">
+ <tp:docstring>
+ The stream type (a value from MediaStreamType)
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a new stream has been added to this channel.
+ Clients SHOULD assume that the stream's
+ <tp:type>Media_Stream_State</tp:type> is initially Disconnected.</p>
+
+ <p>If a connection manager needs to represent the addition of a stream
+ whose state is already Connecting or Connected, it MUST do this
+ by emitting StreamAdded, closely followed by
+ <tp:member-ref>StreamStateChanged</tp:member-ref> indicating a
+ change to the appropriate state.</p>
+
+ <tp:rationale>
+ <p>Historically, it was not clear from the StreamAdded signal what
+ the state of the stream was. telepathy-spec 0.17.22
+ clarified this.</p>
+ </tp:rationale>
+
+ <p>Similarly, clients SHOULD assume that the initial
+ <tp:type>Media_Stream_Direction</tp:type> of a newly added stream
+ is Receive, and that the initial
+ <tp:type>Media_Stream_Pending_Send</tp:type> is
+ Pending_Local_Send.</p>
+
+ <p>If a connection manager needs to represent the addition of a stream
+ whose direction or pending-send differs from those initial values,
+ it MUST do so by emitting StreamAdded, closely followed by
+ <tp:member-ref>StreamDirectionChanged</tp:member-ref> indicating a
+ change to the appropriate direction and pending-send state.</p>
+
+ <tp:rationale>
+ <p>StreamAdded doesn't itself indicate the stream's direction; this
+ is unfortunate, but is preserved for compatibility.</p>
+
+ <p>This is the appropriate direction for streams added by a remote
+ contact on existing connection managers, and does not violate
+ user privacy by automatically sending audio or video (audio streams
+ start off muted, video streams start off not sending). For
+ streams added by the local user using the client receiving the
+ signal, the true direction can also be determined from the return
+ value of the <tp:member-ref>RequestStreams</tp:member-ref>
+ method.</p>
+
+ <p>Existing clients typically operate by maintaining a separate
+ idea of the directions that they would like the streams to have,
+ and enforcing these intended directions by calling
+ <tp:member-ref>RequestStreamDirection</tp:member-ref> whenever
+ needed.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </signal>
+
+ <signal name="StreamDirectionChanged"
+ tp:name-for-bindings="Stream_Direction_Changed">
+ <arg name="Stream_ID" type="u">
+ <tp:docstring>
+ The stream identifier (as defined in <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+ <arg name="Stream_Direction" type="u" tp:type="Media_Stream_Direction">
+ <tp:docstring>
+ The new stream direction (as defined in ListStreams)
+ </tp:docstring>
+ </arg>
+ <arg name="Pending_Flags" type="u" tp:type="Media_Stream_Pending_Send">
+ <tp:docstring>
+ The new pending send flags (as defined in ListStreams)
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the direction or pending flags of a stream are
+ changed.</p>
+
+ <p>If the MEDIA_STREAM_PENDING_LOCAL_SEND flag is set, the remote user
+ has requested that we begin sending on this stream.
+ <tp:member-ref>RequestStreamDirection</tp:member-ref>
+ should be called to indicate whether or not this change is
+ acceptable.</p>
+
+ <tp:rationale>
+ <p>This allows for a MSN-style user interface, "Fred has asked you
+ to enable your webcam. (Accept | Reject)", if desired.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </signal>
+
+ <signal name="StreamError" tp:name-for-bindings="Stream_Error">
+ <arg name="Stream_ID" type="u">
+ <tp:docstring>
+ The stream identifier (as defined in
+ <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+ <arg name="Error_Code" type="u" tp:type="Media_Stream_Error">
+ <tp:docstring>
+ A stream error number, one of the values of MediaStreamError
+ </tp:docstring>
+ </arg>
+ <arg name="Message" type="s">
+ <tp:docstring>
+ A string describing the error (for debugging purposes only)
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when a stream encounters an error.
+ </tp:docstring>
+ </signal>
+
+ <signal name="StreamRemoved" tp:name-for-bindings="Stream_Removed">
+ <arg name="Stream_ID" type="u">
+ <tp:docstring>
+ stream_id - the stream identifier (as defined in
+ <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when a stream has been removed from this channel.
+ </tp:docstring>
+ </signal>
+
+ <signal name="StreamStateChanged"
+ tp:name-for-bindings="Stream_State_Changed">
+ <arg name="Stream_ID" type="u">
+ <tp:docstring>
+ The stream identifier (as defined in
+ <tp:member-ref>ListStreams</tp:member-ref>)
+ </tp:docstring>
+ </arg>
+ <arg name="Stream_State" type="u" tp:type="Media_Stream_State">
+ <tp:docstring>
+ The new stream state (as defined in ListStreams)
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when a member's stream's state changes.
+ </tp:docstring>
+ </signal>
+
+ <property name="InitialAudio" tp:name-for-bindings="Initial_Audio"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If set to true in a channel request that will create a new channel,
+ the connection manager should immediately attempt to establish an
+ audio stream to the remote contact, making it unnecessary for the
+ client to call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.StreamedMedia">RequestStreams</tp:dbus-ref>.</p>
+
+ <p>If this property, or InitialVideo, is passed to EnsureChannel
+ (as opposed to CreateChannel), the connection manager SHOULD ignore
+ these properties when checking whether it can return an existing
+ channel as suitable; these properties only become significant when
+ the connection manager has decided to create a new channel.</p>
+
+ <p>If true on a requested channel, this indicates that the audio
+ stream has already been requested and the client does not need to
+ call RequestStreams, although it MAY still do so.</p>
+
+ <p>If true on an unrequested (incoming) channel, this indicates that
+ the remote contact initially requested an audio stream; this does
+ not imply that that audio stream is still active (as indicated by
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.StreamedMedia">ListStreams</tp:dbus-ref>).</p>
+
+ <p>This property is immutable (cannot change), and therefore SHOULD
+ appear wherever immutable properties are reported, e.g. <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signals.</p>
+
+ <tp:rationale><p>This reduces D-Bus round trips.</p></tp:rationale>
+
+ <p>Connection managers capable of signalling audio calls to contacts
+ SHOULD include a channel class in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref>
+ with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>
+ = <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = Contact in the fixed properties dictionary, and InitialAudio
+ (and also InitialVideo, if applicable) in the allowed properties
+ list. Clients wishing to discover whether a connection manager
+ can signal audio and/or video calls SHOULD use this information.</p>
+
+ <tp:rationale>
+ <p>Not all protocols support signalling video calls, and it would be
+ possible (although unlikely) to have a protocol where only video,
+ and not audio, could be signalled.</p>
+ </tp:rationale>
+
+ <p>Connection managers that support the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">ContactCapabilities</tp:dbus-ref>
+ interface SHOULD represent the capabilities of receiving audio
+ and/or video calls by including a channel class in
+ a contact's capabilities with ChannelType = StreamedMedia
+ in the fixed properties dictionary, and InitialAudio and/or
+ InitialVideo in the allowed properties list. Clients wishing to
+ discover whether a particular contact is likely to be able to
+ receive audio and/or video calls SHOULD use this information.</p>
+
+ <tp:rationale>
+ <p>Not all clients support video calls, and it would also be
+ possible (although unlikely) to have a client which could only
+ stream video, not audio.</p>
+ </tp:rationale>
+
+ <p>Clients that are willing to receive audio and/or video calls
+ SHOULD include the following among their channel classes if
+ calling <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref>
+ (clients of a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>
+ SHOULD instead arrange for the ChannelDispatcher to do this,
+ by including the filters in their <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ properties):</p>
+
+ <ul>
+ <li>{ ChannelType = StreamedMedia }</li>
+ <li>{ ChannelType = StreamedMedia, InitialAudio = true }
+ if receiving calls with audio is supported</li>
+ <li>{ ChannelType = StreamedMedia, InitialVideo = true }
+ if receiving calls with video is supported</li>
+ </ul>
+
+ <tp:rationale>
+ <p>Connection managers for protocols with capability discovery,
+ like XMPP, need this information to advertise the appropriate
+ capabilities for their protocol.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="InitialVideo" tp:name-for-bindings="Initial_Video"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same as <tp:member-ref>InitialAudio</tp:member-ref>, but for
+ a video stream. This property is immutable (cannot change).</p>
+
+ <p>In particular, note that if this property is false, this does not
+ imply that an active video stream has not been added, only that no
+ video stream was active at the time the channel appeared.</p>
+
+ <p>This property is the correct way to discover whether connection
+ managers, contacts etc. support video calls; it appears in
+ capabilities structures in the same way as InitialAudio.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="ImmutableStreams" tp:name-for-bindings="Immutable_Streams"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <tt>True</tt>, once streams have been requested for this channel
+ (either by setting <tp:member-ref>InitialAudio</tp:member-ref> or
+ <tp:member-ref>InitialVideo</tp:member-ref> when the channel is
+ requested, or by calling
+ <tp:member-ref>RequestStreams</tp:member-ref> on a channel with no
+ streams), a stream of a different content type cannot be added;
+ subsequent calls to <tp:member-ref>RequestStreams</tp:member-ref>
+ that attempt to do so will fail.</p>
+
+ <p>If this property is missing, clients SHOULD assume that it is false,
+ and thus that the channel's streams can be changed once the call has
+ started.</p>
+
+ <p>If this property is present in the "allowed" set in all of the
+ StreamedMedia entries in a contact's capabilities, then user
+ interfaces MAY choose to show a separate "call" option for each
+ class of call.</p>
+
+ <tp:rationale>
+ <p>For example, once an audio-only Google Talk call has started,
+ it is not possible to add a video stream; both audio and video
+ must be requested at the start of the call if video is desired.
+ User interfaces may use this pseudo-capability as a hint to
+ display separate "Audio call" and "Video call" buttons, rather
+ than a single "Call" button with the option to add and remove
+ video once the call has started for contacts without this flag.
+ </p>
+ </tp:rationale>
+
+ <p>This property is immutable, and therefore SHOULD be announced
+ in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>,
+ etc.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel that can send and receive streamed media such as audio or
+ video. Provides a number of methods for listing and requesting new
+ streams, and signals to indicate when streams have been added, removed
+ and changed status. The state of the call (ringing remotely, ringing
+ locally, answered, missed, etc.) are represented using the properties
+ and signals of the Group interface.</p>
+
+ <p>In general this should be used in conjunction with the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">MediaSignalling</tp:dbus-ref>
+ interface to exchange connection candidates and codec choices with
+ whichever component is responsible for the streams. However, in certain
+ applications where no candidate exchange is necessary (eg the streams
+ are handled by specialised hardware which is controlled directly by the
+ connection manager), the signalling interface can be omitted and this
+ channel type used simply to control the streams.</p>
+
+ <h4>Outgoing calls</h4>
+
+ <p>To make an audio-only call to a contact <tt>foo@example.com</tt>,
+ clients should call:</p>
+
+ <blockquote>
+ <pre>
+<tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">CreateChannel</tp:dbus-ref>({
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>: <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>: Contact,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>: 'foo@example.com',
+ <tp:member-ref>InitialAudio</tp:member-ref>: True,
+)</pre></blockquote>
+
+ <p>As always, <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ may be used in place of TargetID if the contact's handle is already
+ known. To make an audio-and-video call, the client should also specify
+ <tp:member-ref>InitialVideo</tp:member-ref>. The connection manager
+ SHOULD return a channel whose immutable properties contain the local
+ user as the <tp:dbus-ref namespace='ofdT.Channel'>InitiatorHandle</tp:dbus-ref>,
+ the remote contact as the <tp:dbus-ref namespace='ofdT.Channel'>TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref namespace='ofdT.Channel'>Requested</tp:dbus-ref> = <code>True</code>
+ (indicating that the call is outgoing); the <tp:dbus-ref
+ namespace='ofdT.Channel.Interface'>Group</tp:dbus-ref> interface should
+ initially have the local user in <tp:dbus-ref
+ namespace='ofdT.Channel.Interface.Group'>Members</tp:dbus-ref> and the remote
+ contact in <tp:dbus-ref
+ namespace='ofdT.Channel.Interface.Group'>RemotePendingMembers</tp:dbus-ref>, to
+ indicate that we are awaiting their response.</p>
+
+ <p>The contact answering the call is represented by the CM signalling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">MembersChanged</tp:dbus-ref>,
+ moving the remote contact to Members, with the remote contact as the
+ <var>Actor</var> and <var>Reason</var> <code>None</code>. The contact
+ rejecting the call is represented by both contacts being removed from
+ the group, with the remote contact as the <var>Actor</var> and
+ <var>Reason</var> set appropriately. The local user may hang up at any
+ time by calling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">RemoveMembersWithReason</tp:dbus-ref>
+ to remove themself, with an appropriate reason; the CM SHOULD relay the
+ reason to the remote contact, and emit MembersChanged removing both
+ contacts from the group with the self handle as the <var>Actor</var>.</p>
+
+ <p>(In the past, several other patterns have been used to place outgoing
+ calls; see
+ <a href="http://telepathy.freedesktop.org/wiki/Requesting%20StreamedMedia%20channels">'Requesting StreamedMedia Channels' on the Telepathy wiki</a>
+ for the details.)</p>
+
+ <h4>Incoming calls</h4>
+
+ <p>Incoming calls' immutable properties should contain <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ = Contact, both <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref> and
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">InitiatorHandle</tp:dbus-ref>
+ set to the remote contact, <tp:dbus-ref
+ namespace='ofdT.Channel'>Requested</tp:dbus-ref> = <code>False</code>
+ (indicating that this is an incoming call), and appropriate values of
+ <tp:member-ref>InitialAudio</tp:member-ref> and
+ <tp:member-ref>InitialVideo</tp:member-ref>; the Group interface should
+ initially have the local user in <tp:dbus-ref
+ namespace="ofdT.Channel.Interface.Group">LocalPendingMembers</tp:dbus-ref>
+ and the remote contact in <tp:dbus-ref
+ namespace="ofdT.Channel.Interface.Group">Members</tp:dbus-ref>,
+ indicating that the contact is awaiting our response.</p>
+
+ <p>To accept the call, use <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">AddMembers</tp:dbus-ref>
+ to move the local user to the group's members. To reject the call, use
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">RemoveMembersWithReason</tp:dbus-ref>
+ to remove the local member from the group, with an appropriate reason.
+ If the remote user ends the call before it is answered, this is
+ represented by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">MembersChanged</tp:dbus-ref>
+ removing both parties from the group with the remote contact as the
+ <var>Actor</var>, and <var>Reason</var> set appropriately.</p>
+
+ <p>Note that the call may end with the self handle as the
+ <var>Actor</var> without the user having chosen to reject the call, as
+ indicated by the nature of the <var>Reason</var>. Specifically, some
+ local component may time out the call (indicating this with reason
+ <code>No_Answer</code>; for example, the CM may have forwarded the call
+ to another number, as configured using <tp:dbus-ref
+ namespace='ofdT.Connection.Interface'>Forwarding.DRAFT</tp:dbus-ref>),
+ or something may have gone wrong with the call
+ (indicated by reason <code>Error</code>). Such calls SHOULD be
+ considered missed, just as if the remote contact had hung up before the
+ local user answered the call.</p>
+
+ <tp:rationale>
+ <p>This is a bit awkward, but these are the best ways we can represent
+ these situations. It's important to document which calls should be
+ considered missed, to ensure that the user can be notified.</p>
+ </tp:rationale>
+
+ <p>When the local user accepts an incoming call, the connection manager
+ SHOULD change the direction of any streams with pending local send
+ to be sending, without altering whether those streams are
+ receiving.</p>
+
+ <tp:rationale>
+ <p>This matches existing practice, and means that a client
+ can answer incoming calls and get an unmuted microphone/activated
+ webcam without having to take additional action to accept the
+ stream directions.</p>
+
+ <p>It does, however, introduce a race condition: a client believing
+ that it is accepting an audio-only call by calling AddMembers
+ can inadvertantly accept an audio + video call (and hence activate
+ sending from a webcam without the user's permission) if a video
+ stream is added just before AddMembers is processed. This race
+ should be removed when this specification is revised.</p>
+ </tp:rationale>
+
+ <h4>During a call</h4>
+
+ <p>If <tp:member-ref>ImmutableStreams</tp:member-ref> is
+ <code>False</code>, new streams may be requested using
+ <tp:member-ref>RequestStreams</tp:member-ref> (to add video to an
+ audio-only call, for instance), and existing streams may be removed using
+ <tp:member-ref>RemoveStreams</tp:member-ref> (for example, to downgrade
+ an audio-video call to audio-only). The call may be ended by calling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">RemoveMembers</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">RemoveMembersWithReason</tp:dbus-ref>; the call ending is signalled by the CM emitting <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">MembersChanged</tp:dbus-ref>,
+ removing both parties from the group.</p>
+
+ <h4>Handler filters</h4>
+
+ <p>For historical reasons, handlers must specify more than one filter if
+ they want to correctly advertise support for audio and/or video calls. If
+ they can handle channels using the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">MediaSignalling</tp:dbus-ref>
+ interface, they should also advertise various
+ <tp:type>Handler_Capability_Token</tp:type>s to indicate which codecs and
+ transports they support. See <tp:member-ref>InitialAudio</tp:member-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">MediaSignalling/video/h264</tp:dbus-ref>
+ for the gory details. In summary:</p>
+
+ <dl>
+ <dt>To advertise support for streamed media in general, include the
+ following filter in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>:</dt>
+ <dd><pre>
+{ '...Channel.ChannelType': '...Channel.Type.StreamedMedia' ,
+ '...Channel.TargetHandleType': Contact,
+}</pre></dd>
+
+ <dt>To advertise support for audio calls, also include the following
+ filter:</dt>
+ <dd><pre>
+{ '...Channel.ChannelType': '...Channel.Type.StreamedMedia' ,
+ '...Channel.TargetHandleType': Contact,
+ '...Channel.Type.StreamedMedia.InitialAudio': True,
+}</pre></dd>
+
+ <dt>To advertise support for video calls, also include the following
+ filter:</dt>
+ <dd><pre>
+{ '...Channel.ChannelType': '...Channel.Type.StreamedMedia' ,
+ '...Channel.TargetHandleType': Contact,
+ '...Channel.Type.StreamedMedia.InitialVideo': True,
+}</pre></dd>
+
+ <dt>If you use telepathy-farsight, and have H.264 support, you probably
+ want these <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">Capabilities</tp:dbus-ref>:</dt>
+ <dd><pre>
+[ "org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/ice-udp",
+ "org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/gtalk-p2p",
+ "org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/video/h264",
+]</pre></dd>
+ </dl>
+ </tp:docstring>
+
+ <tp:flags name="Channel_Media_Capabilities" value-prefix="Channel_Media_Capability" type="u">
+ <tp:docstring>
+ The channel-type-specific capability flags used for
+ Channel.Type.StreamedMedia in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.Interface.Capabilities</tp:dbus-ref>
+ interface. See the <tp:member-ref>InitialAudio</tp:member-ref>
+ property for details of the mechanisms that will replace this.
+ </tp:docstring>
+ <tp:flag suffix="Audio" value="1">
+ <tp:docstring>
+ The handle is capable of using audio streams within a media channel.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Video" value="2">
+ <tp:docstring>
+ The handle is capable of using video streams within a media channel.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="NAT_Traversal_STUN" value="4">
+ <tp:docstring>
+ The handle is capable of performing STUN to traverse NATs.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="NAT_Traversal_GTalk_P2P" value="8">
+ <tp:docstring>
+ The handle is capable of establishing Google Talk peer-to-peer
+ connections (as implemented in libjingle 0.3) to traverse NATs.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="NAT_Traversal_ICE_UDP" value="16">
+ <tp:docstring>
+ The handle is capable of establishing ICE UDP peer-to-peer
+ connections (as defined by the IETF MMUSIC working group) to traverse
+ NATs.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Immutable_Streams" value="32">
+ <tp:docstring>
+ Channels whose target handle is this contact will have
+ <tp:member-ref>ImmutableStreams</tp:member-ref> = <tt>True</tt>.
+ </tp:docstring>
+ </tp:flag>
+
+ </tp:flags>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Text.xml b/qt4/spec/Channel_Type_Text.xml
new file mode 100644
index 000000000..0cd4d5bb2
--- /dev/null
+++ b/qt4/spec/Channel_Type_Text.xml
@@ -0,0 +1,669 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Text" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2009 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright © 2005-2009 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.Text">
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:requires
+ interface="org.freedesktop.Telepathy.Channel.Interface.Messages"/>
+ <tp:changed version="0.21.5">The Messages interface is now
+ mandatory</tp:changed>
+
+ <tp:simple-type name="Message_ID" type="u" array-name="Message_ID_List">
+ <tp:docstring>
+ A unique-per-channel identifier for an incoming message. These
+ SHOULD be allocated in a way that minimizes collisions (in particular,
+ message IDs SHOULD NOT be re-used until all of the 32-bit integer
+ space has already been used).
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:struct name="Pending_Text_Message" array-name="Pending_Text_Message_List">
+ <tp:deprecated version="0.21.5">New APIs should use
+ an array of <tp:type>Message_Part</tp:type> instead.</tp:deprecated>
+ <tp:docstring>A struct (message ID, timestamp in seconds since
+ 1970-01-01 00:00 UTC, sender's handle, message type, flags, text)
+ representing a pending text message, as returned by
+ <tp:member-ref>ListPendingMessages</tp:member-ref>. The arguments of
+ the <tp:member-ref>Received</tp:member-ref> signal also match this
+ struct's signature.</tp:docstring>
+ <tp:member type="u" tp:type="Message_ID" name="Identifier"/>
+ <tp:member type="u" tp:type="Unix_Timestamp" name="Unix_Timestamp"/>
+ <tp:member type="u" tp:type="Contact_Handle" name="Sender"/>
+ <tp:member type="u" tp:type="Channel_Text_Message_Type"
+ name="Message_Type"/>
+ <tp:member type="u" tp:type="Channel_Text_Message_Flags" name="Flags"/>
+ <tp:member type="s" name="Text"/>
+ </tp:struct>
+
+ <method name="AcknowledgePendingMessages"
+ tp:name-for-bindings="Acknowledge_Pending_Messages">
+ <arg direction="in" name="IDs" type="au" tp:type="Message_ID[]">
+ <tp:docstring>
+ The IDs of the messages to acknowledge
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Inform the channel that you have handled messages by displaying them to
+ the user (or equivalent), so they can be removed from the pending queue.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ A given message ID was not found, so no action was taken
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetMessageTypes" tp:name-for-bindings="Get_Message_Types">
+ <tp:deprecated version="0.21.5">Consulting
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >MessageTypes</tp:dbus-ref> is preferred.
+ </tp:deprecated>
+ <arg direction="out" type="au" tp:type="Channel_Text_Message_Type[]"
+ name="Available_Types">
+ <tp:docstring>
+ An array of integer message types (ChannelTextMessageType)
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Return an array indicating which types of message may be sent on this
+ channel.
+ </tp:docstring>
+ </method>
+
+ <method name="ListPendingMessages"
+ tp:name-for-bindings="List_Pending_Messages">
+ <tp:deprecated version="0.21.5">Consulting
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >PendingMessages</tp:dbus-ref> is preferred.
+ </tp:deprecated>
+ <arg direction="in" name="Clear" type="b">
+ <tp:docstring>
+ If true, behave as if
+ <tp:member-ref>AcknowledgePendingMessages</tp:member-ref> had also
+ been called.
+ </tp:docstring>
+ <tp:deprecated version="0.17.3">
+ Setting this to true is NOT RECOMMENDED for clients that
+ have some sort of persistent message storage - clients SHOULD only
+ acknowledge messages after they have actually stored them, which is
+ impossible if this flag is true.</tp:deprecated>
+ </arg>
+ <arg direction="out" type="a(uuuuus)" tp:type="Pending_Text_Message[]"
+ name="Pending_Messages">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structs representing the pending queue. Each contains:
+ <ul>
+ <li>a numeric identifier</li>
+ <li>a Unix timestamp indicating when the message was received</li>
+ <li>the contact handle for the contact who sent the message</li>
+ <li>the message type, taken from ChannelTextMessageType</li>
+ <li>the bitwise-OR of the message flags from ChannelTextMessageFlags</li>
+ <li>the text of the message</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ List the messages currently in the pending queue, and optionally
+ remove then all.
+ </tp:docstring>
+ </method>
+
+ <signal name="LostMessage" tp:name-for-bindings="Lost_Message">
+ <tp:deprecated version="0.21.5">In practice, this signal
+ was not emitted, and does not have useful semantics.</tp:deprecated>
+ <tp:docstring>
+ This signal is emitted to indicate that an incoming message was
+ not able to be stored and forwarded by the connection manager
+ due to lack of memory.
+ </tp:docstring>
+ </signal>
+
+ <signal name="Received" tp:name-for-bindings="Received">
+ <tp:deprecated version="0.21.5">The
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >MessageReceived</tp:dbus-ref> signal is more informative.
+ </tp:deprecated>
+ <arg name="ID" type="u">
+ <tp:docstring>
+ A numeric identifier for acknowledging the message
+ </tp:docstring>
+ </arg>
+ <arg name="Timestamp" type="u" tp:type="Unix_Timestamp">
+ <tp:docstring>
+ A Unix timestamp indicating when the message was received
+ </tp:docstring>
+ </arg>
+ <arg name="Sender" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the contact who sent the message
+ </tp:docstring>
+ </arg>
+ <arg name="Type" type="u" tp:type="Channel_Text_Message_Type">
+ <tp:docstring>
+ The type of the message (normal, action, notice, etc.)
+ </tp:docstring>
+ </arg>
+ <arg name="Flags" type="u" tp:type="Channel_Text_Message_Flags">
+ <tp:docstring>
+ A bitwise OR of the message flags
+ </tp:docstring>
+ </arg>
+ <arg name="Text" type="s">
+ <tp:docstring>
+ The text of the message
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signals that a message with the given id, timestamp, sender, type
+ and text has been received on this channel. Applications that catch
+ this signal and reliably inform the user of the message should
+ acknowledge that they have dealt with the message with the
+ <tp:member-ref>AcknowledgePendingMessages</tp:member-ref> method.
+ </tp:docstring>
+ </signal>
+
+ <method name="Send" tp:name-for-bindings="Send">
+ <tp:deprecated version="0.21.5">The
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >SendMessage</tp:dbus-ref> method is more flexible.
+ </tp:deprecated>
+ <arg direction="in" name="Type" type="u" tp:type="Channel_Text_Message_Type">
+ <tp:docstring>
+ An integer indicating the type of the message
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Text" type="s">
+ <tp:docstring>
+ The message to send
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that a message be sent on this channel. When the message has
+ been submitted for delivery, this method will return and the
+ <tp:member-ref>Sent</tp:member-ref> signal will be emitted. If the
+ message cannot be submitted for delivery, the method returns an error
+ and no signal is emitted.</p>
+
+ <p>This method SHOULD return before the Sent signal is
+ emitted.</p>
+
+ <tp:rationale>
+ <p>When a Text channel implements the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Messages</tp:dbus-ref>
+ interface, that "SHOULD" becomes a "MUST".</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:enum name="Channel_Text_Send_Error" type="u">
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ An unknown error occurred
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Offline" value="1">
+ <tp:docstring>
+ The requested contact was offline
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Invalid_Contact" value="2">
+ <tp:docstring>
+ The requested contact is not valid
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Permission_Denied" value="3">
+ <tp:docstring>
+ The user does not have permission to speak on this channel
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Too_Long" value="4">
+ <tp:docstring>
+ The outgoing message was too long and was rejected by the server
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Not_Implemented" value="5">
+ <tp:docstring>
+ The channel doesn't support sending text messages to the requested
+ contact
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <signal name="SendError" tp:name-for-bindings="Send_Error">
+ <tp:deprecated version="0.21.5">Delivery reporting is now
+ provided by the <tp:dbus-ref namespace="ofdT.Channel.Interface"
+ >Messages</tp:dbus-ref> interface.
+ </tp:deprecated>
+ <arg name="Error" type="u" tp:type="Channel_Text_Send_Error">
+ <tp:docstring>
+ The error that occurred
+ </tp:docstring>
+ </arg>
+ <arg name="Timestamp" type="u" tp:type="Unix_Timestamp">
+ <tp:docstring>
+ The Unix timestamp indicating when the message was sent
+ </tp:docstring>
+ </arg>
+ <arg name="Type" type="u" tp:type="Channel_Text_Message_Type">
+ <tp:docstring>
+ The message type
+ </tp:docstring>
+ </arg>
+ <arg name="Text" type="s">
+ <tp:docstring>
+ The text of the message
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Signals that an outgoing message has failed to send. The error
+ will be one of the values from ChannelTextSendError.</p>
+
+ <p>This signal should only be emitted for messages for which
+ <tp:member-ref>Sent</tp:member-ref> has already been emitted and
+ <tp:member-ref>Send</tp:member-ref> has already returned success.</p>
+ </tp:docstring>
+ <tp:changed version="0.17.3">older spec versions claimed that SendError
+ was emitted <em>instead of</em> Sent, rather than <em>in addition
+ to</em> Sent. However, the 0.17.3+ semantics were what we'd always
+ actually implemented.</tp:changed>
+ </signal>
+
+ <signal name="Sent" tp:name-for-bindings="Sent">
+ <tp:deprecated version="0.21.5">The
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >MessageSent</tp:dbus-ref> signal is more informative.
+ </tp:deprecated>
+ <arg name="Timestamp" type="u" tp:type="Unix_Timestamp">
+ <tp:docstring>
+ Unix timestamp indicating when the message was sent
+ </tp:docstring>
+ </arg>
+ <arg name="Type" type="u" tp:type="Channel_Text_Message_Type">
+ <tp:docstring>
+ The message type (normal, action, notice, etc) from
+ ChannelTextMessageType
+ </tp:docstring>
+ </arg>
+ <arg name="Text" type="s">
+ <tp:docstring>
+ The text of the message. If the message was, or will be, altered
+ during transmission, this argument SHOULD reflect what other
+ contacts will receive rather than being a copy of the argument
+ to <tp:member-ref>Send</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Signals that a message has been submitted for sending.</p>
+ </tp:docstring>
+ </signal>
+
+ <tp:enum name="Channel_Text_Message_Type" type="u"
+ array-name="Channel_Text_Message_Type_List">
+ <tp:docstring>
+ The type of message.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Normal" value="0">
+ <tp:docstring>
+ An ordinary chat message. Unknown types SHOULD be treated like this.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Action" value="1">
+ <tp:docstring>
+ An action which might be presented to the user as
+ "* &lt;sender&gt; &lt;action&gt;", such as an IRC CTCP
+ ACTION (typically selected by the "/me" command). For example, the
+ text of the message might be "drinks more coffee".
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Notice" value="2">
+ <tp:docstring>
+ A one-off or automated message not necessarily expecting a reply
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Auto_Reply" value="3">
+ <tp:docstring>
+ An automatically-generated reply message.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Delivery_Report" value="4">
+ <tp:docstring>
+ A delivery report. This message type MUST NOT appear unless the
+ channel supports the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Messages</tp:dbus-ref>
+ interface; see <tp:type>Message_Part</tp:type> for the format that
+ delivery reports must take.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:flags name="Channel_Text_Message_Flags" value-prefix="Channel_Text_Message_Flag" type="u">
+ <tp:deprecated version="0.21.5">The
+ <tp:dbus-ref namespace="ofdT.Channel.Interface"
+ >Messages</tp:dbus-ref> interface has an extensible data structure
+ including separate booleans for most of these flags.
+ </tp:deprecated>
+ <tp:flag suffix="Truncated" value="1">
+ <tp:docstring>
+ The incoming message was truncated to a shorter length by the
+ server or the connection manager.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Non_Text_Content" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The incoming message contained non-text content which cannot be
+ represented by this interface, but has been signalled
+ in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">Messages</tp:dbus-ref>
+ interface.</p>
+
+ <p>Connection managers SHOULD only set this flag if the non-text
+ content appears to be relatively significant (exactly how
+ significant is up to the implementor). The intention is that
+ if this flag is set, clients using this interface SHOULD inform
+ the user that part of the message was not understood.</p>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Scrollback" value="4">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The incoming message was part of a replay of message history.</p>
+
+ <tp:rationale>
+ <p>In XMPP multi-user chat, a few past messages are replayed
+ when you join a chatroom. A sufficiently capable IRC connection
+ manager could also set this flag on historical messages when
+ connected to a proxy like bip or irssi-proxy. The existence
+ of this flag allows loggers and UIs to use better heuristics
+ when eliminating duplicates (a simple implementation made
+ possible by this flag would be to avoid logging scrollback
+ at all).</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Rescued" value="8">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The incoming message has been seen in a previous channel during
+ the lifetime of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>, but
+ had not been acknowledged
+ when that channel closed, causing an identical channel (the
+ channel in which the message now appears) to open.</p>
+
+ <tp:rationale>
+ <p>This means that a logger (which should already have seen the
+ message in the previous channel) is able to recognise and ignore
+ these replayed messages.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:property name="anonymous" type="b">
+ <tp:docstring>
+ True if people may join the channel without other members being made
+ aware of their identity.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="invite-only" type="b">
+ <tp:docstring>
+ True if people may not join the channel until they have been invited.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="limit" type="u">
+ <tp:docstring>
+ The limit to the number of members, if limited is true.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="limited" type="b">
+ <tp:docstring>
+ True if there is a limit to the number of channel members.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="moderated" type="b">
+ <tp:docstring>
+ True if channel membership is not sufficient to allow participation.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="name" type="s">
+ <tp:docstring>
+ A human-visible name for the channel, if it differs from the channel's
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">TargetID</tp:dbus-ref>.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="description" type="s">
+ <tp:docstring>
+ A human-readable description of the channel's overall purpose.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="password" type="s">
+ <tp:docstring>
+ The password required to enter the channel if password-required is true.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="password-required" type="b">
+ <tp:docstring>
+ True if a password must be provided to enter the channel.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="persistent" type="b">
+ <tp:docstring>
+ True if the channel will remain in existence on the server after all
+ members have left it.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="private" type="b">
+ <tp:docstring>
+ True if the channel is not visible to non-members.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="subject" type="s">
+ <tp:docstring>
+ A human-readable description of the current subject of conversation in
+ the channel, similar to /topic in IRC. This is equivalent to the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ >Subject</tp:dbus-ref> property in the Room interface which will replace
+ this Telepathy property.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="subject-contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ A contact handle representing who last modified the subject, or 0
+ if it isn't known. This is equivalent to the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ >Subject</tp:dbus-ref> property in the Room interface which will replace
+ this Telepathy property.
+ </tp:docstring>
+ </tp:property>
+ <tp:property name="subject-timestamp" type="u" tp:type="Unix_Timestamp">
+ <tp:docstring>
+ A unix timestamp indicating when the subject was last modified.
+ This is equivalent to the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface.Room.DRAFT"
+ >Subject</tp:dbus-ref> property in the Room interface which will replace
+ this Telepathy property.
+ </tp:docstring>
+ </tp:property>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A channel type for sending and receiving messages. This channel type
+ is primarily used for textual messages, but can also be used for
+ formatted text, text with "attachments", or binary messages on some
+ protocols.</p>
+
+ <p>Most of the methods and signals on this interface are deprecated,
+ since they only support plain-text messages with limited metadata.
+ See the mandatory <tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Messages</tp:dbus-ref>
+ interface for the modern equivalents.</p>
+
+ <p>When a message is received, an identifier is assigned and a
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >MessageReceived</tp:dbus-ref> signal emitted, and the message
+ is placed in a pending queue represented by the
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >PendingMessages</tp:dbus-ref> property.
+ When the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref>
+ for a channel has handled the message by showing it to the user
+ (or equivalent), it should acknowledge the receipt of that message
+ using the <tp:member-ref>AcknowledgePendingMessages</tp:member-ref>
+ method, and the message will then be removed from the pending queue.
+ Numeric identifiers for received messages may be reused over the
+ lifetime of the channel.</p>
+
+ <p>Sending messages can be requested using the
+ <tp:dbus-ref namespace="ofdT.Channel.Interface.Messages"
+ >SendMessage</tp:dbus-ref> method, which will return
+ successfully when the message has been submitted for sending, or
+ return an error with no signal emission if there is an immediate
+ failure. If a message is submitted for sending but delivery of the
+ message later fails, this is indicated by a delivery report, which
+ is received in the same way as an incoming message.</p>
+
+ <p>Simple one-to-one chats (such as streams of private messages in
+ XMPP or IRC) should be represented by a Text channel whose
+ <tp:dbus-ref namespace="ofdT.Channel">TargetHandleType</tp:dbus-ref>
+ is <tp:type>Handle_Type</tp:type>_Contact. The expected way to
+ request such a channel is to set the ChannelType, TargetHandleType,
+ and either TargetHandle or TargetID in a call to EnsureChannel.</p>
+
+ <p>Named chat rooms whose identity can be saved and used again later
+ (IRC channels, Jabber MUCs) are expected to be represented by Text
+ channels with <tp:type>Handle_Type</tp:type>_Room and the <tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Group</tp:dbus-ref>
+ interface. In protocols where a chatroom can be used as a continuation
+ of one or more one-to-one chats, these channels should also have the
+ <tp:dbus-ref namespace="ofdT.Channel.Interface">Conference</tp:dbus-ref>
+ interface.</p>
+
+ <p>Unnamed, transient chat rooms which cannot be rejoined by their
+ unique identifier (e.g. a conversation on MSN which has, or once had,
+ three or more participants) are expected to be represented by Text
+ channels with Handle_Type_None (and hence TargetHandle 0), the
+ <tp:dbus-ref namespace="ofdT.Channel.Interface">Group</tp:dbus-ref>
+ interface, and optionally the
+ <tp:dbus-ref namespace="ofdT.Channel.Interface">Conference</tp:dbus-ref>
+ interface.</p>
+
+ <p>On protocols like MSN where a conversation with a user is actually
+ just a nameless chat room starting with exactly two members, to which
+ more members can be invited, the initial one-to-one conversation
+ SHOULD be represented with Handle_Type_Contact. If a third participant
+ joins or is invited, this SHOULD be represented by signalling
+ a new <tp:dbus-ref
+ namespace="ofdT.Channel.Interface">Conference</tp:dbus-ref> channel
+ with the one-to-one channel in its <tp:dbus-ref
+ namespace="ofdT.Channel.Interface.Conference"
+ >InitialChannels</tp:dbus-ref>, migrating the underlying protocol
+ object from the one-to-one channel to the Conference channel,
+ and creating a new protocol-level conversation if the one-to-one
+ channel is re-used. See the Conference interface for more details.</p>
+
+ <tp:rationale>
+ <p>This keeps the presentation of all one-to-one conversations
+ uniform, and makes it easier to hand over a conversation from a
+ 1-1-specific UI to a more elaborate multi-user UI; while it does
+ require UIs to understand Conference to follow the
+ upgrade, UIs that will deal with XMPP need to understand Conference
+ anyway.</p>
+ </tp:rationale>
+
+ <p>If a channel of type Text is closed while it has pending messages,
+ the connection manager MUST allow this, but SHOULD open a new channel
+ to deliver those messages, signalling it as a new channel with the
+ <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal. The new channel should resemble the old channel, but have
+ Requested = FALSE regardless of its previous value; the InitiatorHandle
+ and InitiatorID should correspond to the sender of one of the pending
+ messages.</p>
+
+ <tp:rationale>
+ <p>In effect, this turns this situation, in which a client
+ is likely to lose messages:</p>
+
+ <ul>
+ <li>UI window is closed</li>
+ <li>message arrives</li>
+ <li>text channel emits Received</li>
+ <li>UI calls Close on text channel before it has seen the
+ Received signal</li>
+ <li>text channel emits Closed and closes</li>
+ </ul>
+
+ <p>into something nearly equivalent to this situation, which is
+ fine:</p>
+
+ <ul>
+ <li>UI window is closed</li>
+ <li>UI calls Close on text channel</li>
+ <li>text channel emits Closed and closes</li>
+ <li>message arrives</li>
+ <li>new text channel is created, connection emits NewChannels</li>
+ <li>(the same or a different) UI handles it</li>
+ </ul>
+
+ <p>Requested must be set to FALSE so the replacement channel
+ will be handled by something.</p>
+
+ <p>In practice, connection managers usually implement this by keeping
+ the same internal object that represented the old channel, adjusting
+ its properties, signalling that it was closed, then immediately
+ re-signalling it as a new channel.</p>
+ </tp:rationale>
+
+ <p>As a result, Text channels SHOULD implement <tp:dbus-ref
+ namespace="ofdT">Channel.Interface.Destroyable</tp:dbus-ref>.</p>
+
+ <tp:rationale>
+ <p>This "respawning" behaviour becomes problematic if there is no
+ suitable handler for Text channels, or if a particular message
+ repeatedly crashes the Text channel handler; a channel dispatcher
+ can't just Close() the channel in these situations, because
+ it will come back.</p>
+
+ <p>In these situations, the channel dispatcher needs a last-resort
+ way to destroy the channel and stop it respawning. It could either
+ acknowledge the messages itself, or use the Destroyable interface;
+ the Destroyable interface has the advantage that it's not
+ channel-type-dependent, so the channel dispatcher only has to
+ understand one extra interface, however many channel types
+ eventually need a distinction between Close and Destroy.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Channel_Type_Tubes.xml b/qt4/spec/Channel_Type_Tubes.xml
new file mode 100644
index 000000000..c0a973faa
--- /dev/null
+++ b/qt4/spec/Channel_Type_Tubes.xml
@@ -0,0 +1,615 @@
+<?xml version="1.0" ?>
+<node name="/Channel_Type_Tubes" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>
+ Copyright © 2007-2009 Collabora Limited
+ </tp:copyright>
+ <tp:license>
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Channel.Type.Tubes">
+
+ <tp:deprecated version="0.17.25">Client implementations
+ SHOULD use <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamTube</tp:dbus-ref> and
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">DBusTube</tp:dbus-ref>
+ instead.</tp:deprecated>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Channel"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A "tube" is a mechanism for arbitrary data transfer. Two types of
+ data transfer are currently specified: D-Bus messages, and streams of
+ bytes. Each tube has a service name, which is a string specifying the
+ kind of communication that takes place over it, and a dictionary of
+ arbitrary parameters. Tube parameters are commonly used for bootstrap
+ information such as usernames and passwords. Each tube is identified
+ by a locally unique identifier.</p>
+
+ <p>The Tubes channel type may be requested for handles of type
+ HANDLE_TYPE_CONTACT and HANDLE_TYPE_ROOM.</p>
+
+ <p>Stream tubes specify listening addresses using pairs of parameters
+ with signature 'u', 'v', where the integer 'u' is a member of
+ Socket_Address_Type and the v is dependent on the type of address.</p>
+ </tp:docstring>
+
+ <tp:simple-type name="Tube_ID" type="u">
+ <tp:docstring>An identifier for a tube. These are local to a Tubes
+ channel, and may not be assumed to be the same as the other
+ participants' idea of the tube identifier.</tp:docstring>
+ </tp:simple-type>
+
+ <tp:struct name="Tube_Info" array-name="Tube_Info_List">
+ <tp:docstring>A struct (tube ID, initiator handle, tube type,
+ service name, parameters, state) representing a tube, as returned
+ by ListTubes on the Tubes channel type.</tp:docstring>
+ <tp:member type="u" tp:type="Tube_ID" name="Identifier"/>
+ <tp:member type="u" tp:type="Contact_Handle" name="Initiator"/>
+ <tp:member type="u" tp:type="Tube_Type" name="Type"/>
+ <tp:member type="s" name="Service"/>
+ <tp:member type="a{sv}" tp:type="String_Variant_Map" name="Parameters"/>
+ <tp:member type="u" tp:type="Tube_State" name="State"/>
+ </tp:struct>
+
+ <tp:struct name="DBus_Tube_Member" array-name="DBus_Tube_Member_List">
+ <tp:docstring>Represents a participant in a multi-user D-Bus tube, as
+ returned by <tp:member-ref>GetDBusNames</tp:member-ref> and seen in the
+ <tp:member-ref>DBusNamesChanged</tp:member-ref> signal.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle">
+ <tp:docstring>
+ The handle of a participant in this D-Bus tube.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" tp:type="DBus_Unique_Name" name="Unique_Name">
+ <tp:docstring>
+ That participant's unique name.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:enum name="Tube_Type" type="u" array-name="Tube_Type_List">
+ <tp:enumvalue suffix="DBus" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The tube is D-Bus tube as described by the
+ org.freedesktop.Telepathy.Channel.Type.DBusTube interface.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Stream" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The tube is stream tube as described by the
+ org.freedesktop.Telepathy.Channel.Type.StreamTube interface.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Tube_State" type="u">
+ <tp:enumvalue suffix="Local_Pending" value="0">
+ <tp:docstring>
+ The tube is waiting to be accepted/closed locally.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Remote_Pending" value="1">
+ <tp:docstring>
+ The tube is waiting to be accepted/closed remotely.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Open" value="2">
+ <tp:docstring>
+ The tube is open for traffic.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:mapping name="Supported_Socket_Map">
+ <tp:docstring>The supported socket address and access-control types
+ for tubes. See GetAvailableStreamTubeTypes.</tp:docstring>
+ <tp:member name="Address_Type" type="u" tp:type="Socket_Address_Type"/>
+ <tp:member name="Access_Control" type="au"
+ tp:type="Socket_Access_Control[]"/>
+ </tp:mapping>
+
+ <method name="GetAvailableStreamTubeTypes"
+ tp:name-for-bindings="Get_Available_Stream_Tube_Types">
+ <tp:docstring>List the available address types and access-control types
+ for stream tubes.</tp:docstring>
+ <arg direction="out" type="a{uau}" tp:type="Supported_Socket_Map"
+ name="Available_Stream_Tube_Types">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A mapping from address types (members of Socket_Address_Type) to
+ arrays of access-control type (members of Socket_Access_Control)
+ that the connection manager supports for stream tubes with that
+ address type. For simplicity, if a CM supports offering a
+ particular type of tube, it is assumed to support accepting it.</p>
+
+ <p>A typical value for a host without IPv6 support:</p>
+
+ <pre>
+ {
+ Socket_Address_Type_IPv4:
+ [Socket_Access_Control_Localhost, Socket_Access_Control_Port,
+ Socket_Access_Control_Netmask],
+ Socket_Address_Type_Unix:
+ [Socket_Access_Control_Localhost, Socket_Access_Control_Credentials]
+ }
+ </pre>
+
+ <p>If stream tubes are not supported, this will be an empty
+ dictionary.</p>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="GetAvailableTubeTypes"
+ tp:name-for-bindings="Get_Available_Tube_Types">
+ <arg direction="out" type="au" tp:type="Tube_Type[]"
+ name="Available_Tube_Types">
+ <tp:docstring>
+ An array of the available tube types, as defined by the Tube_Type
+ enum.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="ListTubes" tp:name-for-bindings="List_Tubes">
+ <arg direction="out" type="a(uuusa{sv}u)" tp:type="Tube_Info[]"
+ name="Tubes">
+ <tp:docstring>
+ Return an array of tuples, each representing a tube, with the
+ following members:
+
+ <ul>
+ <li>the tube's ID</li>
+ <li>the tube's initiator</li>
+ <li>the tube's type</li>
+ <li>the tube's service</li>
+ <li>the tube's parameters</li>
+ <li>the tube's state</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <!-- this tp:name-for-bindings is ugly, but compatible with
+ the code generation in telepathy-glib versions that did not use
+ tp:name-for-bindings -->
+ <method name="OfferDBusTube" tp:name-for-bindings="Offer_D_Bus_Tube">
+ <tp:docstring>
+ Offers a D-Bus tube providing the service specified.
+ </tp:docstring>
+ <arg direction="in" name="Service" type="s">
+ <tp:docstring>
+ A string representing the service name that will be used over the
+ tube.
+ It should be a well-known D-Bus service name, of the form
+ com.example.ServiceName.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Parameters" type="a{sv}"
+ tp:type="String_Variant_Map">
+ <tp:docstring>
+ A dictionary of properties for the new tube; the allowable keys,
+ types and values are defined by the service. Connection managers
+ must support the value being any primitive (non-container)
+ D-Bus type, or a byte array 'ay'.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="u" tp:type="Tube_ID" name="Tube_ID">
+ <tp:docstring>
+ The ID of the new tube.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The contact associated with this channel doesn't have tubes
+ capabilities.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The connection manager doesn't support D-Bus tubes.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="OfferStreamTube" tp:name-for-bindings="Offer_Stream_Tube">
+ <tp:docstring>
+ Offer a stream tube exporting the local socket specified.
+ </tp:docstring>
+ <arg direction="in" name="Service" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ A string representing the service name that will be used over the
+ tube.
+ It should be a well-known TCP service name as defined by
+ <a href="http://www.iana.org/assignments/port-numbers">
+ http://www.iana.org/assignments/port-numbers</a> or
+ <a href="http://www.dns-sd.org/ServiceTypes.html">
+ http://www.dns-sd.org/ServiceTypes.html</a>, for instance
+ "rsync" or "daap".
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Parameters" type="a{sv}"
+ tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary of properties for the new tube; the allowable keys,
+ types and values are defined by the service. Connection managers
+ must support the value being any primitive (non-container)
+ D-Bus type, or a byte array 'ay'.</p>
+ <p>These should usually be the same key-value pairs specified for
+ use in the DNS-SD TXT record for that service.</p>
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Address_Type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of the listening address of the local service, as a member of
+ Socket_Address_Type.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Address" type="v">
+ <tp:docstring>
+ The listening address of the local service, as indicated by the
+ address_type.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The access control the local service applies to the local socket,
+ specified so the connection manager can behave appropriately
+ when it connects.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control_Param" type="v">
+ <tp:docstring>
+ A parameter for the access control type, to be interpreted as
+ specified in the documentation for the Socket_Access_Control enum.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="u" tp:type="Tube_ID" name="Tube_ID">
+ <tp:docstring>
+ The ID of the new tube.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The contact associated with this channel doesn't have tube
+ capabilities.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The connection manager doesn't support stream tubes, or
+ does not support the given address type or access-control type.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="NewTube" tp:name-for-bindings="New_Tube">
+ <tp:docstring>
+ Emitted when a tube is created.
+ </tp:docstring>
+ <arg name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the new tube.
+ </tp:docstring>
+ </arg>
+ <arg name="Initiator" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the contact who initiated the tube.
+ </tp:docstring>
+ </arg>
+ <arg name="Type" type="u" tp:type="Tube_Type">
+ <tp:docstring>
+ The tube type, as defined by the Tube_Type enum.
+ </tp:docstring>
+ </arg>
+ <arg name="Service" type="s">
+ <tp:docstring>
+ A string representing the service that will be used over the tube.
+ </tp:docstring>
+ </arg>
+ <arg name="Parameters" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring>
+ The new tube's properties.
+ </tp:docstring>
+ </arg>
+ <arg name="State" type="u" tp:type="Tube_State">
+ <tp:docstring>
+ The new tube's state.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <!-- this tp:name-for-bindings is ugly, but compatible with
+ the code generation in telepathy-glib versions that did not use
+ tp:name-for-bindings -->
+ <method name="AcceptDBusTube" tp:name-for-bindings="Accept_D_Bus_Tube">
+ <tp:docstring>
+ Accept a D-Bus tube that's in the "local pending" state. The
+ connection manager will attempt to open the tube. The tube remains in
+ the "local pending" state until the TubeStateChanged signal is
+ emitted.
+ </tp:docstring>
+ <arg direction="in" name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube to accept.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Address" type="s">
+ <tp:docstring>
+ The string describing the address of the private bus. The client
+ should not attempt to connect to the address until the tube is open.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The given tube ID is invalid or does not refer to a D-Bus
+ tube.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AcceptStreamTube" tp:name-for-bindings="Accept_Stream_Tube">
+ <tp:docstring>
+ Accept a stream tube that's in the "local pending" state. The
+ connection manager will attempt to open the tube. The tube remains in
+ the "local pending" state until the TubeStateChanged signal is
+ emitted.
+ </tp:docstring>
+ <arg direction="in" name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube to accept.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Address_Type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of address the connection manager should listen on.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control" type="u" tp:type="Socket_Access_Control">
+ <tp:docstring>
+ The type of access control the connection manager should apply to
+ the socket.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Access_Control_Param" type="v">
+ <tp:docstring>
+ A parameter for the access control type, to be interpreted as
+ specified in the documentation for the Socket_Access_Control enum.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Address" type="v">
+ <tp:docstring>
+ The address on which the connection manager will listen for
+ connections to this tube. The client should not attempt to connect
+ to the address until the tube is open.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The given tube ID is invalid or does not refer to a stream
+ tube.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The given address type or access-control mechanism is not supported.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="TubeStateChanged" tp:name-for-bindings="Tube_State_Changed">
+ <tp:docstring>
+ Emitted when the state of a tube changes.
+ </tp:docstring>
+ <arg name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube that changed state.
+ </tp:docstring>
+ </arg>
+ <arg name="State" type="u" tp:type="Tube_State">
+ <tp:docstring>
+ The new state of the tube; see the Tube_State enumeration.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="CloseTube" tp:name-for-bindings="Close_Tube">
+ <tp:docstring>
+ Close a tube.
+ </tp:docstring>
+ <arg direction="in" name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube to close.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument" />
+ </tp:possible-errors>
+ </method>
+
+ <signal name="TubeClosed" tp:name-for-bindings="Tube_Closed">
+ <tp:docstring>
+ Emitted when a tube has been closed. The ID of a closed tube is no
+ longer valid. The ID may later be reused for a new tube.
+ </tp:docstring>
+ <arg name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube that was closed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <!-- this tp:name-for-bindings is ugly, but compatible with
+ the code generation in telepathy-glib versions that did not use
+ tp:name-for-bindings -->
+ <method name="GetDBusTubeAddress"
+ tp:name-for-bindings="Get_D_Bus_Tube_Address">
+ <tp:docstring>
+ For a D-Bus tube, return a string describing the address of the
+ private bus.
+ </tp:docstring>
+ <arg direction="in" name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube to get an address for.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="s" name="Address">
+ <tp:docstring>
+ The bus address.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The tube is not a D-Bus tube.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This tube is not in the "open" state.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <!-- this tp:name-for-bindings is ugly, but compatible with
+ the code generation in telepathy-glib versions that did not use
+ tp:name-for-bindings -->
+ <method name="GetDBusNames" tp:name-for-bindings="Get_D_Bus_Names">
+ <tp:docstring>
+ For a multi-user (i.e. Handle_Type_Room) D-Bus tube, obtain a mapping
+ between contact handles and their unique bus names on this tube.
+ </tp:docstring>
+ <arg direction="in" name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube to get names for.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a(us)" tp:type="DBus_Tube_Member[]"
+ name="DBus_Names">
+ <tp:docstring>
+ An array of structures, each containing a contact handle and a D-Bus
+ bus name.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The tube is not a multi-user D-Bus tube.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This tube is not in the "open" state.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <!-- this tp:name-for-bindings is ugly, but compatible with
+ the code generation in telepathy-glib versions that did not use
+ tp:name-for-bindings -->
+ <signal name="DBusNamesChanged" tp:name-for-bindings="D_Bus_Names_Changed">
+ <tp:docstring>
+ Emitted on a multi-user (i.e. Handle_Type_Room) D-Bus tube when a
+ participant opens or closes the tube.
+ </tp:docstring>
+ <arg name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube whose names have changed.
+ </tp:docstring>
+ </arg>
+ <arg name="Added" type="a(us)" tp:type="DBus_Tube_Member[]">
+ <tp:docstring>
+ Array of handles and D-Bus names of new participants.
+ </tp:docstring>
+ </arg>
+ <arg name="Removed" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ Array of handles of former participants.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="GetStreamTubeSocketAddress"
+ tp:name-for-bindings="Get_Stream_Tube_Socket_Address">
+ <tp:docstring>
+ For a stream tube, obtain the address of the socket used to
+ communicate over this tube.
+ </tp:docstring>
+ <arg direction="in" name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the stream tube to get the socket for.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Address_Type" type="u" tp:type="Socket_Address_Type">
+ <tp:docstring>
+ The type of the listening address of the socket, as a member of
+ Socket_Address_Type.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Address" type="v">
+ <tp:docstring>
+ The listening address of the socket, as indicated by the
+ address_type.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The tube is not a stream tube.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ This tube is not in the "open" state.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="StreamTubeNewConnection"
+ tp:name-for-bindings="Stream_Tube_New_Connection">
+ <tp:docstring>
+ Emitted on a stream tube when a participant opens a new connection
+ to its socket.
+ </tp:docstring>
+ <arg name="ID" type="u" tp:type="Tube_ID">
+ <tp:docstring>
+ The ID of the tube
+ </tp:docstring>
+ </arg>
+ <arg name="Handle" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the participant who opened the new connection
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Client.xml b/qt4/spec/Client.xml
new file mode 100644
index 000000000..19f691495
--- /dev/null
+++ b/qt4/spec/Client.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" ?>
+<node name="/Client"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Client">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Telepathy clients use connection managers, the channel dispatcher
+ and optionally the account manager to provide useful
+ functionality.</p>
+
+ <p>User interface processes are the obvious example of Telepathy
+ clients, but they can provide other functionality, such as
+ address-book synchronization.</p>
+
+ <p>Every running or activatable process with a well-known
+ name of the form org.freedesktop.Telepathy.Client.<em>clientname</em>
+ should be probed by the channel dispatcher to discover its
+ capabilities. Each client is either an <em>observer</em>, an
+ <em>approver</em>, a <em>channel handler</em>, or some combination
+ of these.</p>
+
+ <tp:rationale>
+ <p>Activatable services (those with a D-Bus <code>.service</code>
+ file) must be supported so that we can run clients
+ in response to channel creation.</p>
+
+ <p>Non-activatable services (those that do not register a D-Bus
+ <code>.service</code> file for their well-known name, but do
+ request it at runtime) must be supported so that we can have
+ programs that process channels, but only if they are already
+ running - for instance, a full-screen media centre
+ application might do this.</p>
+ </tp:rationale>
+
+ <p>The client name, <em>clientname</em>, MUST be a non-empty string of
+ ASCII digits, letters, dots and/or underscores, starting with a
+ letter, and without sets of two consecutive dots or a dot
+ followed by a digit. For non-activatable services, it MAY contain a
+ part that is generated per instance at runtime.</p>
+
+ <tp:rationale>
+ <p>If each of a client Foo's instances should be able to manipulate
+ channels separately, the instance with unique name
+ <code>:1.25</code> might request a well-known name like
+ <code>org.freedesktop.Telepathy.Client.Foo._1._25</code>.</p>
+
+ <p>(Note that well-known bus-name components may not start with a
+ digit, so o.f.T.Client.Foo.1.25 would not be acceptable.)</p>
+ </tp:rationale>
+
+ <p>Each Client MUST export an object whose object path may be
+ determined by replacing '.' with '/' in the well-known name and
+ prepending '/'. This object represents its API as a Telepathy
+ client; the channel dispatcher will call its methods and read
+ its properties when appropriate.</p>
+
+ <p>As an optimization, activatable clients SHOULD install a file
+ <code><a href="http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html">$XDG_DATA_DIRS</a>/telepathy/clients/<em>clientname</em>.client</code>
+ containing a cached version of its immutable properties,
+ so that for most clients, the channel dispatcher can
+ just read a file to discover capabilities, instead of
+ having to service-activate the client immediately in order to fetch
+ its read-only properties. However, the D-Bus API is canonical, and
+ the channel dispatcher MUST support clients without such a file.</p>
+
+ <p>Non-activatable clients MAY install a <code>.client</code> file,
+ but there's not much point in them doing so.</p>
+
+ <p>The .client files MUST contain UTF-8 text with the same syntax
+ as
+ <a href="http://standards.freedesktop.org/desktop-entry-spec/latest/">Desktop
+ Entry files</a> (although the allowed groups, keys and values differ).
+ Every <code>.client</code> file MUST contain a group whose name is
+ the name of this interface.</p>
+
+ <p>The groups, keys and values in the <code>.client</code> file are
+ defined by individual interfaces. Each interface that can usefully
+ cache information in the <code>.client</code> file SHOULD correspond
+ to a group with the same name.</p>
+ </tp:docstring>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" access="read" tp:type="DBus_Interface[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of the extra interfaces provided by this client.
+ This SHOULD include at least one of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Client.Observer</tp:dbus-ref>,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Client.Approver</tp:dbus-ref> or
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Client.Handler</tp:dbus-ref>.</p>
+
+ <p>In the <code>.client</code> file, this is represented by key
+ "<code>Interfaces</code>" in the group named after this interface.
+ The value of the key is a list of interface names each followed by
+ a semicolon (so it always ends with a semicolon unless it is empty),
+ i.e. a key of type "strings" as described in the Desktop Entry
+ specification.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Client_Approver.xml b/qt4/spec/Client_Approver.xml
new file mode 100644
index 000000000..12cbc76ac
--- /dev/null
+++ b/qt4/spec/Client_Approver.xml
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<node name="/Client_Approver"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Client.Approver">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Client"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Approvers are clients that notify the user that new channels have
+ been created by a contact, and allow the user to accept or reject
+ those channels. The new channels are represented by a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation</tp:dbus-ref>
+ object, which is passed to the
+ <tp:member-ref>AddDispatchOperation</tp:member-ref> method.</p>
+
+ <tp:rationale>
+ <p>For instance, Empathy's tray icon, or the answer/reject window
+ seen when a Maemo device receives a VoIP call, should be
+ Approvers.</p>
+ </tp:rationale>
+
+ <p>Approvers can also select which channel handler will be used for the
+ channel, for instance by offering the user a list of possible
+ handlers rather than just an accept/reject choice.
+ However, the Channel Dispatcher must be able to prioritize
+ possible handlers on its own using some reasonable heuristic,
+ probably based on user configuration.</p>
+
+ <p>It is possible (and useful) to have an approver and
+ a channel handler in the same process; this is particularly useful
+ if a channel handler wants to claim responsibility for particular
+ channels itself.</p>
+
+ <p>All approvers are notified simultaneously. For instance, in a
+ desktop system, there might be one approver that displays a
+ notification-area icon, one that is part of a contact list
+ window and highlights contacts there, and one that is part
+ of a full-screen media player.</p>
+
+ <p>Any approver can approve the handling of a channel dispatch operation
+ with a particular channel handler by calling the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">HandleWith</tp:dbus-ref>
+ method. Approvers can also attempt to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">Claim</tp:dbus-ref>
+ channels; if this succeeds, the approver may handle the channels
+ itself (if it is also a Handler), or close the channels in order to
+ reject them.</p>
+
+ <p>At the D-Bus level, there is no "reject" operation: approvers wishing
+ to reject channels SHOULD call the Claim method, then (if it succeeds)
+ close the channels in any way they see fit.</p>
+
+ <p>The first approver to reply gets its decision acted on; any other
+ approvers that reply at approximately the same time will get a D-Bus
+ error, indicating that the channel has already been dealt with.</p>
+
+ <p>Approvers should usually prompt the user and ask for
+ confirmation, rather than dispatching the channel to a handler
+ straight away.</p>
+
+ <p>Non-interactive approvers can also be implemented as
+ <tp:dbus-ref namespace="ofdT.Client">Observer</tp:dbus-ref>s as
+ described in the interface description.</p>
+ </tp:docstring>
+
+ <property name="ApproverChannelFilter"
+ tp:name-for-bindings="Approver_Channel_Filter"
+ type="aa{sv}" access="read" tp:type="Channel_Class[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A specification of the channels in which this approver is
+ interested. The <tp:member-ref>AddDispatchOperation</tp:member-ref>
+ method should be called by the channel dispatcher whenever at least
+ one of the channels in a channel dispatch operation matches this
+ description.</p>
+
+ <p>This property works in exactly the same way as the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Client.Observer.ObserverChannelFilter</tp:dbus-ref>
+ property. In particular, it cannot change while the approver process
+ continues to own the corresponding Client bus name.</p>
+
+ <p>In the .client file, it is represented in the
+ same way as ObserverChannelFilter, but the group has the same
+ name as this interface and the keys start with
+ ApproverChannelFilter instead of ObserverChannelFilter.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="AddDispatchOperation"
+ tp:name-for-bindings="Add_Dispatch_Operation">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by the channel dispatcher when a ChannelDispatchOperation
+ in which the approver has registered an interest is created,
+ or when the approver starts up while such channel dispatch
+ operations already exist.</p>
+
+ <p>The channel dispatcher SHOULD call this method on all approvers
+ at the same time. If an approver returns an error from this method,
+ the approver is assumed to be faulty.</p>
+
+ <p>If no approvers return from this method
+ successfully (including situations where there are no matching
+ approvers at all), the channel dispatcher SHOULD consider this
+ to be an error, and recover by dispatching the channel to the
+ most preferred handler.</p>
+
+ <tp:rationale>
+ Processes that aren't approvers (or don't at least ensure that there
+ is some approver) probably shouldn't be making connections
+ anyway, so there should always be at least one approver running.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Channels" direction="in"
+ type="a(oa{sv})" tp:type="Channel_Details[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The initial value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation.Channels</tp:dbus-ref>
+ property, containing the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>s
+ to be dispatched and their properties.</p>
+
+ <tp:rationale>
+ <p>This can't be signalled to the approver through the Properties
+ parameter of this method, because Channels is not an immutable
+ property.</p>
+ </tp:rationale>
+
+ <p>This argument always contains all of the channels in the channel
+ dispatch operation, even if not all of them actually match
+ the <tp:member-ref>ApproverChannelFilter</tp:member-ref>.</p>
+
+ <tp:rationale>
+ <p>This seems the least bad way to handle such a situation;
+ see the discussion on
+ <a href="http://bugs.freedesktop.org/show_bug.cgi?id=21090">bug
+ #21090</a>.</p>
+ </tp:rationale>
+
+ <p>The actual channels to be dispatched may reduce as channels are
+ closed: this is signalled by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation.ChannelLost</tp:dbus-ref>.
+ </p>
+
+ <p>Approvers SHOULD connect to ChannelLost and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation.Finished</tp:dbus-ref>.
+ (if desired) before returning from AddDispatchOperation, since
+ those signals are guaranteed not to be emitted until after all
+ AddDispatchOperation calls have returned (with success or failure)
+ or timed out.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="DispatchOperation" type="o" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">ChannelDispatchOperation</tp:dbus-ref>
+ to be processed.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map" direction="in">
+ <tp:docstring>
+ <p>Properties of the channel dispatch operation. The keys MUST be
+ fully qualified D-Bus property names. This MUST NOT include
+ properties that could change, SHOULD include as many properties as
+ possible given that constraint, and MUST include at least the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">Account</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">Connection</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">PossibleHandlers</tp:dbus-ref>
+ properties.</p>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Client_Handler.xml b/qt4/spec/Client_Handler.xml
new file mode 100644
index 000000000..3a922e8cc
--- /dev/null
+++ b/qt4/spec/Client_Handler.xml
@@ -0,0 +1,337 @@
+<?xml version="1.0" ?>
+<node name="/Client_Handler"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Client.Handler">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Client"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Handlers are the user interface for a channel. They turn an abstract
+ Telepathy channel into something the user wants to see, like a text
+ message stream or an audio and/or video call.</p>
+
+ <p>For its entire lifetime, each channel on a connection known to the
+ channel dispatcher is either being processed by the channel dispatcher,
+ or being handled by precisely one Handler.</p>
+
+ <p>Because each channel is only handled by one Handler, handlers may
+ perform actions that only make sense to do once, such as acknowledging
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>
+ messages, doing the actual streaming for <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ channels with the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">MediaSignalling</tp:dbus-ref>
+ interface, or transferring the file in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">FileTransfer</tp:dbus-ref>
+ channels.</p>
+
+ <p>When a new incoming channel (one with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">Requested</tp:dbus-ref>
+ = FALSE) is offered to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Client">Approver</tp:dbus-ref>s
+ by the channel dispatcher, it also offers the Approvers a list of all
+ the running or activatable handlers whose
+ <tp:member-ref>HandlerChannelFilter</tp:member-ref> property
+ (possibly as cached in the .client file) indicates that they
+ are able to handle the channel. The Approvers can choose one of
+ those channel handlers to handle the channel.</p>
+
+ <p>When a new outgoing channel (one with
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel">Requested</tp:dbus-ref>
+ = TRUE) appears, the channel dispatcher passes it to an appropriate
+ channel handler automatically.
+ </p>
+
+ </tp:docstring>
+
+ <property name="HandlerChannelFilter"
+ tp:name-for-bindings="Handler_Channel_Filter"
+ type="aa{sv}" access="read" tp:type="Channel_Class[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A specification of the channels that this channel handler can
+ deal with. It will be offered to approvers as a potential
+ channel handler for bundles that contain only suitable channels,
+ or for suitable channels that must be handled separately.</p>
+
+ <p>This property works in exactly the same way as the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Client.Observer.ObserverChannelFilter</tp:dbus-ref>
+ property. In particular, it cannot change while the handler process
+ continues to own the corresponding Client bus name.</p>
+
+ <p>In the .client file, it is represented in the
+ same way as ObserverChannelFilter, but the group has the same
+ name as this interface and the keys start with
+ HandlerChannelFilter instead of ObserverChannelFilter.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="BypassApproval" tp:name-for-bindings="Bypass_Approval"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, channels destined for this handler are automatically
+ handled, without invoking approvers.</p>
+
+ <tp:rationale>
+ <p>The intended usage is to allow a client handling one channel to
+ pick up closely related channels. Suppose a client capable of
+ handling both Text and StreamedMedia,
+ <code>org.freedesktop.Telepathy.Client.Empathy</code>, is
+ handling a StreamedMedia channel. That client can take a second
+ well-known bus name, say
+ <code>org.freedesktop.Telepathy.Client.Empathy._1._42.Bundle1</code>,
+ and configure an object at
+ <code>/org/freedesktop/Telepathy/Client/Empathy/_1/_42/Bundle1</code>
+ with BypassApproval = TRUE,
+ whose <tp:member-ref>HandlerChannelFilter</tp:member-ref>
+ matches closely related Text channels by their Bundle property.</p>
+ </tp:rationale>
+
+ <p>For service-activatable handlers, this property should be specified
+ in the handler's <tt>.client</tt> file as follows:</p>
+
+<pre>
+[org.freedesktop.Telepathy.Client.Handler]
+BypassApproval=true
+</pre>
+ </tp:docstring>
+ </property>
+
+ <tp:simple-type name="Handler_Capability_Token" type="s"
+ array-name="Handler_Capability_Token_List">
+ <tp:docstring>
+ A <tp:type>DBus_Interface</tp:type>, followed by a slash '/' character
+ and an identifier for a capability defined by that interface. The
+ capability identifier SHOULD be in lower case. If an interface
+ references an external specification which is case-insensitive (such
+ as MIME), then names from that specification MUST be normalized to
+ lower-case before providing them to this Telepathy API, so that
+ implementations can safely rely on simple byte-by-byte comparison.
+
+ <tp:rationale>
+ These aren't D-Bus core Properties, and we want them to look visibly
+ different.
+ </tp:rationale>
+
+ <p>So far, all client capabilities are defined by the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface">MediaSignalling</tp:dbus-ref>
+ interface.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <property name="Capabilities" tp:name-for-bindings="Capabilities"
+ type="as" tp:type="Handler_Capability_Token[]" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The set of additional capabilities supported by this handler.
+ This describes things like support for streamed media codecs and
+ NAT traversal mechanisms: see the Contact Capabilities
+ interface for more details.</p>
+
+ <p>For handlers that have a <code>.client</code> file, the
+ channel dispatcher may discover this property from the
+ <code>org.freedesktop.Telepathy.Client.Handler.Capabilities</code>
+ group; for each capability, that group contains a key
+ whose name is the capability, with value <code>true</code>.
+ Keys with other values SHOULD NOT appear in this group.</p>
+
+ <p>For instance, the <code>.client</code> file for a streamed media
+ handler that supports ICE-UDP NAT traversal, Speex audio,
+ and Theora and H264 video might contain this group:</p>
+
+<pre>
+[org.freedesktop.Telepathy.Client.Handler.Capabilities]
+org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/ice-udp=true
+org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/audio/speex=true
+org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/video/theora=true
+org.freedesktop.Telepathy.Channel.Interface.MediaSignalling/video/h264=true
+</pre>
+
+ <p>Like the <tp:member-ref>HandlerChannelFilter</tp:member-ref>
+ property, this property cannot change while the Handler owns its
+ Client bus name. However, the <code>.client</code> file, if any,
+ can change (due to upgrades or installation of pluggable codecs),
+ and the capabilities really supported by the handler might not
+ exactly match what is cached in the <code>.client</code> file.</p>
+
+ <tp:rationale>
+ <p>The client file is installed statically and is intended to list
+ codecs etc. that the handler guarantees it can support (e.g. by
+ having a hard dependency on them), whereas the running handler
+ process might be able to find additional codecs.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <method name="HandleChannels" tp:name-for-bindings="Handle_Channels">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by the channel dispatcher when this client should handle these
+ channels, or when this client should present channels that it is already
+ handling to the user (e.g. bring them into the foreground).</p>
+
+ <tp:rationale>
+ <p>Clients are expected to know what channels they're already handling,
+ and which channel object path corresponds to which window or tab.
+ This can easily be done using a hash table keyed by channels' object
+ paths.</p>
+ </tp:rationale>
+
+ <p>This method can raise any D-Bus error. If it does, the
+ handler is assumed to have failed or crashed, and the channel
+ dispatcher MUST recover in an implementation-specific way; it MAY
+ attempt to dispatch the channels to another handler, or close the
+ channels.</p>
+
+ <p>If closing the channels, it is RECOMMENDED that the channel
+ dispatcher attempts to close the channels using
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Close</tp:dbus-ref>,
+ but resorts to calling
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.Interface.Destroyable.Destroy</tp:dbus-ref>
+ (if available) or ignoring the channel (if not) if the same handler
+ repeatedly fails to handle channels.</p>
+
+ <p>After HandleChannels returns successfully, the client process is
+ considered to be responsible for the channel until it its unique
+ name disappears from the bus.</p>
+
+ <tp:rationale>
+ <p>If a process has multiple Client bus names - some temporary and
+ some long-lived - and drops one of the temporary bus names in order
+ to reduce the set of channels that it will handle, any channels
+ that it is already handling should remain unaffected.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Account" type="o" direction="in">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ with which the channels are associated. The
+ well-known bus name to use is that of the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">AccountManager</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Connection" type="o" direction="in">
+ <tp:docstring>
+ The Connection with which the channels are associated. The
+ well-known bus name to use can be derived from this object
+ path by removing the leading '/' and replacing all subsequent
+ '/' by '.'.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channels" type="a(oa{sv})" direction="in"
+ tp:type="Channel_Details[]">
+ <tp:docstring>
+ The channels and their immutable properties. Their well-known
+ bus name is the same as that of the Connection.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Requests_Satisfied" type="ao" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The requests satisfied by these channels.</p>
+
+ <tp:rationale>
+ <p>If the handler implements Requests, this tells it
+ that these channels match previous <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Interface.Requests">AddRequest</tp:dbus-ref>
+ calls that it may have received.</p>
+
+ <p>There can be more than one, if they were EnsureChannel
+ requests.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="User_Action_Time" type="t" direction="in">
+ <tp:docstring>
+ The time at which user action occurred, or 0 if this channel
+ is to be handled for some reason not involving user action.
+ Handlers SHOULD use this for focus-stealing prevention,
+ if applicable.
+ This property has the same semantic as <tp:type>User_Action_Timestamp</tp:type>
+ but is unsigned for historical reasons.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Handler_Info" type="a{sv}" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Additional information about these channels. Currently defined
+ keys are:</p>
+
+ <dl>
+ <dt><code>request-properties</code> - a{oa{sv}}</dt>
+ <dd>A map from <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ paths listed in <var>Requests_Satisfied</var> to
+ <tp:type>Qualified_Property_Value_Map</tp:type>s containing
+ namespaced immutable properties of each request.</dd>
+ </dl>
+
+ <p>When more keys are defined for this dictionary, all will be
+ optional; handlers MAY safely ignore any entry in this
+ dictionary.</p>
+ </tp:docstring>
+ </arg>
+
+ <!-- FIXME: invent a way to say "any error is possible" in spec markup -->
+ </method>
+
+ <property name="HandledChannels" tp:name-for-bindings="Handled_Channels"
+ type="ao" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of the channels that this process is currently handling.</p>
+
+ <p>There is no change notification.</p>
+
+ <tp:rationale>
+ <p>This property exists for state recovery - it makes it possible
+ for channel handling to survive a ChannelDispatcher crash.</p>
+
+ <p>If the channel dispatcher is automatically replaced, the
+ replacement can discover all Handlers by looking for the Client
+ well-known bus names, and discover which channels they are
+ currently handling. Once this has been done, all unhandled
+ channels can be re-dispatched, and the only issue visible to
+ the user is that unhandled channels that they have already
+ approved might be sent back to Approvers.</p>
+ </tp:rationale>
+
+ <p>The value of this property SHOULD be the same for all Client
+ instances that share a unique bus name, and SHOULD include all
+ channels that are being handled, even if they were conceptually
+ handled by a different Client instance.</p>
+
+ <tp:rationale>
+ <p>Otherwise, when a process released a temporary Client name,
+ channels that it handled because of that Client name would no
+ longer be state-recoverable.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Client_Handler_Future.xml b/qt4/spec/Client_Handler_Future.xml
new file mode 100644
index 000000000..4c1a8b761
--- /dev/null
+++ b/qt4/spec/Client_Handler_Future.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" ?>
+<node name="/Client_Handler_Future"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Client.Handler.FUTURE"
+ tp:causes-havoc="a staging area for future Handler functionality">
+ <tp:requires interface="org.freedesktop.Telepathy.Client.Handler"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface contains functionality which we intend to incorporate
+ into the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref>
+ interface in future. It should be considered to
+ be conceptually part of the core Handler interface, but without
+ API or ABI guarantees.</p>
+ </tp:docstring>
+
+ <property name="BypassObservers" tp:name-for-bindings="Bypass_Observers"
+ type="b" access="read">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, channels destined for this handler are not passed to
+ observers for observing.</p>
+
+ <tp:rationale>
+ <p>This is useful in use-cases where the handler doesn't want anyone
+ observing the channel - for example, because channels it handles
+ shouldn't be logged.</p>
+ </tp:rationale>
+
+ <p>For service-activatable handlers, this property should be specified
+ in the handler's <tt>.client</tt> file as follows:</p>
+
+<pre>
+[org.freedesktop.Telepathy.Client.Handler]
+BypassObservers=true
+</pre>
+ </tp:docstring>
+ </property>
+
+ <property name="RelatedConferencesBypassApproval"
+ tp:name-for-bindings="Related_Conferences_Bypass_Approval"
+ type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, channels destined for this handler that have the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface"
+ >Conference</tp:dbus-ref> interface, with a channel that
+ was previously handled by the same client process in their
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface.Conference"
+ >InitialChannels</tp:dbus-ref> property, should bypass the
+ approval stage. In effect, this is a weaker form of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Client.Handler"
+ >BypassApproval</tp:dbus-ref>.</p>
+
+ <tp:rationale>
+ <p>It would be reasonable for a user interface to accept
+ invitations to continuations of an existing channel automatically,
+ or not; this is a matter of UI policy.</p>
+
+ <p>It's somewhat complex for an Approver to keep track of which
+ channels are being handled by a particular Handler, but
+ the Channel Dispatcher already has to track this, so it's
+ useful for the channel dispatcher to assist here.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Client_Interface_Requests.xml b/qt4/spec/Client_Interface_Requests.xml
new file mode 100644
index 000000000..3cecfce49
--- /dev/null
+++ b/qt4/spec/Client_Interface_Requests.xml
@@ -0,0 +1,175 @@
+<?xml version="1.0" ?>
+<node name="/Client_Interface_Requests"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Client.Interface.Requests">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Client"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Client.Handler"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface can be implemented by a Handler to be notified about
+ requests for channels that it is likely to be asked to handle.</p>
+ </tp:docstring>
+
+ <method name="AddRequest" tp:name-for-bindings="Add_Request">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by the ChannelDispatcher to indicate that channels have been
+ requested, and that if the request is successful, they will probably
+ be handled by this Handler. The ChannelDispatcher SHOULD only
+ call this method on one handler per request.</p>
+
+ <tp:rationale>
+ <p>This allows the UI to start preparing to handle the channels
+ in advance (e.g. render a window with an "in progress" message),
+ improving perceived responsiveness.</p>
+
+ <p>The use of "probably" is because you can't necessarily tell from
+ a channel request which handler will handle particular channels.
+ A reasonable heuristic would be to match the request against the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>,
+ and respect the preferred handler (if any).</p>
+ </tp:rationale>
+
+ <p>If the request succeeds and is given to the expected Handler,
+ the Requests_Satisfied parameter to
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ can be used to match the channel to a previous AddRequest call.</p>
+
+ <tp:rationale>
+ <p>This lets the UI direct the channels to the window that it
+ already opened.</p>
+ </tp:rationale>
+
+ <p>If the request fails, the expected handler is notified by the
+ channel dispatcher calling its
+ <tp:member-ref>RemoveRequest</tp:member-ref> method.</p>
+
+ <tp:rationale>
+ <p>This lets the UI close the window or display the error.</p>
+ </tp:rationale>
+
+ <p>The channel dispatcher SHOULD remember which handler was notified,
+ and if the channel request succeeds, it SHOULD dispatch the channels
+ to the expected handler, unless the channels do not match that
+ handler's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>.
+ If the channels are not dispatched to the expected handler, the
+ handler that was expected is notified by the channel dispatcher
+ calling its <tp:member-ref>RemoveRequest</tp:member-ref> method
+ with the NotYours error.</p>
+
+ <tp:rationale>
+ <p>Expected handling is for the UI to close the window it
+ previously opened.</p>
+ </tp:rationale>
+
+ <p>Handlers SHOULD NOT return an error from this method; errors
+ returned from this method SHOULD NOT alter the channel dispatcher's
+ behaviour.</p>
+
+ <tp:rationale>
+ <p>Calls to this method are merely a notification.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Request" type="o" direction="in">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ object, which MUST have been returned by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatcher">CreateChannel</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatcher">EnsureChannel</tp:dbus-ref>
+ before this method is called.
+
+ <tp:rationale>
+ See those methods for the rationale of this ordering.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map" direction="in">
+ <tp:docstring>
+ <p>Some of the properties of the ChannelRequest. To avoid race
+ conditions, this dictionary MUST NOT include properties whose
+ values could subsequently change. It SHOULD include as many
+ properties as possible, given that constraint.</p>
+
+ <p>In particular, the properties <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">Requests</tp:dbus-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">UserActionTime</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelRequest">Account</tp:dbus-ref>
+ MUST be included, and <tp:dbus-ref
+ namespace="ofdT.ChannelRequest">Hints</tp:dbus-ref>
+ MUST be included if implemented.</p>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="RemoveRequest"
+ tp:name-for-bindings="Remove_Request">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by the ChannelDispatcher to indicate that a request
+ previously passed to <tp:member-ref>AddRequest</tp:member-ref>
+ has failed and should be disregarded.</p>
+
+ <p>Handlers SHOULD NOT return an error from this method; errors
+ returned from this method SHOULD NOT alter the channel dispatcher's
+ behaviour.</p>
+
+ <tp:rationale>
+ <p>Calls to this method are merely a notification.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Request" type="o" direction="in">
+ <tp:docstring>
+ The request that failed.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Error" type="s" tp:type="DBus_Error_Name" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the D-Bus error with which the request failed.</p>
+
+ <p>If this is <code>org.freedesktop.Telepathy.Error.NotYours</code>,
+ this indicates that the request succeeded, but all the resulting
+ channels were given to some other handler.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Message" type="s" direction="in">
+ <tp:docstring>
+ Any message supplied with the D-Bus error.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Client_Observer.xml b/qt4/spec/Client_Observer.xml
new file mode 100644
index 000000000..b42b3b1df
--- /dev/null
+++ b/qt4/spec/Client_Observer.xml
@@ -0,0 +1,447 @@
+<?xml version="1.0" ?>
+<node name="/Client_Observer"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2008-2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2008-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Client.Observer">
+ <tp:added version="0.17.26">(as a stable interface)</tp:added>
+
+ <tp:requires interface="org.freedesktop.Telepathy.Client"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Observers monitor the creation of new channels. This
+ functionality can be used for things like message logging.
+ All observers are notified simultaneously.</p>
+
+ <p>Observers SHOULD NOT modify the state of a channel except
+ via user interaction.</p>
+
+ <tp:rationale>
+ <p>We want Observer UIs for file transfer channels (a progress
+ bar for the transfer) to be able to have a Cancel button.</p>
+ </tp:rationale>
+
+ <p>Observers MUST NOT carry out actions that exactly one process
+ must take responsibility for (e.g. acknowledging Text
+ messages, or carrying out the actual transfer in a file transfer
+ channel).</p>
+
+ <tp:rationale>
+ <p>Since arbitrarily many observers can be activated for
+ each channel, it would not make sense for observers to do things
+ that can only be done by one process (acknowledging
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>
+ messages, carrying out streaming for
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">StreamedMedia</tp:dbus-ref>
+ channels, doing the actual data transfer for file transfers,
+ setting up the out-of-band connection for Tubes). The
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler</tp:dbus-ref>
+ is responsible for such tasks.</p>
+
+ <p>Handlers MAY, of course, delegate responsibility for these
+ tasks to other processes (including those run as observers),
+ but this MUST be done explicitly via a request from the Handler
+ to the Observer.</p>
+ </tp:rationale>
+
+ <p>Whenever a collection of new channels is signalled, the channel
+ dispatcher will notify all running or activatable observers whose
+ <tp:member-ref>ObserverChannelFilter</tp:member-ref> property
+ (possibly as cached in the .client file) indicates that they are
+ interested in some of the channels.</p>
+
+ <p>Observers are activated for all channels in which they have
+ registered an interest - incoming, outgoing or automatically created -
+ although of course the ObserverChannelFilter property can be set
+ to filter on the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Requested</tp:dbus-ref>
+ property.</p>
+
+ <p>Because it might take time for an observer to become ready (for
+ instance, a Text logger needs to wait until pending messages have been
+ downloaded), the channel dispatcher must wait (up to some timeout) for
+ all observers to return from
+ <tp:member-ref>ObserveChannels</tp:member-ref> before letting anything
+ destructive happen. Destructive things (e.g. acknowledging messages)
+ are defined to be done by handlers, therefore HandleWith and Claim
+ aren't allowed to succeed until all observers are ready.</p>
+
+ <p>Non-interactive approvers (for instance, to shoot down spam
+ IM channels before the tray icon blinks at the user, or to grab
+ a SASL channel before the user is prompted for a password) can
+ be implemented as observers by following these steps:</p>
+
+ <ol>
+ <li><tp:member-ref>ObserveChannels</tp:member-ref>() is called
+ on the observer.</li>
+ <li>The observer calls <tp:dbus-ref
+ namespace="ofdT.ChannelDispatchOperation">Claim</tp:dbus-ref>()
+ on the CDO.</li>
+ <li>The observer then returns from
+ <tp:member-ref>ObserveChannels</tp:member-ref>().</li>
+ <li><tp:dbus-ref
+ namespace="ofdT.ChannelDispatchOperation">Claim</tp:dbus-ref>
+ will return successfully if the channels were successfully
+ claimed, or failure if someone else got there first.</li>
+ </ol>
+
+ <p>Non-interactive approvers implemented as observers SHOULD
+ also set <tp:member-ref>DelayApprovers</tp:member-ref> to TRUE
+ so that other Approvers are not called on until all observers
+ return from <tp:member-ref>ObserveChannels</tp:member-ref>.
+ This gives non-interactive approvers a chance to claim the
+ channels before Approvers are called.</p>
+ </tp:docstring>
+
+ <property name="ObserverChannelFilter"
+ tp:name-for-bindings="Observer_Channel_Filter"
+ type="aa{sv}" access="read" tp:type="Channel_Class[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A specification of the channels in which this observer is
+ interested. The <tp:member-ref>ObserveChannels</tp:member-ref> method
+ should be called by the channel dispatcher whenever any of the new
+ channels in a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal match this description.</p>
+
+ <p>Only certain D-Bus types have useful semantics for matching like this,
+ so only certain types are allowed:</p>
+
+ <dl>
+ <dt>Integers of all sizes, including byte (y, n, q, i, u, x, t)</dt>
+ <dd>Matched by numeric value, regardless of type (e.g. 42 as a
+ 16-bit signed integer 'n' is considered equal to 42 as a 32-bit
+ unsigned integer 'u')</dd>
+
+ <dt>Booleans (b)</dt>
+ <dd>Matched by equality in the obvious way; not considered equal to any
+ other type</dd>
+
+ <dt>Strings (s)</dt>
+ <dd>Matched by equality in the obvious way; not considered equal to any
+ other type</dd>
+
+ <dt>Object paths (o)</dt>
+ <dd>Matched by equality in the obvious way; not considered equal to any
+ other type</dd>
+
+ </dl>
+
+ <p>This property never changes while the observer process owns its
+ Client bus name. For activatable processes, the filter can change
+ due to an upgrade - the channel dispatcher SHOULD observe changes to
+ .client files using a mechanism like inotify.</p>
+
+ <tp:rationale>
+ <p>Not allowing this property to change is a simplification,
+ particularly for activatable processes (we reject the possibility
+ that a process with a .client file, when activated, has a filter
+ that differs from what its .client file said).</p>
+
+ <p>If an Observer wants to add extra channels to its list of
+ interests at runtime, it can register an additional Client bus name
+ (for instance, the org.freedesktop.Telepathy.Client.Empathy process
+ with unique name :1.42 could additionally register
+ org.freedesktop.Telepathy.Client.Empathy._1_42) with additional
+ filters. To remove those filters, it can release the bus name;
+ it could even re-claim the bus name immediately, with different
+ filters.</p>
+
+ <p>The same principle is applied to Approvers and Handlers.</p>
+ </tp:rationale>
+
+ <p>For observers that have a .client file, the channel dispatcher
+ may discover this property from keys of the form
+ "<code><em>propertyname</em> <em>type</em></code>",
+ in groups in the .client file whose name is the name of this
+ interface followed by <code>.ObserverChannelFilter</code>,
+ a space and an ASCII decimal number starting from 0.</p>
+
+ <p>Values in the .client file are encoded in exactly the same way as
+ the <code>default-<em>p</em></code> keys in .manager files, as
+ described in the <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >ConnectionManager</tp:dbus-ref> interface (but note that not all
+ types supported in .manager files can appear in .client files).</p>
+
+ <p>For instance, a .client file for an observer that is only interested
+ in Text channels, with CONTACT or ROOM handles, that were requested by
+ a local client:</p>
+
+<pre>
+[org.freedesktop.Telepathy.Client]
+Interfaces=org.freedesktop.Telepathy.Client.Observer;
+
+[org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Requested b=true
+
+[org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter 1]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
+org.freedesktop.Telepathy.Channel.TargetHandleType u=2
+org.freedesktop.Telepathy.Channel.Requested b=true
+</pre>
+
+ </tp:docstring>
+ </property>
+
+ <property name="Recover" tp:name-for-bindings="Recover" type="b"
+ access="read">
+ <tp:added version="0.19.4">
+ When using telepathy-mission-control, version 5.4.0 or later is
+ needed for this property to be useful.
+ </tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, upon the startup of this observer, <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Observer">ObserveChannels</tp:dbus-ref>
+ will be called for every already existing channel matching
+ its <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Observer">ObserverChannelFilter</tp:dbus-ref></p>
+
+ <p>When an activatable client having this property disappears from the
+ bus and there are channels matching its ObserverChannelFilter,
+ ObserveChannels will be called immediately to reactivate it
+ again. Such clients should specify this property in their
+ <tt>.client</tt> file as follows:</p>
+
+<pre>
+[org.freedesktop.Telepathy.Client.Observer]
+Recover=true
+</pre>
+
+ <tp:rationale>
+ <p>This means that if an activatable Observer crashes, it will
+ be restarted as soon as possible; while there is an unavoidable
+ possibility that it will miss some events during this process
+ (particularly <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>
+ messages), this window of event loss is kept to a minimum.</p>
+
+ <p>Non-activatable observers can't take advantage of this
+ mechanism, but setting this property on a non-activatable
+ observer does allow it to "catch up" on channels that are
+ currently active at the time that it starts up.</p>
+ </tp:rationale>
+
+ <p>When the ObserveChannels method is called due to observer recovery,
+ the <var>Observer_Info</var> dictionary will contain one extra item
+ mapping the key <code>"recovering"</code> to <code>True</code>.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="ObserveChannels" tp:name-for-bindings="Observe_Channels">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Called by the channel dispatcher when channels in which the
+ observer has registered an interest are announced in a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>
+ signal.</p>
+
+ <p>If the same NewChannels signal announces some channels that match
+ the filter, and some that do not, then only a subset of the channels
+ (those that do match the filter) are passed to this method.</p>
+
+ <p>If the channel dispatcher will split up the channels from a single
+ NewChannels signal and dispatch them separately (for instance
+ because no installed Handler can handle all of them), it will call
+ ObserveChannels several times.</p>
+
+ <p>The observer MUST NOT return from this method call until it is ready
+ for a handler for the channel to run (which may change the channel's
+ state).</p>
+
+ <tp:rationale>
+ <p>The channel dispatcher must wait for observers to start up,
+ to avoid the following race: text channel logger (observer) gets
+ ObserveChannels, text channel handler gets
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandleChannels</tp:dbus-ref>
+ channel handler starts up faster and acknowledges messages,
+ logger never sees those messages.</p>
+ </tp:rationale>
+
+ <p>The channel dispatcher SHOULD NOT change its behaviour based on
+ whether this method succeeds or fails: there are no defined D-Bus
+ errors for this method, and if it fails, this only indicates that
+ an Observer is somehow broken.</p>
+
+ <tp:rationale>
+ <p>The expected error response in the channel dispatcher is to
+ log a warning, and otherwise continue as though this method
+ had succeeded.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Account" type="o" direction="in">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Account</tp:dbus-ref>
+ with which the channels are associated. The
+ well-known bus name to use is that of the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">AccountManager</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Connection" type="o" direction="in">
+ <tp:docstring>
+ The
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ with which the channels are associated. The
+ well-known bus name to use can be derived from this object
+ path by removing the leading '/' and replacing all subsequent
+ '/' by '.'.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channels" type="a(oa{sv})" tp:type="Channel_Details[]"
+ direction="in">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>s
+ and their properties. Their well-known bus names are all the same as
+ that of the Connection.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Dispatch_Operation" type="o" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The path to the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatchOperation</tp:dbus-ref>
+ for these channels, or the special value '/' if there is no
+ ChannelDispatchOperation (because the channels were requested, not
+ incoming).</p>
+
+ <p>If the Observer calls <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">Claim</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">HandleWith</tp:dbus-ref>
+ on the dispatch operation, it MUST be careful to avoid deadlock,
+ since these methods cannot return until the Observer has returned
+ from <tp:member-ref>ObserveChannels</tp:member-ref>.</p>
+
+ <tp:rationale>
+ <p>This allows an Observer to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ChannelDispatchOperation">Claim</tp:dbus-ref>
+ a set of channels without having to match up calls to this method
+ with calls to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Approver">AddDispatchOperation</tp:dbus-ref>.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Requests_Satisfied" type="ao" direction="in">
+ <tp:docstring>
+ The <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>s
+ satisfied by these channels.
+
+ <tp:rationale>
+ If the same process is an Observer and a Handler, it can be useful
+ to be given this information as soon as possible (it will also
+ be passed to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client">Handler.HandleChannels</tp:dbus-ref>).
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Observer_Info" type="a{sv}" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Additional information about these channels. Currently defined
+ keys are:</p>
+
+ <dl>
+ <dt><code>recovering</code> - b</dt>
+ <dd><code>True</code> if ObserveChannels was called for an existing
+ channel (due to the <tp:member-ref>Recover</tp:member-ref>
+ property being <code>True</code>); <code>False</code> or omitted
+ otherwise.
+
+ <tp:rationale>
+ This allows observers to distinguish between new channels (the normal
+ case), and existing channels that were given to the observer in order
+ to catch up on previous events (perhaps after a previous instance of
+ the same observer crashed).
+ </tp:rationale>
+ </dd>
+
+ <dt><code>request-properties</code> - a{oa{sv}}</dt>
+ <dd>A map from <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelRequest</tp:dbus-ref>
+ paths listed in <var>Requests_Satisfied</var> to
+ <tp:type>Qualified_Property_Value_Map</tp:type>s containing
+ namespaced immutable properties of each request.</dd>
+ </dl>
+
+ <p>All defined keys for this dictionary are optional;
+ observers MAY safely ignore any entry in this dictionary.</p>
+ </tp:docstring>
+ </arg>
+
+ </method>
+
+ <property name="DelayApprovers" type="b" access="read"
+ tp:name-for-bindings="Delay_Approvers">
+ <tp:added version="0.21.11"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, the channel dispatcher will wait for
+ <tp:member-ref>ObserveChannels</tp:member-ref> to return
+ before calling <tp:dbus-ref
+ namespace="ofdT.Client">Approver.AddDispatchOperation</tp:dbus-ref>
+ on appropriate Approvers.</p>
+
+ <p>This property SHOULD be false unless there is a reason
+ why a channel should not be given to approvers. An example
+ of this is if an Observer is also a Handler and wants to
+ <tp:dbus-ref
+ namespace="ofdT.ChannelDispatchOperation">Claim</tp:dbus-ref>
+ a channel so that it becomes its handler and doesn't want
+ any approver to be called, this property should be true.</p>
+
+ <p>Observers and Approvers should be called at the same time
+ in normal operation (with this property set to false) to
+ improve responsiveness. For example, if an incoming call
+ appears, the approver should get the channel as fast as
+ possible to show a dialog, but if an approver has to make
+ round-trips to set itself up, then the approval of the
+ channel is delayed. As a result, it is recommended for this
+ property to remain false unless absolutely necessary.</p>
+
+ <p>For service-activatable clients, this property should be
+ specified in the observer's <tt>.client</tt> file as
+ follows:</p>
+
+ <p>If this property is not implemented (telepathy-mission-control
+ 5.7.5 and older), the channel dispatcher SHOULD consider it as
+ being false.</p>
+
+<pre>
+[org.freedesktop.Telepathy.Client.Observer]
+DelayApprovers=true
+</pre>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection.xml b/qt4/spec/Connection.xml
new file mode 100644
index 000000000..0ba095a9e
--- /dev/null
+++ b/qt4/spec/Connection.xml
@@ -0,0 +1,1299 @@
+<?xml version="1.0" ?>
+<node name="/Connection"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ >
+ <tp:copyright>Copyright (C) 2005-2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright (C) 2005-2009 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright (C) 2006 INdT</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.Requests"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.Contacts"/>
+
+ <tp:struct name="Channel_Info" array-name="Channel_Info_List">
+ <tp:deprecated version="0.17.23"/>
+ <tp:docstring>A struct representing a channel, as returned by
+ ListChannels on the Connection interface.</tp:docstring>
+ <tp:member type="o" name="Channel">
+ <tp:docstring>The object path of the channel, which is on the
+ same bus name as the connection</tp:docstring>
+ </tp:member>
+ <tp:member type="s" tp:type="DBus_Interface" name="Channel_Type">
+ <tp:docstring>The channel's type</tp:docstring>
+ </tp:member>
+ <tp:member type="u" tp:type="Handle_Type" name="Handle_Type">
+ <tp:docstring>The type of the handle that the channel communicates
+ with, or Handle_Type_None if there is no associated
+ handle</tp:docstring>
+ </tp:member>
+ <tp:member type="u" tp:type="Handle" name="Handle">
+ <tp:docstring>The handle that the channel communicates with,
+ or 0 if there is no associated handle</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="Connect" tp:name-for-bindings="Connect">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the connection be established. This will be done
+ asynchronously and errors will be returned by emitting
+ <tp:member-ref>StatusChanged</tp:member-ref> signals.</p>
+
+ <p>Calling this method on a Connection that is already connecting
+ or connected is allowed, and has no effect.</p>
+ </tp:docstring>
+ </method>
+
+ <method name="Disconnect" tp:name-for-bindings="Disconnect">
+ <tp:docstring>
+ Request that the connection be closed. This closes the connection if
+ it's not already in DISCONNECTED state, and destroys the connection
+ object.
+ </tp:docstring>
+ </method>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ access="read" type="as" tp:type="DBus_Interface[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The set of optional interfaces supported by this connection.
+ Before the connection status changes to CONNECTED,
+ this property may change at any time, but it is guaranteed that
+ interfaces will only be added, not removed. After the connection
+ status changes to CONNECTED, this property cannot
+ change further.</p>
+
+ <p>There is no explicit change notification; reasonable behaviour
+ for a client would be to retrieve the interfaces list once
+ initially, and once more when it becomes CONNECTED.</p>
+
+ <tp:rationale>
+ <p>In some connection managers, certain capabilities of a connection
+ are known to be implemented for all connections (e.g. support
+ for SimplePresence), and some interfaces (like SimplePresence) can
+ even be used before connecting. Other capabilities may
+ or may not exist, depending on server functionality; by the time
+ the connection goes CONNECTED, the connection manager is expected
+ to have evaluated the server's functionality and enabled any extra
+ interfaces for the remainder of the Connection's lifetime.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:added version="0.19.2">Clients SHOULD fall back
+ to calling <tp:member-ref>GetInterfaces</tp:member-ref> if this
+ property is not supported.</tp:added>
+ </property>
+
+ <method name="GetInterfaces" tp:name-for-bindings="Get_Interfaces">
+ <arg direction="out" type="as" tp:type="DBus_Interface[]"
+ name="Interfaces">
+ <tp:docstring>
+ The value of the <tp:member-ref>Interfaces</tp:member-ref> property
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Returns the set of optional interfaces supported by this
+ connection. See <tp:member-ref>Interfaces</tp:member-ref> for more
+ details.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected">
+ <tp:docstring>
+ Before version 0.17.8 calling GetInterfaces while
+ on a connection that is not yet CONNECTED wasn't allowed. If a
+ CM returns this error, its list of interfaces should be regarded
+ as empty until it becomes CONNECTED.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetProtocol" tp:name-for-bindings="Get_Protocol">
+ <arg direction="out" type="s" tp:type="Protocol" name="Protocol">
+ <tp:docstring>
+ A string identifier for the protocol
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Get the protocol this connection is using.
+ </tp:docstring>
+ </method>
+
+ <signal name="SelfHandleChanged"
+ tp:name-for-bindings="Self_Handle_Changed">
+ <tp:docstring>
+ Emitted whenever the <tp:member-ref>SelfHandle</tp:member-ref> property
+ changes. If the connection
+ is not yet in the CONNECTED state, this signal is not guaranteed
+ to be emitted.
+ </tp:docstring>
+ <tp:added version="0.17.10">Clients MAY assume that if the
+ SelfHandle property exists, this signal will be emitted when
+ necessary.</tp:added>
+
+ <arg type="u" tp:type="Contact_Handle" name="Self_Handle">
+ <tp:docstring>
+ The new value of the SelfHandle property.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="SelfHandle" tp:name-for-bindings="Self_Handle"
+ type="u" tp:type="Contact_Handle" access="read">
+ <tp:docstring>
+ The handle which represents the user on this connection, which will
+ remain valid for the lifetime of this connection, or until a change
+ in the user's identifier is signalled by the
+ <tp:member-ref>SelfHandleChanged</tp:member-ref> signal.
+ If the connection is not yet in the CONNECTED state, the value of
+ this property MAY be zero.
+ </tp:docstring>
+ <tp:added version="0.17.10">For compatibility with older
+ versions, clients should fall back to calling the
+ <tp:member-ref>GetSelfHandle</tp:member-ref>
+ method.</tp:added>
+ </property>
+
+ <method name="GetSelfHandle" tp:name-for-bindings="Get_Self_Handle">
+ <arg direction="out" type="u" tp:type="Contact_Handle"
+ name="Self_Handle">
+ <tp:docstring>
+ The value of the <tp:member-ref>SelfHandle</tp:member-ref> property
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Returns the value of the SelfHandle property. Change notification
+ is via the SelfHandleChanged signal.
+ </tp:docstring>
+ <tp:deprecated version="0.17.10">Use GetAll to get the
+ SelfHandle property (and all other Connection properties)
+ instead.</tp:deprecated>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="Status" tp:name-for-bindings="Status"
+ access="read" type="u" tp:type="Connection_Status">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current status of the connection. Change notification is via
+ the <tp:member-ref>StatusChanged</tp:member-ref> signal.</p>
+
+ <p>If retrieval of property succeeds and yields the value Disconnected,
+ this indicates that the connection has not yet been established.
+ If connection has been attempted and failed, the Connection object
+ SHOULD be removed from the bus entirely, meaning that retrieval of
+ this property SHOULD fail.</p>
+ </tp:docstring>
+ <tp:added version="0.19.2">Clients SHOULD fall back
+ to calling <tp:member-ref>GetStatus</tp:member-ref> if this
+ property is not supported.</tp:added>
+ </property>
+
+ <method name="GetStatus" tp:name-for-bindings="Get_Status">
+ <arg direction="out" type="u" tp:type="Connection_Status"
+ name="Status">
+ <tp:docstring>
+ The value of the <tp:member-ref>Status</tp:member-ref> property
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Get the current status as defined in the
+ <tp:member-ref>StatusChanged</tp:member-ref> signal.
+ </tp:docstring>
+ </method>
+
+ <method name="HoldHandles" tp:name-for-bindings="Hold_Handles">
+ <tp:changed version="0.21.6">If
+ <tp:member-ref>HasImmortalHandles</tp:member-ref> is true,
+ this method no longer does anything.</tp:changed>
+ <arg direction="in" name="Handle_Type" type="u" tp:type="Handle_Type">
+ <tp:docstring>
+ The type of handle to be held
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Handles" type="au" tp:type="Handle[]">
+ <tp:docstring>
+ A array of integer handles to hold
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <tp:member-ref>HasImmortalHandles</tp:member-ref> is true,
+ which SHOULD always be the case in this version of telepathy-spec,
+ this method does nothing and returns successfully, unless
+ the given handle type or any of the given handles is invalid.</p>
+
+ <p>In older connection managers, this method
+ notifies the connection manger that your client is holding a copy
+ of handles which may not be in use in any existing channel or
+ list, and were not obtained by using the
+ <tp:member-ref>RequestHandles</tp:member-ref> method. For
+ example, a handle observed in an emitted signal, or displayed
+ somewhere in the UI that is not associated with a channel. The
+ connection manager must not deallocate a handle where any clients
+ have used this method to indicate it is in use until the
+ <tp:member-ref>ReleaseHandles</tp:member-ref>
+ method is called, or the clients disappear from the bus.</p>
+
+ <p>Note that HoldHandles is idempotent - calling it multiple times
+ is equivalent to calling it once. If a handle is "referenced" by
+ several components which share a D-Bus unique name, the client
+ should perform reference counting internally, and only call
+ ReleaseHandles when none of the cooperating components need the
+ handle any longer.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The handle type is invalid
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ One of the given handles is not valid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="InspectHandles" tp:name-for-bindings="Inspect_Handles">
+ <arg direction="in" name="Handle_Type" type="u" tp:type="Handle_Type">
+ <tp:docstring>
+ The type of handle to be inspected
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Handles" type="au" tp:type="Handle[]">
+ <tp:docstring>
+ An array of integer handles of this type
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="as" name="Identifiers">
+ <tp:docstring>
+ An array of identifiers corresponding to the given handles, in the same order.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Return a string representation for a number of handles of a given
+ type.
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The handle type is invalid
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ One of the given handles is not valid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="ListChannels" tp:name-for-bindings="List_Channels">
+ <tp:deprecated version="0.17.23">Use the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Requests.Channels</tp:dbus-ref>
+ property instead.
+ </tp:deprecated>
+
+ <arg direction="out" type="a(osuu)" tp:type="Channel_Info[]"
+ name="Channel_Info">
+ <tp:docstring>
+ An array of structs representing channels.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ List all the channels which currently exist on this connection.
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="NewChannel" tp:name-for-bindings="New_Channel">
+ <tp:deprecated version="0.17.23">Connection managers MUST still
+ emit this signal, but clients SHOULD listen for the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Requests.NewChannels</tp:dbus-ref>
+ signal instead.
+ </tp:deprecated>
+
+ <arg name="Object_Path" type="o">
+ <tp:docstring>
+ A D-Bus object path for the channel object on this service
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channel_Type" type="s" tp:type="DBus_Interface">
+ <tp:docstring>
+ A D-Bus interface name representing the channel type
+ </tp:docstring>
+ </arg>
+
+ <arg name="Handle_Type" type="u" tp:type="Handle_Type">
+ <tp:docstring>
+ An integer representing the type of handle this channel
+ communicates with, or Handle_Type_None if no handle is specified
+ </tp:docstring>
+ </arg>
+
+ <arg name="Handle" type="u" tp:type="Handle">
+ <tp:docstring>
+ A handle indicating the specific contact, room or list this
+ channel communicates with, or zero if no handle is specified
+ </tp:docstring>
+ </arg>
+
+ <arg name="Suppress_Handler" type="b">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, the channel was requested by a client that intends to
+ present it to the user itself (i.e. it passed suppress_handler=TRUE
+ to the <tp:member-ref>RequestChannel</tp:member-ref> method), so no
+ other handler should be
+ launched. Clients MAY assume that channels where this is true
+ were created by a user request.</p>
+
+ <p>If false, either the channel was created due to incoming
+ information from the service, or the channel was requested by
+ a local client that does not intend to handle the channel itself
+ (this usage is deprecated).</p>
+
+ <p>Clients MUST NOT assume that only incoming channels will have
+ this flag set to false.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Emitted when a new Channel object is created, either through user
+ request or incoming information from the service.
+ </tp:docstring>
+ </signal>
+
+ <method name="ReleaseHandles" tp:name-for-bindings="Release_Handles">
+ <tp:changed version="0.21.6">If
+ <tp:member-ref>HasImmortalHandles</tp:member-ref> is true,
+ this method no longer does anything.</tp:changed>
+ <arg direction="in" name="Handle_Type" type="u" tp:type="Handle_Type">
+ <tp:docstring>
+ An integer handle type (as defined in RequestHandle)
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Handles" type="au" tp:type="Handle[]">
+ <tp:docstring>
+ An array of integer handles being held by the client
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ <p>If <tp:member-ref>HasImmortalHandles</tp:member-ref> is true,
+ which SHOULD always be the case in this version of telepathy-spec,
+ this method does nothing and returns successfully, unless
+ the given handle type or any of the given handles is invalid.</p>
+
+ <p>In older connection managers, this method
+ explicitly notifies the connection manager that your client is no
+ longer holding any references to the given handles, and that they
+ may be deallocated if they are not held by any other clients or
+ referenced by any existing channels. See
+ <tp:member-ref>HoldHandles</tp:member-ref> for notes.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The handle type is invalid
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ One of the given handles is not valid
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestChannel" tp:name-for-bindings="Request_Channel">
+ <tp:deprecated version="0.17.23">Use
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Requests.CreateChannel</tp:dbus-ref>
+ or <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Requests.EnsureChannel</tp:dbus-ref>
+ instead. Connection managers MAY implement RequestChannel by
+ raising NotImplemented, or implement fewer types of channel via
+ this API.</tp:deprecated>
+
+ <arg direction="in" name="Type" type="s" tp:type="DBus_Interface">
+ <tp:docstring>
+ A D-Bus interface name representing base channel type
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Handle_Type" type="u" tp:type="Handle_Type">
+ <tp:docstring>
+ An integer representing the handle type, or Handle_Type_None if
+ no handle is specified
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Handle" type="u" tp:type="Handle">
+ <tp:docstring>
+ A nonzero integer handle representing a contact, room, list etc.
+ according to handle_type, or zero if the handle_type is
+ Handle_Type_None
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Suppress_Handler" type="b">
+ <tp:docstring>
+ <p>Clients SHOULD always set this to true.</p>
+
+ <tp:rationale>
+ <p>The historical meaning was that clients that did not
+ intend to take responsibility for displaying the channel to
+ the user could set this to FALSE, in which case the channel
+ dispatcher would launch an appropriate channel handler.</p>
+
+ <p>However, clients whose functionality relies on having a
+ working channel dispatcher should obtain that functionality by
+ calling methods on the channel dispatcher, so that they will
+ get an appropriate error if the channel dispatcher is missing
+ or not working.</p>
+
+ <p>The channel dispatcher itself should set this to true too,
+ so that it will ignore the
+ <tp:member-ref>NewChannel</tp:member-ref> signal that results
+ from the creation of the channel. It can then dispatch the
+ channel returned from this method to an
+ appropriate handler.</p>
+
+ <p>So, there is no sensible use-case for setting this to false,
+ and setting it to false can result in unhandled channels (in
+ the case where clients assume that a channel dispatcher is
+ present, but it isn't).</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="o" name="Object_Path">
+ <tp:docstring>
+ The D-Bus object path for the channel created or retrieved
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request a channel satisfying the specified type and communicating
+ with the contact, room, list etc. indicated by the given
+ handle_type and handle. The handle_type and handle may both be
+ zero to request the creation of a new, empty channel, which may
+ or may not be possible, depending on the protocol and channel
+ type.</p>
+
+ <p>On success, the returned channel will always be of the requested
+ type (i.e. implement the requested channel-type interface).</p>
+
+ <p>If a new, empty channel is requested, on success the returned
+ channel will always be an "anonymous" channel for which the type
+ and handle are both zero.</p>
+
+ <p>If a channel to a contact, room etc. is requested, on success, the
+ returned channel may either be a new or existing channel to
+ the requested entity (i.e. its
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref>
+ and <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">TargetHandle</tp:dbus-ref>
+ properties are the
+ requested handle type and handle), or a newly created "anonymous"
+ channel associated with the requested handle in some
+ implementation-specific way.</p>
+
+ <p>For example, for a contact handle, the returned channel
+ might be "anonymous", but implement the groups interface and have
+ the requested contact already present among the members.</p>
+
+ <p>If the request cannot be satisfied, an error is raised and no
+ channel is created.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Unknown channel type
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ The given handle does not exist or cannot be created
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The requested channel type cannot be created with the given handle
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable">
+ <tp:docstring>
+ The requested channel cannot be created because contact doesn't
+ have the required capabilities.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Banned"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Full"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.InviteOnly"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:enum name="Handle_Type" type="u">
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ A "null" handle type used to indicate the absence of a handle.
+ When a handle type and a handle appear as a pair, if the handle
+ type is zero, the handle must also be zero.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Contact" value="1">
+ <tp:docstring>
+ A contact
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Room" value="2">
+ <tp:docstring>
+ A chat room
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="List" value="3">
+ <tp:docstring>
+ A server-generated contact list (see Channel.Interface.Group)
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Group" value="4">
+ <tp:docstring>
+ A user-defined contact list (see Channel.Interface.Group)
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:simple-type name="Handle" type="u" array-name="Handle_List">
+ <tp:docstring>An unsigned 32-bit integer representing a
+ handle</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Contact_Handle" type="u"
+ array-name="Contact_Handle_List">
+ <tp:docstring>An unsigned 32-bit integer representing a handle of type
+ Handle_Type_Contact</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Room_Handle" type="u"
+ array-name="Room_Handle_List">
+ <tp:docstring>An unsigned 32-bit integer representing a handle of type
+ Handle_Type_Room</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="List_Handle" type="u"
+ array-name="List_Handle_List">
+ <tp:docstring>An unsigned 32-bit integer representing a handle of type
+ Handle_Type_List</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Group_Handle" type="u"
+ array-name="Group_Handle_List">
+ <tp:docstring>An unsigned 32-bit integer representing a handle of type
+ Handle_Type_Group</tp:docstring>
+ </tp:simple-type>
+
+ <method name="RequestHandles" tp:name-for-bindings="Request_Handles">
+ <tp:changed version="0.21.6">If
+ <tp:member-ref>HasImmortalHandles</tp:member-ref> is true,
+ this method no longer has its reference-counting effect.</tp:changed>
+ <arg direction="in" name="Handle_Type" type="u" tp:type="Handle_Type">
+ <tp:docstring>
+ The type of handle required
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Identifiers" type="as">
+ <tp:docstring>
+ An array of identifiers of entities to request handles for
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="au" tp:type="Handle[]"
+ name="Handles">
+ <tp:docstring>
+ An array of integer handle numbers in the same order as the given identifiers.
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request several handles from the connection manager which represent a
+ number of contacts, rooms or server-stored lists on the service.</p>
+
+ <p>If <tp:member-ref>HasImmortalHandles</tp:member-ref> is true,
+ which SHOULD always be the case in this version of telepathy-spec,
+ the handles remain valid until the connection disconnects.</p>
+
+ <p>The implementation of this method in older connection managers
+ must record that these handles are in use by the
+ client who invokes this method, and must not deallocate the handles
+ until the client disconnects from the bus or calls the
+ <tp:member-ref>ReleaseHandles</tp:member-ref>
+ method. Where the identifier refers to an entity that already has a
+ handle in this connection manager, this handle should be returned
+ instead. The handle number 0 must not be returned by the connection
+ manager.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ The given identifier does not identify a valid entity of the given
+ type.
+
+ <tp:rationale>
+ For instance, an XMPP connection would raise this error for
+ identifiers with type Handle_Type_Room that do not contain
+ exactly one '@' character, that contain spaces, and so on.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The given handle type is not valid, or is not implemented on this
+ connection.
+
+ <tp:rationale>
+ For instance, a connection to a protocol that doesn't have
+ chat rooms would raise this error for room handles, and all CMs
+ would raise this error for Handle_Type_None.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:enum name="Connection_Status" plural="Connection_Statuses" type="u">
+ <tp:enumvalue suffix="Connected" value="0">
+ <tp:docstring>
+ The connection is fully connected and all methods are available.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Connecting" value="1">
+ <tp:docstring>
+ <tp:member-ref>Connect</tp:member-ref> has been called but the
+ connection has not yet been established. Some methods may fail
+ until the connection has been established.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Disconnected" value="2">
+ <tp:docstring>
+ If this is retrieved from <tp:member-ref>GetStatus</tp:member-ref> or
+ <tp:member-ref>Status</tp:member-ref>, it indicates that connection
+ has not yet been attempted. If seen in a
+ <tp:member-ref>StatusChanged</tp:member-ref> signal, it indicates
+ that the connection has failed; the Connection object SHOULD be
+ removed from D-Bus immediately, and all subsequent method calls
+ SHOULD fail.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Connection_Status_Reason" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A reason why the status of the connection changed. Apart from
+ Requested, the values of this enumeration only make sense as
+ reasons why the status changed to Disconnected.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None_Specified" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There is no reason set for this state change. Unknown status
+ reasons SHOULD be treated like this reason.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.<tp:error-ref>Disconnected</tp:error-ref></code>.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Requested" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The change is in response to a user request. Changes to the
+ Connecting or Connected status SHOULD always indicate this reason;
+ changes to the Disconnected status SHOULD indicate this reason
+ if and only if the disconnection was requested by the user.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cancelled</code>.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Network_Error" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There was an error sending or receiving on the network socket.</p>
+
+ <p>When the status changes from Connecting to Disconnected for this
+ reason, the equivalent D-Bus error is either
+ <code>org.freedesktop.Telepathy.Error.NetworkError</code>,
+ <code>org.freedesktop.Telepathy.Error.ConnectionRefused</code>,
+ <code>org.freedesktop.Telepathy.Error.ConnectionFailed</code>
+ or some more specific error.</p>
+
+ <p>When the status changes from Connected to Disconnected for this
+ reason, the equivalent D-Bus error is either
+ <code>org.freedesktop.Telepathy.Error.NetworkError</code>,
+ <code>org.freedesktop.Telepathy.Error.ConnectionLost</code>
+ or some more specific error.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Authentication_Failed" value="3">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The username or password was invalid.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.AuthenticationFailed</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Encryption_Error" value="4">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There was an error negotiating SSL on this connection, or
+ encryption was unavailable and require-encryption was set when the
+ connection was created.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.EncryptionNotAvailable</code>
+ if encryption was not available at all, or
+ <code>org.freedesktop.Telepathy.Error.EncryptionError</code>
+ if encryption failed.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Name_In_Use" value="5">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>In general, this reason indicates that the requested account
+ name or other identification could not be used due to conflict
+ with another connection. It can be divided into three cases:</p>
+
+ <ul>
+ <li>If the status change is from Connecting to Disconnected
+ and the 'register' parameter to RequestConnection was present
+ and true, the requested account could not be created on the
+ server because it already exists.
+ The equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.RegistrationExists</code>.
+ </li>
+
+ <li>If the status change is from Connecting to Disconnected
+ but the 'register' parameter is absent or false, the connection
+ manager could not connect to the specified account because
+ a connection to that account already exists.
+ The equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.AlreadyConnected</code>.
+
+ <tp:rationale>
+ In some protocols, like XMPP (when connecting with the same
+ JID and resource as an existing connection), the existing
+ connection "wins" and the new one fails to connect.
+ </tp:rationale>
+ </li>
+
+ <li>If the status change is from Connected to Disconnected,
+ the existing connection was automatically disconnected because
+ a new connection to the same account (perhaps from a different
+ client or location) was established.
+ The equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.ConnectionReplaced</code>.
+
+ <tp:rationale>
+ In some protocols, like MSNP (when connecting twice with the
+ same Passport), the new connection "wins" and the
+ existing one is automatically disconnected.
+ </tp:rationale>
+ </li>
+ </ul>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Not_Provided" value="6">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server did not provide a SSL certificate.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.NotProvided</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Untrusted" value="7">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate is signed by an untrusted certifying
+ authority. This error SHOULD NOT be used to represent a self-signed
+ certificate: use the more specific Cert_Self_Signed reason for
+ that.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.Untrusted</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Expired" value="8">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate has expired.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.Expired</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Not_Activated" value="9">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate is not yet valid.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.NotActivated</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Hostname_Mismatch" value="10">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate did not match its hostname.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.HostnameMismatch</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Fingerprint_Mismatch" value="11">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate does not have the expected
+ fingerprint.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.FingerprintMismatch</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Self_Signed" value="12">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate is self-signed.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.SelfSigned</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Other_Error" value="13">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There was some other error validating the server's SSL
+ certificate.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.Invalid</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Revoked" value="14">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate has been revoked.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.Revoked</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Insecure" value="15">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The server's SSL certificate uses an insecure algorithm,
+ or is cryptographically weak.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.Insecure</code>.
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Cert_Limit_Exceeded" value="16">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The length in bytes of the server certificate, or the depth of the
+ sever certificate chain exceed the limits imposed by the crypto
+ library.</p>
+
+ <p>When disconnected for this reason, the equivalent D-Bus error is
+ <code>org.freedesktop.Telepathy.Error.Cert.LimitExceeded</code>
+ </p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <signal name="ConnectionError" tp:name-for-bindings="Connection_Error">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when an error occurs that renders this connection unusable.
+ </p>
+
+ <p>Whenever this signal is emitted, it MUST immediately be followed by
+ a <tp:member-ref>StatusChanged</tp:member-ref> signal with status
+ Connection_Status_Disconnected and an appropriate reason
+ code.</p>
+
+ <p>Connection managers SHOULD emit this signal on disconnection, but
+ need not do so. Clients MUST support connection managers that emit
+ StatusChanged(Disconnected, ...) without first emitting
+ ConnectionError.</p>
+
+ <tp:rationale>
+ <p>This signal provides additional information about the reason
+ for disconnection. The reason for connection is always
+ straightforward - it was requested - so it does not need further
+ explanation. However, on errors, it can be useful to provide
+ additional information.</p>
+
+ <p>The <tp:type>Connection_Status_Reason</tp:type> is not given
+ here, since it will be signalled in
+ <tp:member-ref>StatusChanged</tp:member-ref>. A reasonable client
+ implementation would be to store the information given by this
+ signal until StatusChanged is received, at which point the
+ information given by this signal can be used to supplement the
+ StatusChanged signal.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Error" type="s" tp:type="DBus_Error_Name">
+ <tp:docstring>
+ The name of a D-Bus error describing the error that occurred,
+ which may correspond to a
+ <tp:type>Connection_Status_Reason</tp:type>, or may be a more
+ specific Telepathy error
+ (such as
+ <code>org.freedesktop.Telepathy.Error.ConnectionRefused</code>
+ for Connection_Status_Reason_Network_Error)
+ or a protocol-specific or connection-manager-specific error in a
+ suitable namespace.
+
+ <tp:rationale>
+ For instance, a SIP connection manager could signal
+ "402 Payment Required" as an error in a
+ connection-manager-specific namespace, or a link-local
+ XMPP implementation that used Avahi could provide the error
+ given to it by the avahi-daemon.
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Details" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Additional information about the error, which may include
+ the following well-known keys:</p>
+
+ <dl>
+ <dt>debug-message (s)</dt>
+ <dd>Debugging information on the change, corresponding to the
+ message part of a D-Bus error message, which SHOULD NOT be
+ displayed to users under normal circumstances</dd>
+
+ <dt>server-message (s)</dt>
+ <dd>A human-readable message from the server explaining what
+ happened. This may be in the user's native language, or in the
+ server operator's native language, or even in Lojban.</dd>
+
+ <dt>user-requested (b), expected-hostname (s), certificate-hostname (s)</dt>
+ <dd>The same details defined in <tp:type>TLS_Certificate_Rejection</tp:type>.</dd>
+ </dl>
+
+ </tp:docstring>
+ </arg>
+
+ </signal>
+
+ <signal name="StatusChanged" tp:name-for-bindings="Status_Changed">
+ <arg name="Status" type="u" tp:type="Connection_Status">
+ <tp:docstring>
+ An integer indicating the new status, as defined by ConnectionStatus
+ </tp:docstring>
+ </arg>
+
+ <arg name="Reason" type="u" tp:type="Connection_Status_Reason">
+ <tp:docstring>
+ An integer indicating the reason for the status change, as defined
+ by ConnectionStatusReason
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Emitted when the status of the connection changes. All states and
+ reasons have numerical values, as defined in ConnectionStatus
+ and ConnectionStatusReason.
+ </tp:docstring>
+ </signal>
+
+ <tp:contact-attribute name="contact-id" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same string that would be returned by
+ <tp:member-ref>InspectHandles</tp:member-ref>. As a special case,
+ this is always present in the result of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Contacts">GetContactAttributes</tp:dbus-ref>,
+ whether it was explicitly requested or not.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <method name="AddClientInterest" tp:name-for-bindings="Add_Client_Interest">
+ <tp:added version="0.21.3"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Register a client's interest in notifications related to one or
+ more interfaces.</p>
+
+ <p>Groups of notifications are identified by a token which is either
+ a D-Bus interface name, or a string that starts with a D-Bus
+ interface name. The meaning of each token is given by that D-Bus
+ interface, which MUST define it in its documentation.</p>
+
+ <tp:rationale>
+ <p>Initially, all interests are in entire interface, but allowing
+ other strings allows subscription to part of an interface; for
+ instance, an interest in ...MailNotification/count could track
+ the number of messages without caring about their detailed
+ content.</p>
+ </tp:rationale>
+
+ <p>For each token with which this method interacts, the
+ Connection tracks an "interest count" (like a reference count) for
+ each unique bus name that has called this method. When a client
+ calls this method, for each token, the interest count for its
+ unique bus name is incremented; when
+ <tp:member-ref>RemoveClientInterest</tp:member-ref> is called,
+ all interest counts for that unique bus name are decremented.
+ If the unique bus name leaves the bus (for instance, if the
+ client crashes or exits), all interest counts for that unique bus
+ name are set to zero.</p>
+
+ <p>The Connection can then use these reference counts to
+ avoid subscribing to protocol-level notifications unless at least
+ one client has a non-zero interest count for the relevant
+ token.</p>
+
+ <tp:rationale>
+ <p>This method exists to reduce memory and network overhead when
+ there is no active subscription.</p>
+
+ <p>One situation where this is useful is <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface"
+ >Location</tp:dbus-ref>: on XMPP, location updates are received
+ over PEP. If the Connection advertises the
+ <code>geoloc+notify</code> capability, it will be sent location
+ updates for all contacts. To avoid consuming resources for this,
+ the connection should avoid advertising that capability until
+ a client has expressed an interest in contacts' locations.</p>
+
+ <p>Another example of a protocol that benefits from this method is
+ the Google XMPP Mail Notification extension, which can be used
+ to implement <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface"
+ >MailNotification</tp:dbus-ref>. In this protocol, the CM
+ receives a notification that something has changed, but to get
+ more information, the CM must request this information. Knowing
+ that nobody is currently interested in this information, the CM
+ can avoid generating useless network traffic. Similarly, the CM
+ may free the list of unread messages to reduce memory overhead.</p>
+ </tp:rationale>
+
+ <p>If this method is called for an interface that might require
+ protocol-level subscription, but the connection cannot set up
+ that subscription yet (for instance because the
+ <tp:member-ref>Status</tp:member-ref> is not Connected yet), the
+ Connection MUST remember the client's interest, and attempt to
+ subscribe to the appropriate protocol feature when this becomes
+ possible.</p>
+
+ <p>Clients MAY ignore any errors raised by this method; it is intended
+ to be called with the reply ignored.</p>
+
+ <tp:rationale>
+ <p>The only reason it could fail is if it's unimplemented, in which
+ case the only thing the client can usefully do is to proceed as if
+ it had succeeded.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Tokens" type="as" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interfaces or parts of interfaces in which to register an
+ interest, represented by either a
+ <tp:type>DBus_Interface</tp:type>, or a string prefixed with a
+ <tp:type>DBus_Interface</tp:type>.</p>
+
+ <p>If the Connection does not support one of these tokens, this
+ is not considered to be an error; the unsupported token is
+ simply ignored.</p>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="RemoveClientInterest"
+ tp:name-for-bindings="Remove_Client_Interest">
+ <tp:added version="0.21.3"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Release an interest registered using
+ <tp:member-ref>AddClientInterest</tp:member-ref>. See that
+ method's documentation for details.</p>
+
+ <p>Clients MAY ignore any errors raised by this method; it is intended
+ to be called with the reply ignored.</p>
+
+ <tp:rationale>
+ <p>The only reasons it could fail are if it's unimplemented, or if
+ the client's reference-counting is wrong and it has tried to
+ remove a client interest that it did not add. In both cases,
+ there's nothing the client could do about it.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Tokens" type="as" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interfaces or parts of interfaces that were previously passed to
+ <tp:member-ref>AddClientInterest</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <property name="HasImmortalHandles"
+ tp:name-for-bindings="Has_Immortal_Handles"
+ access="read" type="b">
+ <tp:added version="0.21.6"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if handles last for the whole lifetime of the Connection.
+ This SHOULD be the case in all connection managers, but clients
+ MUST interoperate with older connection managers
+ (which reference-count handles).</p>
+ </tp:docstring>
+ </property>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This models a connection to a single user account on a communication
+ service. Its basic capability is to provide the facility to request and
+ receive channels of differing types (such as text channels or streaming
+ media channels) which are used to carry out further communication.</p>
+
+ <p>In order to allow Connection objects to be discovered by new clients,
+ the object path and well-known bus name MUST be of the form
+ <code>/org/freedesktop/Telepathy/Connection/cmname/proto/account</code>
+ and
+ <code>org.freedesktop.Telepathy.Connection.cmname.proto.account</code>
+ where:</p>
+
+ <ul>
+ <li><em>cmname</em> is the same
+ <tp:type>Connection_Manager_Name</tp:type> that appears
+ in the connection manager's object path and well-known bus name</li>
+ <li><em>proto</em> is the <tp:type>Protocol</tp:type> name as seen in
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.ConnectionManager">ListProtocols</tp:dbus-ref>,
+ but with "-" replaced with "_" to get a valid
+ object path/bus name</li>
+ <li><em>account</em> is some non-empty sequence of ASCII letters,
+ digits and underscores not starting with a digit</li>
+ </ul>
+
+ <p><em>account</em> SHOULD be formed such that any valid distinct
+ connection instance on this protocol has a distinct name. This
+ might be formed by including the server name followed by the user
+ name (escaped via some suitable mechanism like telepathy-glib's
+ tp_escape_as_identifier() function to preserve uniqueness); on
+ protocols where connecting multiple times is permissable, a
+ per-connection identifier might be necessary to ensure
+ uniqueness.</p>
+
+ <p>Clients MAY parse the object path to determine the connection
+ manager name and the protocol, but MUST NOT attempt to parse the
+ <em>account</em> part. Connection managers MAY use any unique string
+ for this part.</p>
+
+ <p>As well as the methods and signatures below, arbitrary interfaces may be
+ provided by the Connection object to represent extra connection-wide
+ functionality, such as the Connection.Interface.SimplePresence for
+ receiving and
+ reporting presence information, and Connection.Interface.Aliasing for
+ connections where contacts may set and change an alias for themselves.
+ These interfaces can be discovered using the
+ <tp:member-ref>GetInterfaces</tp:member-ref> method.</p>
+
+ <p>Contacts, rooms, and server-stored lists (such as subscribed contacts,
+ block lists, or allow lists) on a service are all represented by
+ immutable <em>handles</em>, which are unsigned non-zero integers which are
+ valid only for the lifetime of the connection object, and are used
+ throughout the protocol where these entities are represented, allowing
+ simple testing of equality within clients.</p>
+
+ <p>Zero as a handle value is sometimes used as a "null" value to mean
+ the absence of a contact, room, etc.</p>
+
+ <p>Handles have per-type uniqueness, meaning that
+ every (handle type, handle number) tuple is guaranteed to be unique within
+ a connection and that a handle alone (without its type) is meaningless or
+ ambiguous. Connection manager implementations should reference count these
+ handles to determine if they are in use either by any active clients or any
+ open channels, and may deallocate them when this ceases to be true. Clients
+ may request handles of a given type and identifier with the
+ <tp:member-ref>RequestHandles</tp:member-ref> method, inspect the entity
+ identifier with the <tp:member-ref>InspectHandles</tp:member-ref>
+ method, keep handles from being released with
+ <tp:member-ref>HoldHandles</tp:member-ref>, and notify that they are no
+ longer storing handles with
+ <tp:member-ref>ReleaseHandles</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <tp:changed version="0.17.10">Previously, the account part of
+ Connection bus names/object paths was allowed to have more than one
+ component (i.e. contain dots or slashes), resulting in Connection
+ bus names and object paths with more than 7 components. We now restrict
+ Connection bus names/object paths to have exactly 7
+ components.</tp:changed>
+
+ <tp:changed version="0.17.23">The Requests and Contacts interfaces
+ are now mandatory. Their functionality will be merged into the main
+ Connection interface at some point in future.</tp:changed>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Future.xml b/qt4/spec/Connection_Future.xml
new file mode 100644
index 000000000..6b5291efd
--- /dev/null
+++ b/qt4/spec/Connection_Future.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" ?>
+<node name="/Connection_FUTURE"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ >
+ <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.FUTURE"
+ tp:causes-havoc='experimental'>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <method name="EnsureSidecar" tp:name-for-bindings="Ensure_Sidecar">
+ <tp:added version="0.19.0">(as a draft)</tp:added>
+
+ <arg direction="in" name="Main_Interface" type="s"
+ tp:type="DBus_Interface">
+ <tp:docstring>
+ The "primary" interface implemented by an object attached
+ to a connection. For example, a Gabble plugin implementing
+ fine-grained control of XEP-0016 privacy lists might expose an object
+ implementing <tt>com.example.PrivacyLists</tt>.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Path" type="o">
+ <tp:docstring>The object path of the sidecar, exported by the same bus
+ name as the Connection to which it is attached.</tp:docstring>
+ </arg>
+ <arg direction="out" name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring>Immutable properties of the sidecar.</tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request an object with a particular interface providing additional
+ connection-specific functionality, together with its immutable
+ properties. These will often be implemented by plug-ins to the
+ connection managers; for example, support for an XMPP XEP for which
+ no generic Telepathy interface exists might be implemented by a
+ Gabble plugin exposing a sidecar with a particular interface.</p>
+
+ <p>This method may be called at any point during the lifetime of a
+ connection, even before its <tp:type>Connection_Status</tp:type>
+ changes to Connected. It MAY take a long time to
+ return—perhaps it needs to wait for a connection to be established
+ and for all the services supported by the server to be discovered
+ before determining whether necessary server-side support is
+ available—so callers SHOULD override the default method timeout (25
+ seconds) with a much higher value (perhaps even MAX_INT32, meaning
+ “no timeout” in recent versions of libdbus).</p>
+
+ <tp:rationale>
+ <p>There is an implicit assumption that any connection
+ manager plugin will only want to export one “primary” object per
+ feature it implements, since there is a one-to-one mapping between
+ interface and object. This is reasonable since Sidecars are
+ (intended to be) analogous to extra interfaces on the connection,
+ providing once-per-connection shared functionality; it also makes
+ client code straightforward (look up the interface you care about
+ in a dictionary, build a proxy object from the value). More
+ “plural” plugins are likely to want to implement new types of
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel</tp:dbus-ref>
+ instead.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The requested sidecar is not implemented by this connection manager,
+ or a necessary server-side component does not exist. (FIXME: split
+ these two errors out? Then again, once we list the guaranteed and
+ possible sidecars on a Protocol object, clients can tell the
+ difference themselves, because they shouldn't be calling this in the
+ first case.)
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.ServiceBusy">
+ <tp:docstring>
+ A server-side component needed by the requested sidecar reported it
+ is currently too busy, or did not respond for some
+ implementation-defined time. The caller may wish to try again later.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.Cancelled">
+ <tp:docstring>
+ The connection was disconnected while the sidecar was being set up.
+ </tp:docstring>
+ </tp:error>
+ </method>
+
+ </interface>
+</node>
diff --git a/qt4/spec/Connection_Interface_Addressing.xml b/qt4/spec/Connection_Interface_Addressing.xml
new file mode 100644
index 000000000..497c6d0d9
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Addressing.xml
@@ -0,0 +1,258 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Addressing" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2010 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or (at
+ your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public License
+ along with this library; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Addressing.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.Contacts"/>
+ <tp:added version="0.19.12">(as draft)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface deals with the multiple address types that can
+ refer to the same contact, such as vCard fields and URIs.</p>
+
+ <p>It can be used to retrieve contacts with a specific addresses
+ through <tp:member-ref>GetContactsByVCardField</tp:member-ref> and
+ <tp:member-ref>GetContactsByURI</tp:member-ref>, as well as
+ defining the various addressing methods for a given contact
+ through this interface's contact attributes.</p>
+ </tp:docstring>
+
+ <method name="GetContactsByVCardField"
+ tp:name-for-bindings="Get_Contacts_By_VCard_Field">
+ <arg direction="in" name="Field" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The vCard field of the addresses we are requesting. The
+ field name SHOULD be in lower case. Supported
+ fields can be found in
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Protocol.Interface.Addressing.DRAFT">AddressableVCardFields</tp:dbus-ref>.</p>
+
+ <p>The <code>url</code> vCard field MUST NOT appear here; see
+ <tp:member-ref>GetContactsByURI</tp:member-ref> instead.</p>
+
+ <tp:rationale>
+ <p>In practice, protocols have a limited set of URI
+ schemes that make sense to resolve as a contact.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Addresses" type="as">
+ <tp:docstring>
+ The addresses to get contact handles for. The address types
+ should match the given vCard field.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Interfaces" type="as"
+ tp:type="DBus_Interface[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of strings indicating which D-Bus interfaces the calling
+ process is interested in. All supported attributes from these
+ interfaces, whose values can be obtained without additional network
+ activity, will be in the reply.</p>
+
+ <p>Attributes from this interface and from
+ <tp:dbus-ref>org.freedesktop.Telepathy.Connection</tp:dbus-ref>
+ are always returned, and need not be requested
+ explicitly.</p>
+
+ <p>The behavior of this parameter is similar to the same
+ parameter in
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">Contacts.GetContactAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="a{ua{sv}}" name="Requested_Contacts"
+ tp:type="Contact_Attributes_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary mapping the contact handles to contact attributes.
+ If any of the requested addresses are in fact invalid, they are
+ simply omitted from this mapping. If contact attributes are not
+ immediately known, the behaviour is defined by the interface;
+ the attribute should either be omitted from the result or
+ replaced with a default value.</p>
+
+ <p>Requested addresses that cannot be satisfied MUST be ommitted
+ from the mapping.</p>
+
+ <p>Each contact's attributes will always include at least the
+ identifier that would be obtained by inspecting the handle
+ (<code>org.freedesktop.Telepathy.Connection/contact-id</code>),
+ and the vCard field used for requesting the contact in
+ <code>org.freedesktop.Telepathy.Connection.Interface.ContactInfo/info</code>.
+ </p>
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request contacts and retrieve their attributes using a given field
+ in their vCards.</p>
+
+ <p>The connection manager should record that these handles are in
+ use by the client who invokes this method, and must not
+ deallocate the handles until the client disconnects from the
+ bus or calls the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.ReleaseHandles</tp:dbus-ref>
+ method.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetContactsByURI"
+ tp:name-for-bindings="Get_Contacts_By_URI">
+ <arg direction="in" name="URIs" type="as">
+ <tp:docstring>
+ The URI addresses to get contact handles for. Supported
+ schemes can be found in
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Protocol.Interface.Addressing.DRAFT">AddressableURISchemes</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Interfaces" type="as"
+ tp:type="DBus_Interface[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of strings indicating which D-Bus interfaces the calling
+ process is interested in. All supported attributes from these
+ interfaces, whose values can be obtained without additional network
+ activity, will be in the reply.</p>
+
+ <p>Attributes from this interface and from
+ <tp:dbus-ref>org.freedesktop.Telepathy.Connection</tp:dbus-ref>
+ are always returned, and need not be requested
+ explicitly.</p>
+
+ <p>The behavior of this parameter is similar to the same
+ parameter in
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">Contacts.GetContactAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="a{ua{sv}}" name="Requested_Contacts"
+ tp:type="Contact_Attributes_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary mapping the contact handles to contact attributes.
+ If any of the requested addresses are in fact invalid, they are
+ simply omitted from this mapping. If contact attributes are not
+ immediately known, the behaviour is defined by the interface;
+ the attribute should either be omitted from the result or
+ replaced with a default value.</p>
+
+ <p>Requested URIs that cannot be satisfied MUST be ommitted
+ from the mapping.</p>
+
+ <p>Each contact's attributes will always include at least the
+ identifier that would be obtained by inspecting the handle
+ (<code>org.freedesktop.Telepathy.Connection/contact-id</code>).
+ </p>
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request contacts and retrieve their attributes using URI addresses.</p>
+
+ <p>The connection manager should record that these handles are in
+ use by the client who invokes this method, and must not
+ deallocate the handles until the client disconnects from the
+ bus or calls the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.ReleaseHandles</tp:dbus-ref>
+ method.</p>
+ </tp:docstring>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:mapping name="VCard_Field_Address_Map" array-name="">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A mapping of vCard fields and addresses that repreent
+ the given contact.</p>
+ </tp:docstring>
+ <tp:member type="s" name="VCard_Field"/>
+ <tp:member type="s" name="Address"/>
+ </tp:mapping>
+
+ <tp:contact-attribute name="addresses" type="a{ss}"
+ tp:type="VCard_Field_Address_Map">
+ <tp:docstring>
+ The various vCard addresses that identify this contact.
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:contact-attribute name="uris" type="as">
+ <tp:docstring>
+ The various URI addresses that identify this contact.
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:contact-attribute name="requested-address" type="(ss)"
+ tp:type="Requested_Address">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The contact's address, as it was requested
+ through <tp:member-ref>GetContactsByVCardField</tp:member-ref>. This
+ attribute MUST be ommitted if the contact was not retrieved
+ through <tp:member-ref>GetContactsByVCardField</tp:member-ref>.</p>
+ <tp:rationale>
+ <p>When retrieving more than one contact
+ through <tp:member-ref>GetContactsByVCardField</tp:member-ref>,
+ there needs to be a way to map the given contact back o the
+ original request.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:contact-attribute name="requested-uri" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The contact's URI, as it was requested through
+ <tp:member-ref>GetContactsByURI</tp:member-ref>. This
+ attribute MUST be ommitted if the contact was not retrieved
+ through <tp:member-ref>GetContactsByURI</tp:member-ref>.</p>
+ <tp:rationale>
+ <p>When retrieving more than one contact
+ through <tp:member-ref>GetContactsByURI</tp:member-ref>,
+ there needs to be a way to map the given contact back o the
+ original request.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:struct name="Requested_Address" array-name="">
+ <tp:docstring>
+ The address that has been requested by
+ <tp:member-ref>GetContactsByVCardField</tp:member-ref> or
+ <tp:member-ref>GetContactsByURI</tp:member-ref>.
+ </tp:docstring>
+
+ <tp:member name="Field" type="s">
+ <tp:docstring>
+ The vCard field used in <tp:member-ref>GetContactsByVCardField</tp:member-ref>.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Address" type="s">
+ <tp:docstring>
+ The address that was requested.
+ </tp:docstring>
+ </tp:member>
+
+ </tp:struct>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Aliasing.xml b/qt4/spec/Connection_Interface_Aliasing.xml
new file mode 100644
index 000000000..967577135
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Aliasing.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Aliasing" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Aliasing">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:mapping name="Alias_Map" array-name="">
+ <tp:docstring>A dictionary whose keys are contact handles and whose
+ values are aliases.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle"/>
+ <tp:member type="s" name="Alias"/>
+ </tp:mapping>
+
+ <tp:struct name="Alias_Pair" array-name="Alias_Pair_List">
+ <tp:docstring>
+ A pair (contact handle, alias) as seen in the
+ <tp:member-ref>AliasesChanged</tp:member-ref> signal.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle"/>
+ <tp:member type="s" name="Alias"/>
+ </tp:struct>
+
+ <signal name="AliasesChanged" tp:name-for-bindings="Aliases_Changed">
+ <arg name="Aliases" type="a(us)" tp:type="Alias_Pair[]">
+ <!-- FIXME: if we break API, this could be an Alias_Map, a{us} -->
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array containing structs of:
+ <ul>
+ <li>the handle representing the contact</li>
+ <li>the new alias</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when a contact's alias (or that of the user) is changed.
+ </tp:docstring>
+ </signal>
+ <tp:flags name="Connection_Alias_Flags" value-prefix="Connection_Alias_Flag" type="u">
+ <tp:flag suffix="User_Set" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The aliases of contacts on this connection may be changed by the
+ user of the service, not just by the contacts themselves. This is
+ the case on Jabber, for instance.</p>
+ <p>It is possible that aliases can be changed by the contacts too -
+ which alias takes precedence is not defined by this
+ specification, and depends on the server and/or connection manager
+ implementation.</p>
+ <p>This flag only applies to the aliases of "globally valid" contact
+ handles. At this time, clients should not expect to be able to
+ change the aliases corresponding to any channel-specific
+ handles. If this becomes possible in future, a new flag will
+ be defined.</p>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+ <method name="GetAliasFlags" tp:name-for-bindings="Get_Alias_Flags">
+ <arg direction="out" type="u" tp:type="Connection_Alias_Flags"
+ name="Alias_Flags">
+ <tp:docstring>
+ An integer with a bitwise OR of flags from ConnectionAliasFlags
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Return a bitwise OR of flags detailing the behaviour of aliases on this
+ connection.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+ <method name="RequestAliases" tp:name-for-bindings="Request_Aliases">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of handles representing contacts
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="as" name="Aliases">
+ <tp:docstring>
+ A list of aliases in the same order as the contact handles
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request the value of several contacts' aliases at once.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+ <method name="GetAliases" tp:name-for-bindings="Get_Aliases">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of handles representing contacts
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a{us}" tp:type="Alias_Map" name="Aliases">
+ <tp:docstring>
+ A dictionary mapping contact handles to aliases
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request the value of several contacts' aliases at once. This SHOULD
+ only return cached aliases, falling back on the contact identifier
+ (i.e. the string corresponding to the handle) if none is present. Also
+ if there was no cached alias, a request SHOULD be started of which the
+ result is later signalled by
+ <tp:member-ref>AliasesChanged</tp:member-ref>.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+ <method name="SetAliases" tp:name-for-bindings="Set_Aliases">
+ <arg direction="in" name="Aliases" type="a{us}" tp:type="Alias_Map">
+ <tp:docstring>
+ A dictionary mapping integer handles of contacts
+ to strings of the new alias to set.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that the alias of the given contact be changed. Success will be
+ indicated by emitting an <tp:member-ref>AliasesChanged</tp:member-ref>
+ signal. On connections where the CONNECTION_ALIAS_FLAG_USER_SET flag is
+ not set, this method will only ever succeed if the contact is the
+ user's own handle (as returned by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.GetSelfHandle</tp:dbus-ref>).
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:contact-attribute name="alias" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same string that would be returned by
+ <tp:member-ref>GetAliases</tp:member-ref>
+ (always present with some value, possibly the
+ same as Connection/contact-id, if information from the
+ Aliasing interface was requested)
+ </p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface on connections to support protocols where contacts have an
+ alias which they can change at will. Provides a method for the user to set
+ their own alias, and a signal which should be emitted when a contact's
+ alias is changed or first discovered.</p>
+
+ <p>On connections where the user is allowed to set aliases for contacts and
+ store them on the server, the <tp:member-ref>GetAliasFlags</tp:member-ref>
+ method will have the CONNECTION_ALIAS_FLAG_USER_SET flag set, and the
+ <tp:member-ref>SetAliases</tp:member-ref> method may be called on contact
+ handles other than the user themselves.</p>
+
+ <p>Aliases are intended to be used as the main displayed name for the
+ contact, where available.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Anonymity.xml b/qt4/spec/Connection_Interface_Anonymity.xml
new file mode 100644
index 000000000..704263cb9
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Anonymity.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Anonymity"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2010 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Anonymity">
+ <tp:added version="0.19.7">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface to support anonymity settings on a per-connection basis.
+ This defines what personal identifying information a remote contact
+ may or may not see. For example, GSM might use this for CLIR, while
+ SIP might use this for privacy service requests.</p>
+ </tp:docstring>
+
+ <tp:flags name="Anonymity_Mode_Flags" value-prefix="Anonymity_Mode" type="u">
+ <tp:docstring>
+ <p>Flags for the various types of anonymity modes. These modes are solely to
+ inform the CM of the desired anonymous settings. It is up to the
+ CM to determine whether the anonymity modes should be handled within
+ the CM itself, or whether the network that a CM might be talking to
+ should be enforcing anonymity.</p>
+
+ <p>CMs MAY support only a subset of these modes, and specific
+ connections MAY support none at all.</p>
+ </tp:docstring>
+
+ <tp:flag value="1" suffix="Client_Info">
+ <tp:docstring>
+ <p>Obscure any information that provides user identification,
+ user-agent identification or personal details. Examples of this
+ information might be GSM CallerID, SIP from address, various
+ informational email headers, etc.</p>
+
+ <p>The CM should scrub/replace any of this information before
+ passing messages or data onto the network. Note that a CM which
+ has the option of obscuring the information at the CM or privacy
+ service level would choose both (anonymity services are opaque
+ to clients of this interface).</p>
+
+ <p>Clients SHOULD NOT set both Client_Info and Show_Client_Info modes.
+ If they are set, the CM MUST respect Client_Info and ignore
+ Show_Client_Info.</p>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag value="2" suffix="Show_Client_Info">
+ <tp:docstring>
+ <p>Explicitly request showing of client information. In connection
+ context, this can be used to override service default. In channel
+ context, this overrides connection anonymity modes.</p>
+
+ <tp:rationale>
+ <p>In GSM, it's possible to have CLIR enabled by default, and
+ explicitly suppress CLIR for a single phone call.</p>
+ </tp:rationale>
+
+ <p>Clients SHOULD NOT set both Client_Info and Show_Client_Info modes.
+ If they are set, the CM MUST respect Client_Info and ignore
+ Show_Client_Info. The CM MAY set both Client_Info and Show_Client_Info
+ in <tp:member-ref>SupportedAnonymityModes</tp:member-ref> to indicate
+ its support for explicitly hiding and publicising client information.
+ </p>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag value="4" suffix="Network_Info">
+ <tp:docstring>
+ <p>Obscure any originating IP address information, contact URIs,
+ and anonymize all traffic involved with sending/receiving any
+ media streams or call content.
+ Examples of this include the "headers" portions of
+ <a href="http://www.rfc-editor.org/rfc/rfc3323.txt">RFC 3323</a> as
+ well as the History-Info (described in
+ <a href="http://www.rfc-editor.org/rfc/rfc4244.txt">RFC 4244</a>)
+ for a SIP CM.</p>
+
+ <p>This SHOULD have the effect of hiding address information from
+ the remote contact (ie, the contact cannot know what IP address
+ the session is originated from). Obviously the network still needs
+ to be able to route information between contacts, so this provides
+ no guarantees of what can be seen by intermediaries.</p>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <property name="SupportedAnonymityModes" type="u" access="read"
+ tp:type="Anonymity_Mode_Flags" tp:name-for-bindings="Supported_Anonymity_Modes">
+ <tp:docstring>
+ The anonymity modes supported by the CM for this connection. Once
+ Connection.Status has moved to Connected, this property MUST NOT change.
+ </tp:docstring>
+ </property>
+
+ <property name="AnonymityMandatory" type="b" access="readwrite"
+ tp:name-for-bindings="Anonymity_Mandatory"
+ tp:is-connection-parameter='yeah'>
+ <tp:docstring>
+ <p>This specifies whether or not the anonymity settings MUST be respected
+ by the CM and any intermediaries between the local and remote contacts.
+ If this is set to true but anonymity settings cannot be followed, then
+ the session MUST be denied with a
+ <code>org.freedesktop.Telepathy.Error.<tp:error-ref>WouldBreakAnonymity</tp:error-ref></code>
+ error.
+ Any client that sets <tp:member-ref>AnonymityModes</tp:member-ref>
+ SHOULD also set this property first (rather than accepting the CM's
+ default value).</p>
+ </tp:docstring>
+ </property>
+
+ <property name="AnonymityModes" type="u" tp:type="Anonymity_Mode_Flags"
+ access="readwrite" tp:name-for-bindings="Anonymity_Modes"
+ tp:is-connection-parameter='yeah'>
+ <tp:docstring>
+ <p>The currently enabled anonymity modes for the connection. Setting
+ has the effect of requesting new modes for the connection, and may
+ raise an error if the unsupported modes are set. Successfully changing
+ the modes will result in emission of
+ <tp:member-ref>AnonymityModesChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ An unsupported mode was supplied. Supported modes are specified
+ in the SupportedAnonymityModes property, and this should be
+ checked prior to setting AnonymityModes.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </property>
+
+ <signal name="AnonymityModesChanged"
+ tp:name-for-bindings="Anonymity_Modes_Changed">
+ <tp:docstring>
+ Emitted when the anonymity mode has changed.
+ </tp:docstring>
+
+ <arg name="Modes" type="u" tp:type="Anonymity_Mode_Flags">
+ <tp:docstring>
+ The new anonymity modes for this connection.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Avatars.xml b/qt4/spec/Connection_Interface_Avatars.xml
new file mode 100644
index 000000000..3b9290e1d
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Avatars.xml
@@ -0,0 +1,516 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Avatars" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2005-2008 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright (C) 2005-2008 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright (C) 2006 INdT</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Avatars">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:simple-type name="Avatar_Token" type="s"
+ array-name="Avatar_Token_List">
+ <tp:changed version="0.17.16">strengthened uniqueness requirements
+ so (CM name, protocol, token) is unique; previously only
+ (our Account, remote contact identifier, token) was required to be
+ unique</tp:changed>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An opaque token chosen by the connection manager, representing
+ a particular avatar.</p>
+
+ <tp:rationale>
+ <p>Because avatars can be relatively large images, most protocols
+ provide a way to detect whether an old avatar is still valid,
+ or whether an avatar has changed, without pushing the actual
+ avatar data to all clients.</p>
+ </tp:rationale>
+
+ <p>The connection manager MUST choose these tokens in a way that
+ makes it highly unlikely that two different avatars with the same
+ connection manager and protocol will have the same token.</p>
+
+ <tp:rationale>
+ <p>This means that clients MAY use the triple
+ (<tp:type>Connection_Manager_Name</tp:type>,
+ <tp:type>Protocol</tp:type>, avatar token) as a key for
+ their avatar cache. For instance, an avatar for a
+ telepathy-gabble Jabber contact might be stored in a file
+ .../gabble/jabber/4e199b4a1c40b497a95fcd1cd896351733849949.png.</p>
+ </tp:rationale>
+
+ <p>For instance, some protocols (like XMPP) identify avatars by a
+ hash of the avatar data; in this case, the hash can be used as the
+ avatar token.</p>
+
+ <p>Some protocols identify avatars by the timestamp of the last
+ change to the avatar; in these protocols it would be necessary for
+ the connection manager to encode both the timestamp and the
+ contact's identifier into the avatar token in order to ensure
+ uniqueness.</p>
+
+ <p>This token SHOULD be kept short and reasonably suitable for use
+ in a filename, but MAY contain any UTF-8 character (so clients using
+ avatar tokens in filenames MUST be prepared to escape characters
+ that are not valid in filenames). Connection managers for protocols
+ where tokens would otherwise become inconveniently large or contain
+ many unsuitable characters SHOULD hash the identifying data to
+ generate the token.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:mapping name="Avatar_Token_Map">
+ <tp:docstring>A dictionary whose keys are contact handles and whose
+ values are avatar tokens.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle"/>
+ <tp:member type="s" tp:type="Avatar_Token" name="Token"/>
+ </tp:mapping>
+
+ <signal name="AvatarUpdated" tp:name-for-bindings="Avatar_Updated">
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ An integer handle for the contact whose avatar has changed
+ </tp:docstring>
+ </arg>
+ <arg name="New_Avatar_Token" tp:type="Avatar_Token" type="s">
+ <tp:docstring>
+ Unique token for their new avatar
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the avatar for a contact has been updated, or first
+ discovered on this connection. If the token differs from the token
+ associated with the client's cached avatar for this contact, the new
+ avatar should be requested with
+ <tp:member-ref>RequestAvatars</tp:member-ref>.
+ </tp:docstring>
+ </signal>
+
+ <signal name="AvatarRetrieved" tp:name-for-bindings="Avatar_Retrieved">
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact whose avatar has been retrieved
+ </tp:docstring>
+ </arg>
+ <arg name="Token" tp:type="Avatar_Token" type="s">
+ <tp:docstring>
+ The token corresponding to the avatar
+ </tp:docstring>
+ </arg>
+ <arg name="Avatar" type="ay">
+ <tp:docstring>
+ An array of bytes containing the image data
+ </tp:docstring>
+ </arg>
+ <arg name="Type" type="s">
+ <tp:docstring>
+ A string containing the image MIME type (eg image/jpeg), or empty if
+ unknown
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the avatar for a contact has been retrieved.
+ </tp:docstring>
+ </signal>
+
+ <property name="SupportedAvatarMIMETypes"
+ tp:name-for-bindings="Supported_Avatar_MIME_Types"
+ type="as" access="read">
+ <tp:added version="0.17.22">Fall back to calling
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref> if getting this
+ property fails.</tp:added>
+ <tp:docstring>
+ An array of supported MIME types (e.g. "image/jpeg").
+ Clients MAY assume that the first type in this array is preferred.
+ This property cannot change after the Connection goes to the Connected
+ state.
+ </tp:docstring>
+ </property>
+
+ <property name="MinimumAvatarHeight"
+ tp:name-for-bindings="Minimum_Avatar_Height"
+ type="u" access="read">
+ <tp:added version="0.17.22">Fall back to calling
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref> if getting this
+ property fails.</tp:added>
+ <tp:docstring>
+ The minimum height in pixels of an avatar on this protocol, which MAY
+ be 0.
+ This property cannot change after the Connection goes to the Connected
+ state.
+ </tp:docstring>
+ </property>
+
+ <property name="MinimumAvatarWidth"
+ tp:name-for-bindings="Minimum_Avatar_Width"
+ type="u" access="read">
+ <tp:added version="0.17.22">Fall back to calling
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref> if getting this
+ property fails.</tp:added>
+ <tp:docstring>
+ The minimum width in pixels of an avatar on this protocol, which MAY
+ be 0.
+ This property cannot change after the Connection goes to the Connected
+ state.
+ </tp:docstring>
+ </property>
+
+ <property name="RecommendedAvatarHeight"
+ tp:name-for-bindings="Recommended_Avatar_Height"
+ type="u" access="read">
+ <tp:added version="0.17.22"/>
+ <tp:docstring>
+ The recommended height in pixels of an avatar on this protocol, or 0 if
+ there is no preferred height.
+ This property cannot change after the Connection goes to the Connected
+ state.
+
+ <tp:rationale>
+ In XMPP a recommended width is given by the protocol specification;
+ in proprietary protocols, using the same avatar size as the
+ proprietary client is likely to lead to the best display to other
+ users.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="RecommendedAvatarWidth"
+ tp:name-for-bindings="Recommended_Avatar_Width"
+ type="u" access="read">
+ <tp:added version="0.17.22"/>
+ <tp:docstring>
+ The recommended width in pixels of an avatar on this protocol, or 0 if
+ there is no preferred width.
+ This property cannot change after the Connection goes to the Connected
+ state.
+
+ <tp:rationale>
+ The rationale is the same as for
+ <tp:member-ref>RecommendedAvatarHeight</tp:member-ref>.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumAvatarHeight"
+ tp:name-for-bindings="Maximum_Avatar_Height"
+ type="u" access="read">
+ <tp:added version="0.17.22">Fall back to calling
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref> if getting this
+ property fails.</tp:added>
+ <tp:docstring>
+ The maximum height in pixels of an avatar on this protocol, or 0 if
+ there is no limit.
+ This property cannot change after the Connection goes to the Connected
+ state.
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumAvatarWidth"
+ tp:name-for-bindings="Maximum_Avatar_Width"
+ type="u" access="read">
+ <tp:added version="0.17.22">Fall back to calling
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref> if getting this
+ property fails.</tp:added>
+ <tp:docstring>
+ The maximum width in pixels of an avatar on this protocol, or 0 if
+ there is no limit.
+ This property cannot change after the Connection goes to the Connected
+ state.
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumAvatarBytes"
+ tp:name-for-bindings="Maximum_Avatar_Bytes"
+ type="u" access="read">
+ <tp:added version="0.17.22">Fall back to calling
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref> if getting this
+ property fails.</tp:added>
+ <tp:docstring>
+ The maximum size in bytes of an avatar on this protocol, or 0 if
+ there is no limit.
+ This property cannot change after the Connection goes to the Connected
+ state.
+ </tp:docstring>
+ </property>
+
+ <method name="GetAvatarRequirements"
+ tp:name-for-bindings="Get_Avatar_Requirements">
+ <tp:deprecated version="0.17.22">Use GetAll to retrieve the
+ D-Bus properties on this interface, falling back to this method
+ on failure.</tp:deprecated>
+ <arg direction="out" type="as" name="MIME_Types">
+ <tp:docstring>
+ An array of supported MIME types (eg image/jpeg)
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="q" name="Min_Width">
+ <tp:docstring>
+ The minimum image width in pixels
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="q" name="Min_Height">
+ <tp:docstring>
+ The minimum image height in pixels
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="q" name="Max_Width">
+ <tp:docstring>
+ The maximum image width in pixels, or 0 if there is no limit
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="q" name="Max_Height">
+ <tp:docstring>
+ The maximum image height in pixels, or 0 if there is no limit
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="u" name="Max_Bytes">
+ <tp:docstring>
+ The maximum image size in bytes, or 0 if there is no limit
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the required format of avatars on this connection.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetAvatarTokens" tp:name-for-bindings="Get_Avatar_Tokens">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of handles representing contacts
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="as" name="Tokens" tp:type="Avatar_Token[]">
+ <tp:docstring>
+ An array of avatar tokens or empty strings (if no avatar is set) in the
+ same order as the given array of contact handles
+ </tp:docstring>
+ </arg>
+ <tp:deprecated version="0.15.5">Use GetKnownAvatarTokens
+ instead.</tp:deprecated>
+ <tp:docstring>
+ Get the unique tokens for all of the given contacts' avatars.
+
+ Using this method in new Telepathy clients is deprecated; use
+ <tp:member-ref>GetKnownAvatarTokens</tp:member-ref> instead.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetKnownAvatarTokens"
+ tp:name-for-bindings="Get_Known_Avatar_Tokens">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of handles representing contacts
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a{us}" name="Tokens" tp:type="Avatar_Token_Map">
+ <tp:docstring>
+ A dictionary of handles mapped to avatar tokens, containing only
+ the known avatar tokens.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get the unique tokens for the given contacts' avatars. These tokens
+ can be persisted across connections, and should be used by the client
+ to check whether the avatars have been updated. For handles other than
+ the self handle, only tokens that are already known are returned; an
+ empty token means the given contact has no avatar. However, a CM must
+ always have the tokens for the self handle if one is set (even if it is
+ set to no avatar). On protocols where the avatar does not persist
+ between connections, a CM should omit the self handle from the returned
+ map until an avatar is explicitly set or cleared.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestAvatar" tp:name-for-bindings="Request_Avatar">
+ <arg direction="in" name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ An integer handle for the contact to request the avatar for
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="ay" name="Data">
+ <tp:docstring>
+ An array of bytes containing the image data
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="s" name="MIME_Type">
+ <tp:docstring>
+ A string containing the image MIME type (eg image/jpeg), or empty if
+ unknown
+ </tp:docstring>
+ </arg>
+ <tp:deprecated version="0.15.5">Use RequestAvatars
+ instead.</tp:deprecated>
+ <tp:docstring>
+ Request the avatar for a given contact. Using this method in new
+ Telepathy clients is deprecated; use RequestAvatars instead.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The contact does not currently have an avatar.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestAvatars" tp:name-for-bindings="Request_Avatars">
+ <arg direction="in" name="Contacts" type="au"
+ tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The contacts to retrieve avatars for
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request avatars for a number of contacts. The
+ <tp:member-ref>AvatarRetrieved</tp:member-ref> signal is emitted for
+ each avatar retrieved. If the handles are valid but retrieving an
+ avatar fails (for any reason, including the contact not having an
+ avatar) the AvatarRetrieved signal is not emitted for that contact.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="SetAvatar" tp:name-for-bindings="Set_Avatar">
+ <arg direction="in" name="Avatar" type="ay">
+ <tp:docstring>
+ An array of bytes representing the avatar image data
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="MIME_Type" type="s">
+ <tp:docstring>
+ A string representing the image MIME type
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="s" name="Token" tp:type="Avatar_Token">
+ <tp:docstring>
+ The string token of the new avatar
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Set a new avatar image for this connection. The avatar image must
+ respect the requirements obtained by
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref>.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="ClearAvatar" tp:name-for-bindings="Clear_Avatar">
+ <tp:added version="0.15.0" />
+ <tp:docstring>
+ Remove the avatar image for this connection.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:contact-attribute name="token" type="s" tp:type="Avatar_Token">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same string that would be returned by
+ <tp:member-ref>GetKnownAvatarTokens</tp:member-ref>
+ (omitted from the result if the contact's avatar token is not known,
+ present as an empty string if the contact is known not to have
+ an avatar). Unlike in the
+ <tp:member-ref>GetKnownAvatarTokens</tp:member-ref>
+ method, the avatar tokens for the self handle aren't required to be
+ present. This attribute should not be used to determine whether or
+ not the Avatar needs to be set.
+ </p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for requesting avatars for contacts on a given connection,
+ receiving notification when avatars are changed, and publishing your own
+ avatar.</p>
+
+ <p>Avatars are identified by a string, the <tp:type>Avatar_Token</tp:type>,
+ which represents a particular avatar. Tokens MUST be chosen by the
+ connection manager in such a way that the triple
+ (<tp:type>Connection_Manager_Name</tp:type>, <tp:type>Protocol</tp:type>,
+ <tp:type>Avatar_Token</tp:type>) uniquely identifies an avatar.
+ An empty token means that an avatar has not been set for this contact, and
+ a changed token implies the contact's avatar has changed, but the strings
+ should otherwise be considered opaque by clients.</p>
+
+ <p>A client should use <tp:member-ref>GetKnownAvatarTokens</tp:member-ref>
+ to request the tokens for the
+ avatars of all the contacts it is interested in when it connects. The
+ avatars can then be requested using
+ <tp:member-ref>RequestAvatars</tp:member-ref> for the contacts. Clients
+ should bind to the <tp:member-ref>AvatarUpdated</tp:member-ref> signal and
+ request a new copy of
+ the avatar when a contacts' avatar token changes. Clients should cache the
+ token and data of each contact's avatar between connections, to avoid
+ repeatedly retrieving the same avatar.</p>
+
+ <p>To publish an avatar, a client should use
+ <tp:member-ref>SetAvatar</tp:member-ref> to provide an image which meets
+ the requirements returned by the
+ <tp:member-ref>GetAvatarRequirements</tp:member-ref>
+ function. On some protocols the avatar is stored on the server, so setting
+ the avatar is persistent, but on others it is transferred via a peer to
+ peer mechanism, so needs to be set every connection. Hence, on every
+ connection, clients should inspect the avatar token of the connection's
+ self handle using <tp:member-ref>GetKnownAvatarTokens</tp:member-ref>; if
+ the self handle is not in the
+ returned map, the client should re-set the avatar. If the self handle's
+ avatar token is known, but the avatar has been changed locally since the
+ last connection, the client should upload the new avatar; if the avatar has
+ not changed locally, then the client should download the avatar from the
+ server if its token differs from the that of the local avatar.</p>
+
+ <p>To remove the published avatar on protocols which have persistent avatars,
+ a client should use the <tp:member-ref>ClearAvatar</tp:member-ref> method.
+ This method can safely be used even if there is no avatar for this
+ connection.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Balance.xml b/qt4/spec/Connection_Interface_Balance.xml
new file mode 100644
index 000000000..76f9040e9
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Balance.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Balance"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Balance">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.19.0">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>In many real-time communication services the user can pay for certain
+ services, typically calls to the
+ <abbr title="Public Switched Telephone Network">PSTN</abbr>,
+ in advance. In (at least) Skype, it's possible to query the current
+ balance in a machine-readable way.</p>
+ </tp:docstring>
+
+ <tp:struct name="Currency_Amount">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An amount of money in a specified currency. For example,
+ 3.21 British pounds would conventionally be represented by
+ (<var>Amount</var> = <tt>321</tt>, <var>Scale</var> = <tt>2</tt>,
+ <var>Currency</var> = <tt>"GBP"</tt>), but could be represented by
+ (<var>Amount</var> = <tt>3210</tt>, <var>Scale</var> = <tt>3</tt>,
+ <var>Currency</var> = <tt>"GBP"</tt>) in a service that records
+ balance in units of 0.001 pounds.</p>
+
+ <p>As a special case, if <var>Amount</var> = <tt>0</tt>,
+ <var>Scale</var> = <tt>2**32 - 1</tt> (i.e. the largest possible
+ 32-bit unsigned integer) and <var>Currency</var> = <tt>""</tt>, this
+ indicates an unknown amount.</p>
+ </tp:docstring>
+
+ <tp:member type="i" name="Amount">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The amount, expressed as a fixed-point number with decimal scale
+ defined by the <var>Scale</var> field; for instance, an
+ <var>Amount</var> value of <tt>1234</tt> with <var>Scale</var> of
+ <tt>2</tt> represents 12.34 in the currency unit given by the
+ <var>Currency</var> field.</p>
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Scale">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The decimal scale for the fixed point value of the
+ <var>Amount</var> field, defining the number of rightmost decimal
+ digits from the integer value which form the fractional part of the
+ resulting currency value.</p>
+
+ <p>As well as defining the interpretation of <var>Amount</var>, user
+ interfaces may use this value to determine the precision with which
+ to display the amount.</p>
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Currency">
+ <tp:docstring>
+ The currency code represented by this amount, which SHOULD be an
+ international currency code such as <tt>"EUR"</tt>, <tt>"USD"</tt>,
+ or <tt>"JPY"</tt> if possible. An empty string can be used to
+ indicate that the currency is not known.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="AccountBalance" tp:name-for-bindings="Account_Balance"
+ access="read" type="(ius)" tp:type="Currency_Amount">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The user's balance on the account corresponding to this <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>.
+ A negative amount may be possible on some services, and indicates
+ that the user owes money to the service provider.</p>
+
+ <p>On initial connection, this property may have an unknown
+ value, represented by <var>Amount</var> = <tt>0</tt>,
+ <var>Scale</var> = <tt>2**32 - 1</tt> (the largest possible 32-bit
+ unsigned integer) and <var>Currency</var> = <tt>""</tt>.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="BalanceChanged" tp:name-for-bindings="Balance_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the user's balance has changed.</p>
+ </tp:docstring>
+
+ <arg name="Balance" type="(ius)" tp:type="Currency_Amount">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The new value of the <tp:member-ref>AccountBalance</tp:member-ref>
+ property.</p>
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Capabilities.xml b/qt4/spec/Connection_Interface_Capabilities.xml
new file mode 100644
index 000000000..8e5eb3357
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Capabilities.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Capabilities" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Capabilities">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for connections where it is possible to know what channel
+ types may be requested before the request is made to the connection
+ object. Each capability represents a commitment by the connection
+ manager that it will ordinarily be able to create a channel when given
+ a request with the given type and handle.</p>
+
+ <p>Capabilities pertain to particular contact handles, and represent
+ activities such as having a text chat or a voice call with the user.
+ The activities are represented by the D-Bus interface name of the
+ channel type for that activity.</p>
+
+ <p>The generic capability flags are defined by
+ <tp:type>Connection_Capability_Flags</tp:type>.</p>
+
+ <p>In addition, channel types may have type specific capability flags of
+ their own, which are described in the documentation for each channel
+ type.</p>
+
+ <p>This interface also provides for user interfaces notifying the
+ connection manager of what capabilities to advertise for the user. This
+ is done by using the
+ <tp:member-ref>AdvertiseCapabilities</tp:member-ref> method, and deals
+ with the
+ interface names of channel types and the type specific flags pertaining
+ to them which are implemented by available client processes.</p>
+ </tp:docstring>
+
+ <tp:changed version="0.17.8">Previously, this interface
+ also expressed capabilities of the connection itself, indicating what
+ sorts of channels could be requested (for instance, the ability to
+ open chatroom lists or chatrooms). However, this was never very
+ well-defined or consistent, and as far as we know it was never
+ implemented correctly. This usage is now deprecated.</tp:changed>
+
+ <tp:deprecated version="0.19.8">Client implementations SHOULD use <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">ContactCapabilities</tp:dbus-ref>
+ instead.</tp:deprecated>
+ <tp:changed version="0.19.8">Connection managers implementing
+ Capabilities MUST implement ContactCapabilities too.</tp:changed>
+
+ <tp:flags name="Connection_Capability_Flags"
+ value-prefix="Connection_Capability_Flag" type="u">
+ <tp:flag suffix="Create" value="1">
+ <tp:docstring>
+ The given channel type and handle can be given to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">RequestChannel</tp:dbus-ref>
+ to create a new channel of this type.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Invite" value="2">
+ <tp:docstring>
+ The given contact can be invited to an existing channel of this type.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:struct name="Capability_Pair" array-name="Capability_Pair_List">
+ <tp:docstring>A pair (channel type, type-specific flags) as passed to
+ <tp:member-ref>AdvertiseCapabilities</tp:member-ref> on the
+ Capabilities interface.</tp:docstring>
+ <tp:member type="s" tp:type="DBus_Interface" name="Channel_Type"/>
+ <tp:member type="u" name="Type_Specific_Flags"/>
+ </tp:struct>
+
+ <tp:struct name="Contact_Capability" array-name="Contact_Capability_List">
+ <tp:docstring>A struct (contact handle, channel type, generic flags,
+ type-specific flags) representing a capability posessed by a contact,
+ as returned by <tp:member-ref>GetCapabilities</tp:member-ref> on the
+ Capabilities interface.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle"/>
+ <tp:member type="s" tp:type="DBus_Interface" name="Channel_Type"/>
+ <tp:member type="u" tp:type="Connection_Capability_Flags"
+ name="Generic_Flags"/>
+ <tp:member type="u" name="Type_Specific_Flags"/>
+ </tp:struct>
+
+ <tp:struct name="Capability_Change" array-name="Capability_Change_List">
+ <tp:docstring>A struct (contact handle, channel type, old generic flags,
+ new generic flags, old type-specific flags, new type-specific flags)
+ representing a change to one of a contact's capabilities, as seen in the
+ <tp:member-ref>CapabilitiesChanged</tp:member-ref> signal on the
+ Capabilities interface.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle"/>
+ <tp:member type="s" tp:type="DBus_Interface" name="Channel_Type"/>
+ <tp:member type="u" tp:type="Connection_Capability_Flags"
+ name="Old_Generic_Flags"/>
+ <tp:member type="u" tp:type="Connection_Capability_Flags"
+ name="New_Generic_Flags"/>
+ <tp:member type="u" name="Old_Type_Specific_Flags"/>
+ <tp:member type="u" name="New_Type_Specific_Flags"/>
+ </tp:struct>
+
+ <method name="AdvertiseCapabilities"
+ tp:name-for-bindings="Advertise_Capabilities">
+ <arg direction="in" name="Add" type="a(su)" tp:type="Capability_Pair[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structures containing:
+ <ul>
+ <li>a string channel type</li>
+ <li>a bitwise OR of type specific capability flags</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Remove" type="as" tp:type="DBus_Interface[]">
+ <tp:docstring>
+ An array of D-Bus interface names of channel types to remove
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a(su)" tp:type="Capability_Pair[]"
+ name="Self_Capabilities">
+ <tp:docstring>
+ An array of structures describing the current capabilities containing:
+ <ul>
+ <li>a string channel type</li>
+ <li>a bitwise OR of type specific capability flags</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Used by user interfaces to indicate which channel types they are able
+ to handle on this connection. Because these may be provided by
+ different client processes, this method accepts channel types to add
+ and remove from the set already advertised on this connection. The type
+ of advertised capabilities (create versus invite) is protocol-dependent
+ and hence cannot be set by the this method. In the case of a client
+ adding an already advertised channel type but with new channel type
+ specific flags, the connection manager should simply add the new flags
+ to the set of advertised capabilities.</p>
+
+ <p>Upon a successful invocation of this method, the
+ <tp:member-ref>CapabilitiesChanged</tp:member-ref>
+ signal will be emitted for the user's own handle ( <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.GetSelfHandle</tp:dbus-ref>)
+ by the connection manager to indicate the changes
+ that have been made. This signal should also be monitored to ensure
+ that the set is kept accurate - for example, a client may remove
+ capabilities or type specific capability flags when it exits
+ which are still provided by another client.</p>
+
+ <p>On connections managed by the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>,
+ this method SHOULD NOT be used by clients other than the
+ ChannelDispatcher itself.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="CapabilitiesChanged"
+ tp:name-for-bindings="Capabilities_Changed">
+ <arg name="Caps" type="a(usuuuu)" tp:type="Capability_Change[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structures containing:
+ <ul>
+ <li>an integer handle representing the contact</li>
+ <li>a string channel type</li>
+ <li>a bitwise OR of the contact's old generic capability flags</li>
+ <li>a bitwise OR of the contact's new generic capability flags</li>
+ <li>a bitwise OR of the contact's old type specific capability flags</li>
+ <li>a bitwise OR of the contact's new type specific capability flags</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Announce that there has been a change of capabilities on the
+ given handle.</p>
+
+ <p>If the handle is zero, the capabilities refer to the connection
+ itself, in some poorly defined way. This usage is deprecated and
+ clients should ignore it.</p>
+ </tp:docstring>
+ </signal>
+
+ <method name="GetCapabilities" tp:name-for-bindings="Get_Capabilities">
+ <arg direction="in" name="Handles" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of contact handles for this connection.</p>
+
+ <p>This may include zero, which originally meant a query for
+ capabilities available on the connection itself. This usage
+ is deprecated; clients SHOULD NOT do this, and connection managers
+ SHOULD proceed as though zero had not been present in this
+ list.</p>
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a(usuu)" tp:type="Contact_Capability[]"
+ name="Contact_Capabilities">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structures containing:
+ <ul>
+ <li>an integer handle representing the contact</li>
+ <li>a string channel type</li>
+ <li>a bitwise OR of generic capability flags for the type</li>
+ <li>a bitwise OR of type specific capability flags for the type</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Returns an array of capabilities for the given contact handles.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ The handle does not represent a contact and is not zero
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:contact-attribute name="caps"
+ type="a(usuu)" tp:type="Contact_Capability[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same structs that would be returned by
+ <tp:member-ref>GetCapabilities</tp:member-ref>
+ (all of them will redundantly have the contact's handle as the
+ first member). Omitted from the result if the contact's capabilities
+ are not known; present in the result as an empty array if the
+ contact is known to have no capabilities at all.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Cellular.xml b/qt4/spec/Connection_Interface_Cellular.xml
new file mode 100644
index 000000000..e9b10e3c5
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Cellular.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Cellular"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2008-2010 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Cellular">
+ <tp:added version="0.19.8">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface is for various cellular things (GSM and/or CDMA) that
+ aren't really applicable to other protocols.</p>
+ </tp:docstring>
+
+ <property name="MessageValidityPeriod" tp:name-for-bindings="Message_Validity_Period"
+ type="u" access="readwrite"
+ tp:is-connection-parameter='yup'>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Define how long should the service centre try message delivery before
+ giving up, failing delivery and deleting the message. A value of 0
+ means to use the service centre's default period.</p>
+
+ <p>The value specified is in seconds. Note that various protocols or
+ implementations may round the value up (eg. to a minute or hour
+ precision). The maximum validity period may vary depending on
+ protocol or provider.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="OverrideMessageServiceCentre"
+ tp:name-for-bindings="Override_Message_Service_Centre"
+ type="b" access="readwrite"
+ tp:is-connection-parameter='can i get a hell yeah?'>
+ <tp:added version='0.19.12'>Previously, as an undocumented
+ feature, setting <tp:member-ref>MessageServiceCentre</tp:member-ref>
+ to the empty string caused the SIM's default SMSC to be used.</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If <code>True</code>, SMSes will be sent via the service centre
+ specified by <tp:member-ref>MessageServiceCentre</tp:member-ref>. If
+ <code>False</code>, the SIM's default SMSC will be used, ignoring the
+ value of MessageServiceCentre.</p>
+
+ <tp:rationale>
+ <p>It could be desirable for a configuration interface to remember
+ the user's previous choice of custom SMSC, even if it's not in use.
+ This boolean allows that choice to be saved as an account parameter
+ by Mission Control, rather than the UI needing to save it elsewhere
+ to be restored if the user wants to reactivate it.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="MessageServiceCentre" tp:name-for-bindings="Message_Service_Centre"
+ type="s" access="readwrite"
+ tp:is-connection-parameter='HELL YEAH!!!'>
+ <tp:changed version='0.19.12'>This property's value is now
+ ignored unless
+ <tp:member-ref>OverrideMessageServiceCentre</tp:member-ref> is
+ <code>True</code>.</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+
+ <p>Address for the messaging service centre. Typically (as is the case
+ for GSM's SMSC), it's the ISDN / telephony address (ie. a phone
+ number). If
+ <tp:member-ref>OverrideMessageServiceCentre</tp:member-ref> is
+ <code>False</code>, this property's value should be ignored by the CM
+ in favour of the SIM's default SMSC.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="IMSI" tp:name-for-bindings="IMSI" type="s" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The International Mobile Subscriber Identifier, if it exists. This
+ would originate from a SIM card. If the IMSI is unknown, this will
+ contain an empty string ("").</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="IMSIChanged" tp:name-for-bindings="IMSI_Changed">
+ <tp:docstring>
+ Emitted when the IMSI for the connection changes. This sort of thing
+ is rare, but could happen on cellular phones that allow hot-swapping
+ of SIM cards. In the case of SIM swapping, this signal would be
+ emitted twice; the first time while the SIM is being ejected (with an
+ empty string), and the second time after a new SIM has been inserted
+ (assuming that the IMSI can be determined from the new SIM).
+ </tp:docstring>
+
+ <arg name="IMSI" type="s">
+ <tp:docstring>
+ The new IMSI value. This may be an empty string in the case where
+ the IMSI is being reset or removed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="MessageReducedCharacterSet"
+ tp:name-for-bindings="Message_Reduced_Character_Set"
+ type="b" access="readwrite"
+ tp:is-connection-parameter='no... just kidding! yes!'>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Determines how to encode SMSes containing characters that do not
+ fit into a non-Unicode character set.
+ If <code>False</code> (which SHOULD be the default), messages will
+ be encoded as UCS-2 and sent with no loss of fidelity (at the
+ potential financial cost of using twice as many SMSes); if
+ <code>True</code>, the message will be recoded in an
+ implementation‐specific way to fit into a GSM reduced character
+ set.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="MessageNationalCharacterSet"
+ tp:name-for-bindings="Message_National_Character_Set"
+ type="s" access="readwrite"
+ tp:is-connection-parameter='affirmative'>
+ <tp:added version="0.21.12"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Hint for the connection manager for the GSM character set that
+ should be used to send SMSes. The connection manager SHOULD follow
+ this hint unless it has other ways to determine a better encoding.
+ If the value is <code>"gsm"</code> (which SHOULD be the default),
+ SMSes will be encoded in the normal 7-bit GSM character set,
+ eventually falling back to UCS-2; see the
+ <tp:member-ref>MessageReducedCharacterSet</tp:member-ref> property
+ for details.
+ Other valid character sets are specified in the
+ <a href="http://www.3gpp.org/ftp/specs/archive/23_series/23.038/"
+ >GSM standard</a> and are, for instance, <code>"turkey"</code>,
+ <code>"spain"</code> or <code>"portugal"</code>.
+ If the SMS cannot be encoded using the requested character set the
+ behaviour is implementation-specific, but it is RECOMMENDED that
+ the connection manager should behave as if this property was set
+ to <code>"gsm"</code>.</p>
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Client_Types.xml b/qt4/spec/Connection_Interface_Client_Types.xml
new file mode 100644
index 000000000..97908561a
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Client_Types.xml
@@ -0,0 +1,218 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Client_Types"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ClientTypes">
+ <tp:added version="0.21.1">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface on connections to support protocols which allows users to
+ subscribe to the client types of their contacts.</p>
+
+ <p>One can connect to instant messaging networks on a huge variety of
+ devices, from PCs, to phones to consoles. It can be useful for users
+ to know what kind of device a contact is using so that he or she
+ can decide not to send that big file or start a video chat. This
+ interface exposes exactly this information for clients to display.</p>
+
+ <p>The client types are represented in strings, using the values
+ <a href="http://xmpp.org/registrar/disco-categories.html#client">
+ documented by the XMPP registrar</a> with some additional types
+ added for other protocols. A contact can set one or more client types
+ so this interface returns a list of strings to denote client types
+ for a contact. The well-known client types to be used are:</p>
+
+ <ul>
+ <li>bot</li>
+ <li>console (minimal non-GUI client used on dumb terminals or
+ text-only screens, <strong>not</strong> a games console)</li>
+ <li>handheld</li>
+ <li>pc</li>
+ <li>phone</li>
+ <li>web</li>
+<!-- Excluding these two because there's been no conclusion regarding my mail
+ to standards@xmpp.org about adding these two to their list:
+
+ <li>sms (the client is not actually an instant messaging client
+ but all messages sent to this contact will be delivered as SMSs)</li>
+ <li>game (a gaming device)</li>
+-->
+ </ul>
+
+ <p>If the empty list is given as the client types, this means that
+ details about the contact's client types are unknown. If there are
+ multiple resources of a contact online at one point in time, the
+ client types of the most available resource will be returned. In
+ other words, the returned client types are those for the resource whose
+ presence will be retreived using the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface">SimplePresence</tp:dbus-ref>
+ interface.</p>
+
+ <p>For example, if a contact has two resources:</p>
+
+ <ul>
+ <li>their phone, with presence "available"; and</li>
+ <li>their pc, with presence "busy";</li>
+ </ul>
+
+ <p>then the methods in this interface will return an array (with
+ one element: "phone") as the client types because that is the more
+ available resource. If at some later time the contact's phone's presence
+ changes to "away", the
+ <tp:member-ref>ClientTypesUpdated</tp:member-ref> signal will
+ notify that the contact's client types attribute has changed from
+ ["phone"] to ["pc"],
+ because "busy" is a more available presence than "away".</p>
+
+ </tp:docstring>
+
+ <tp:mapping name="Contact_Client_Types">
+ <tp:docstring>
+ A mapping from contact handle to client types.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Contact">
+ <tp:docstring>
+ A contact.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="as" name="Client_Types" tp:type="Contact_Client_Type[]">
+ <tp:docstring>
+ The contact's client types as documented earlier in this interface.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <method name="GetClientTypes" tp:name-for-bindings="Get_Client_Types">
+ <tp:docstring>
+ Return the client types of the given contacts, if they are
+ already known. If any of the given contacts' client types are
+ not known, request their current client types, but return
+ immediately without waiting for a reply; if a reply with a
+ non-empty client type array is later received for those
+ contacts, the
+ <tp:member-ref>ClientTypesUpdated</tp:member-ref> signal will
+ be emitted for them.
+
+ <tp:rationale>
+ This method is appropriate for "lazy" client type finding, for instance
+ displaying the client types (if available) of everyone in your contact
+ list.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The contacts whose client types should be returned or signalled.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Client_Types" type="a{uas}"
+ tp:type="Contact_Client_Types">
+ <tp:docstring>
+ The contacts' client types, if already known. Contacts whose client
+ types are not already known are omitted from the mapping; contacts known
+ to have no client type information appear in the mapping with an empty
+ list.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestClientTypes" tp:name-for-bindings="Request_Client_Types">
+ <tp:docstring>
+ Return the current client types of the given contact. If necessary, make
+ a request to the server for up-to-date information, and wait for a
+ reply.
+
+ <tp:rationale>
+ This method is appropriate for use in a "Contact Information..."
+ dialog; it can be used to show progress information (while waiting
+ for the method to return), and can distinguish between various error
+ conditions.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact whose client types should be returned.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Client_Types" type="as"
+ tp:type="Contact_Client_Type[]">
+ <tp:docstring>
+ The contact's client types. It MAY be empty, indicating that no client
+ type information was found.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied">
+ <tp:docstring>
+ The requested contact does not allow the local user to see their
+ client type information.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ClientTypesUpdated" tp:name-for-bindings="Client_Types_Updated">
+ <tp:docstring>
+ Emitted when a contact's client types change or become known.
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact.
+ </tp:docstring>
+ </arg>
+ <arg name="Client_Types" type="as" tp:type="Contact_Client_Type[]">
+ <tp:docstring>
+ The contact's client types, or an empty list to indicate that nothing
+ is known about the contact's client types.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:contact-attribute name="client-types" type="as"
+ tp:type="Contact_Client_Type[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same mapping that would be returned by
+ <tp:member-ref>GetClientTypes</tp:member-ref> for this contact.
+ Omitted from the result if the contact's client types are not
+ known.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:simple-type name="Contact_Client_Type" type="s"
+ array-name="Contact_Client_Type_List">
+ <tp:docstring>A string representing a single client type of a
+ contact.</tp:docstring>
+ </tp:simple-type>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Communication_Policy.xml b/qt4/spec/Connection_Interface_Communication_Policy.xml
new file mode 100644
index 000000000..31343de68
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Communication_Policy.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Communication_Policy"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Limited</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ </tp:license>
+
+ <interface
+ name="org.freedesktop.Telepathy.Connection.Interface.CommunicationPolicy.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.1">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.SimplePresence"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ This interface supports controlling which contacts are allowed
+ to initiate text chats, incoming calls, and other forms of
+ communication as supported by the underlying protocol. The
+ policies supported for different communication methods on this
+ connection are listed in the
+ <tp:member-ref>SupportedPolicies</tp:member-ref> property. The
+ current configuration is held in
+ <tp:member-ref>ActivePolicies</tp:member-ref>; it can be modified
+ using <tp:member-ref>SetPolicy</tp:member-ref>, and changes
+ are signalled by <tp:member-ref>PolicyChanged</tp:member-ref>.
+ </p>
+ </tp:docstring>
+
+ <tp:mapping name="Active_Policies_Map">
+ <tp:docstring>
+ A mapping of communication methods (channel types), and their
+ associated policy.
+ </tp:docstring>
+
+ <tp:member type="s" tp:type="DBus_Interface" name="Channel_Type">
+ <tp:docstring>
+ The channel interface with the policy.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="(uv)" tp:type="Access_Control" name="Active_Policy">
+ <tp:docstring>
+ The active policy for this channel type.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="SupportedPolicies"
+ tp:name-for-bindings="Supported_Policies" access="read"
+ type="a(asau)" tp:type="Supported_Policy[]">
+ <tp:docstring>
+ The communication policies supported by this connection.
+ </tp:docstring>
+ </property>
+
+ <property name="ActivePolicies" tp:name-for-bindings="Active_Policies"
+ access="read" type="a{s(uv)}" tp:type="Active_Policies_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The active communication policies on this
+ connection. Communication methods that are not in this
+ mapping are considered open.</p>
+
+ <p>For example, to allow incoming calls only from contacts
+ buddy list, and to allow text messages from anyone,
+ the policy would look like this:</p>
+
+ <pre>
+{
+ 'org.freedesktop.Telepathy.Channel.Type.Text' : Access_Control_Type_Open,
+ 'org.freedesktop.Telepathy.Channel.Type.Call' : Access_Control_Type_Publish_List
+}
+ </pre>
+
+ <p>Changes to this property are signalled by
+ <tp:member-ref>PolicyChanged</tp:member-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="SetPolicy" tp:name-for-bindings="Set_Policy">
+ <tp:docstring>
+ Set a policy for a communication method (channel
+ type). Depending on the server or protocol, more than one
+ communication method could be bound to the same policy, if
+ calling this method on one channel type changes the policy on
+ another channel type, the <tp:member-ref>PolicyChanged</tp:member-ref>
+ signal that would follow would include all the channel types
+ that have an altered policy.
+ </tp:docstring>
+ <arg name="Channel_Type" direction="in" type="s"
+ tp:type="DBus_Interface">
+ <tp:docstring>
+ The channel type to set the policy for.
+ </tp:docstring>
+ </arg>
+ <arg name="Policy" direction="in" type="(uv)"
+ tp:type="Access_Control">
+ <tp:docstring>
+ The policy to set for this channel.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="PolicyChanged" tp:name-for-bindings="Policy_Changed">
+ <tp:docstring>
+ <tp:member-ref>ActivePolicies</tp:member-ref> has
+ changed. This occurs when the server unilaterally changed the
+ policy or <tp:member-ref>SetPolicy</tp:member-ref> has been
+ called.
+ </tp:docstring>
+ <arg name="Changed_Policies" type="a{s(uv)}"
+ tp:type="Active_Policies_Map">
+ <tp:docstring>
+ A subset of the active policies that have changed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:struct name="Supported_Policy" array-name="Supported_Policy_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The communication methods (channel types), and the policies
+ that can be applied to them. This is server and protocol
+ dependant.</p>
+
+ <p>Grouped channel types will always have the same policy applied
+ to them.</p>
+
+ <tp:rationale>
+ Different protocols have different limitations to the
+ granularity of communication policies. One protocol might be
+ able to set a different policy for VoIP calls and text chat,
+ while another protocol might only be able to set one policy
+ to both VoIP and text chat.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:member type="as" tp:type="DBus_Interface[]"
+ name="Channel_Types">
+ <tp:docstring>
+ A list of channel interfaces that support these policies.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="au" tp:type="Access_Control_Type[]"
+ name="Supported_Policies">
+ <tp:docstring>
+ A list of supported policies.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ </interface>
+</node>
diff --git a/qt4/spec/Connection_Interface_Contact_Blocking.xml b/qt4/spec/Connection_Interface_Contact_Blocking.xml
new file mode 100644
index 000000000..756fd4db8
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Contact_Blocking.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contact_Blocking" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009–2011 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ContactBlocking">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.ContactList"/>
+ <tp:added version='0.21.13'>Changes from the draft:
+ methods and signals now return <tp:type>Handle_Identifier_Map</tp:type>
+ (<code>a{us}</code>) rather than bare lists of contact handles
+ (<code>au</code>)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for connections where contacts can be blocked from
+ communicating with this user and receiving this user's presence.
+ Clients may retrieve a list of currently-blocked contacts using
+ <tp:member-ref>RequestBlockedContacts</tp:member-ref>, and listen for
+ <tp:member-ref>BlockedContactsChanged</tp:member-ref> to be notified
+ when contacts are blocked and unblocked. The
+ <tp:member-ref>BlockContacts</tp:member-ref> and
+ <tp:member-ref>UnblockContacts</tp:member-ref> methods do what they say
+ on the tin; depending on the value of the
+ <tp:member-ref>ContactBlockingCapabilities</tp:member-ref> property,
+ contacts may be reported for spam or other abuse when calling
+ <tp:member-ref>BlockContacts</tp:member-ref>.</p>
+
+ <p>This interface is intended for protocols where blocking contacts
+ persists on the server between connections; connection managers for
+ protocols with no server-side support for blocking contacts MAY choose
+ to implement this interface using an on-disk file of blocked
+ contacts or some other means to store blocked contacts between
+ connections.</p>
+
+ <p>This interface is intended to replace the
+ <tp:dbus-ref namespace="ofdT.Channel.Type">ContactList</tp:dbus-ref>
+ channel with <tp:dbus-ref
+ namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>
+ <code>List</code> and <tp:dbus-ref
+ namespace='ofdT.Channel'>TargetID</tp:dbus-ref> <code>"deny"</code>
+ (along with the <tp:dbus-ref
+ namespace='ofdT.Connection.Interface'>ContactList</tp:dbus-ref> and
+ <tp:dbus-ref
+ namespace='ofdT.Connection.Interface'>ContactGroups</tp:dbus-ref>
+ interfaces replacing other channels with <tp:dbus-ref
+ namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>
+ <code>List</code> and <tp:dbus-ref
+ namespace='ofdT.Channel'>TargetHandleType</tp:dbus-ref>
+ <code>Group</code>, respectively).</p>
+ </tp:docstring>
+
+ <method name="BlockContacts" tp:name-for-bindings="Block_Contacts">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Direct the server to block some contacts. The precise effect is
+ protocol-dependent, but SHOULD include ignoring all current and
+ subsequent communications from the given contacts, avoiding sending
+ presence to them in future, and if they were already receiving the
+ local user's presence, behaving as if the local user went
+ offline.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" type="au" direction="in" tp:type="Contact_Handle[]">
+ <tp:docstring>Some contacts to block. If some of the contacts in this
+ list are already blocked, the connection manager MUST act as if they
+ were not specified in this list.</tp:docstring>
+ </arg>
+
+ <arg name="Report_Abusive" type="b" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>In addition to blocking, report these contacts as abusive to the
+ server administrators.</p>
+
+ <p>Clients can determine whether this capability is available by
+ checking the
+ <tp:member-ref>ContactBlockingCapabilities</tp:member-ref>
+ property. If this argument is set to <code>True</code> by a client
+ despite <tp:member-ref>ContactBlockingCapabilities</tp:member-ref>
+ not containing the <code>Can_Report_Abusive</code> flag, the
+ connection manager SHOULD act as if it were <code>False</code> and
+ simply block the supplied contacts.</p>
+
+ <tp:rationale>
+ <p>A correct user interface shouldn't get this far without knowing
+ that reporting abusive contacts is not supported. If it does,
+ then the user has expressed their intention to block these
+ contacts. Returning an error would leave the UI with three
+ options:</p>
+
+ <ul>
+ <li>Ignore the error, leaving the contacts not actually blocked;</li>
+ <li>Display an error to the user;</li>
+ <li>Call this method again, passing <code>False</code> for this
+ argument.</li>
+ </ul>
+
+ <p>None of these seem preferable to the CM just ignoring this flag
+ if it doesn't support it: that way, the contacts will be blocked,
+ as the user requested, and UIs have fewer ways to mess up
+ entirely.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="UnblockContacts" tp:name-for-bindings="Unblock_Contacts">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Direct the server to unblock some contacts.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" type="au" direction="in" tp:type="Contact_Handle[]">
+ <tp:docstring>Some contacts to unblock. If some of the contacts in this
+ list are not currently blocked, the connection manager MUST act as if
+ they were not specified in this list.</tp:docstring>
+ </arg>
+ </method>
+
+ <method name="RequestBlockedContacts"
+ tp:name-for-bindings="Request_Blocked_Contacts">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>List the contacts that are blocked.</p>
+
+ <p>Clients SHOULD allow a relatively long timeout for calls to this
+ method, since on some protocols contact blocking is part of the
+ contact list, which can take a significant time to retrieve.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" type="a{us}" direction="out"
+ tp:type="Handle_Identifier_Map">
+ <tp:docstring>The blocked contacts’ handles, together with their
+ identifiers.</tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="BlockedContactsChanged"
+ tp:name-for-bindings="Blocked_Contacts_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the list of blocked contacts is first retrieved
+ (before returning from any pending calls to
+ <tp:member-ref>RequestBlockedContacts</tp:member-ref>), and
+ whenever the list of blocked contacts subsequently changes.</p>
+ </tp:docstring>
+
+ <arg name="Blocked_Contacts" type="a{us}" tp:type="Handle_Identifier_Map">
+ <tp:docstring>Contacts added to the result of
+ <tp:member-ref>RequestBlockedContacts</tp:member-ref>.</tp:docstring>
+ </arg>
+
+ <arg name="Unblocked_Contacts" type="a{us}"
+ tp:type="Handle_Identifier_Map">
+ <tp:docstring>Contacts removed from the result of
+ <tp:member-ref>RequestBlockedContacts</tp:member-ref>.</tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:contact-attribute name="blocked" type="b">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p><code>True</code> if the contact would be in the result of
+ <tp:member-ref>RequestBlockedContacts</tp:member-ref>;
+ <code>False</code> or omitted if the contact is not blocked, or if it
+ is unknown whether the contact is blocked.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <property name="ContactBlockingCapabilities"
+ tp:name-for-bindings="Contact_Blocking_Capabilities"
+ tp:type="Contact_Blocking_Capabilities" type="u" access="read"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Additional capabilities for contact blocking; currently, this is
+ limited to whether contacts may be reported as abusive.</p>
+
+ <p>Note that there is no capability for supporting blocking itself:
+ the presence of this interface on a <tp:dbus-ref
+ namespace='ofdT'>Connection</tp:dbus-ref> indicates that blocking
+ contacts is supported.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:flags name="Contact_Blocking_Capabilities" type="u"
+ value-prefix="Contact_Blocking_Capability">
+ <tp:flag suffix="Can_Report_Abusive" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ When calling <tp:member-ref>BlockContacts</tp:member-ref>, the
+ contacts may be reporting as abusive to the server administrators by
+ setting <var>Report_Abusive</var> to <code>True</code>.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Contact_Capabilities.xml b/qt4/spec/Connection_Interface_Contact_Capabilities.xml
new file mode 100644
index 000000000..fb13c37d7
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Contact_Capabilities.xml
@@ -0,0 +1,306 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contact_Capabilities" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006, 2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006, 2008 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.17.28">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Contact capabilities describe the channel classes which may be
+ created with a given contact in advance of attempting to create a
+ channel. Each capability represents a commitment by the
+ connection manager that it will ordinarily be able to create a channel
+ with a contact when given a request with the properties defined by the
+ channel class.</p>
+
+ <p>Capabilities pertain to particular contact handles, and represent
+ activities such as having a text chat, a voice call with the user or a
+ stream tube of a defined type.</p>
+
+ <p>This interface also enables user interfaces to notify the connection
+ manager what capabilities to advertise for the user to other contacts.
+ This is done by using the
+ <tp:member-ref>UpdateCapabilities</tp:member-ref> method.</p>
+
+ <tp:rationale>
+ <p>XMPP is a major user of this interface: XMPP contacts will not,
+ in general, be callable using VoIP unless they advertise suitable
+ Jingle capabilities.</p>
+
+ <p>Many other protocols also have some concept of capability flags,
+ which this interface exposes in a protocol-independent way.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:struct name="Handler_Capabilities"
+ array-name="Handler_Capabilities_List">
+ <tp:docstring>
+ A structure representing the capabilities of a single client.
+ </tp:docstring>
+
+ <tp:member name="Well_Known_Name" type="s" tp:type="DBus_Well_Known_Name">
+ <tp:docstring>
+ For implementations of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Client</tp:dbus-ref>
+ interface, the well-known bus name name of the client; for any other
+ process, any other reversed domain name that uniquely identifies it.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Channel_Classes" type="aa{sv}"
+ tp:type="String_Variant_Map[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of channel classes that can be handled by this client.
+ This will usually be a copy of the client's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref>
+ property.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Capabilities"
+ type="as" tp:type="Handler_Capability_Token[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of client capabilities supported by this client, to be
+ used by the connection manager to determine what capabilities to
+ advertise. This will usually be a copy of the client's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Client.Handler">Capabilities</tp:dbus-ref>
+ property.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="UpdateCapabilities" tp:name-for-bindings="Update_Capabilities">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Alter the connection's advertised capabilities to include
+ the intersection of the given clients' capabilities with what the
+ connection manager is able to implement.</p>
+
+ <p>On connections managed by the ChannelDispatcher, processes other
+ than the ChannelDispatcher SHOULD NOT call this method, and the
+ ChannelDispatcher SHOULD use this method to advertise the
+ capabilities of all the registered <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Client.Handler</tp:dbus-ref>
+ implementations.On connections not managed by the ChannelDispatcher,
+ clients MAY use this method directly, to indicate the channels they
+ will handle and the extra capabilities they have.</p>
+
+ <p>Upon a successful invocation of this method, the connection manager
+ will only emit the
+ <tp:member-ref>ContactCapabilitiesChanged</tp:member-ref> signal
+ for the user's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">SelfHandle</tp:dbus-ref>
+ if, in the underlying protocol, the new capabilities are distinct
+ from the previous state.</p>
+
+ <tp:rationale>
+ <p>The connection manager will essentially intersect the provided
+ capabilities and the channel classes it implements. Therefore,
+ certain properties which are never fixed for a channel class
+ (such as the target handle, or the Parameters property of a tube
+ channel) will almost certainly not be advertised.</p>
+ </tp:rationale>
+
+ <p>This method MAY be called on a newly-created connection while it
+ is still in the DISCONNECTED state, to request that when the
+ connection connects, it will do so with the appropriate
+ capabilities. Doing so MUST NOT fail.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="Handler_Capabilities" type="a(saa{sv}as)"
+ tp:type="Handler_Capabilities[]">
+ <tp:docstring>
+ <p>The capabilities of one or more clients.</p>
+
+ <p>For each client in the given list, any capabilities previously
+ advertised for the same client name are discarded, then replaced by
+ the capabilities indicated.</p>
+
+ <p>As a result, if a client becomes unavailable, this method SHOULD
+ be called with a <tp:type>Handler_Capabilities</tp:type> structure
+ containing its name, an empty list of channel classes, and an
+ empty list of capabilities. When this is done, the connection
+ manager SHOULD free all memory associated with that client name.</p>
+
+ <tp:rationale>
+ <p>This method takes a list of clients so that
+ when the channel dispatcher first calls it (with a list of all
+ the Handlers that are initially available), the changes can be
+ made atomically, with only one transmission of updated
+ capabilities to the network. Afterwards, the channel dispatcher
+ will call this method with a single-element list every time
+ a Handler becomes available or unavailable.</p>
+ </tp:rationale>
+
+ <p>The connection manager MUST ignore any channel classes and client
+ capabilities for which there is no representation in the protocol
+ or no support in the connection manager.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetContactCapabilities"
+ tp:name-for-bindings="Get_Contact_Capabilities">
+ <arg direction="in" name="Handles" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of contact handles for this connection.</p>
+
+ <p>The handle zero MUST NOT be included in the request.</p>
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a{ua(a{sv}as)}"
+ tp:type="Contact_Capabilities_Map" name="Contact_Capabilities">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from contact handles to lists of requestable channel
+ classes, representing the channel requests that are expected
+ to succeed for that contact.</p>
+
+ <p>Contacts listed among Handles whose capabilities are unknown
+ SHOULD be omitted from this map; contacts known to have an empty
+ set of capabilities SHOULD be included in the keys of this map,
+ with an empty array as the corresponding value.</p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Returns an array of requestable channel classes for the given
+ contact handles, representing the channel requests that are
+ expected to succeed.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ The handle does not represent a contact. Zero is always invalid.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="ContactCapabilitiesChanged"
+ tp:name-for-bindings="Contact_Capabilities_Changed">
+ <arg name="caps" type="a{ua(a{sv}as)}"
+ tp:type="Contact_Capabilities_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ All the capabilities of the contacts
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Announce that there has been a change of capabilities on the
+ given handles. A single signal can be emitted for several
+ contacts.</p>
+
+ <tp:rationale>
+ <p>The underlying protocol can get several contacts' capabilities at
+ the same time.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </signal>
+
+ <tp:mapping name="Contact_Capabilities_Map"
+ array-name="Contact_Capabilities_Map_List">
+ <tp:docstring>A mapping from contact handle to their capabilities.
+ </tp:docstring>
+ <tp:member type="u" name="Key" tp:type="Contact_Handle">
+ <tp:docstring>
+ A contact handle.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="a(a{sv}as)" name="Value"
+ tp:type="Requestable_Channel_Class[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The contact's capabilities. These should be represented
+ in the same way as in <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests"
+ >RequestableChannelClasses</tp:dbus-ref>,
+ except that they may have more fixed properties or fewer allowed
+ properties, to represent contacts who do not have all the
+ capabilities of the connection.</p>
+
+ <p>In particular, requestable channel classes for channels with
+ target handle type Contact MUST list <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandleType</tp:dbus-ref> among their fixed properties when
+ they appear here, and clients MAY assume that this will be the
+ case.</p>
+
+ <tp:rationale>
+ <p>This matches the initial implementations - service-side in
+ telepathy-gabble, and client-side in telepathy-qt4 - and means
+ that clients can use exactly the same code to interpret
+ RequestableChannelClasses and contact capabilities.</p>
+ </tp:rationale>
+
+ <p>Channel classes with target handle type Handle_Type_Contact
+ indicate that a request that matches the channel class, and also
+ either has the contact's handle as <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel"
+ >TargetHandle</tp:dbus-ref> or the contact's identifier as
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel"
+ >TargetID</tp:dbus-ref>, can be expected to succeed. Connection
+ managers SHOULD NOT include the TargetHandle or TargetID as a
+ fixed property in contact capabilities.</p>
+
+ <tp:rationale>
+ <p>This makes one channel class sufficient to describe requests via
+ TargetHandle or TargetID, and is necessary in order to allow
+ clients to interpret RequestableChannelClasses and contact
+ capabilities with the same code.</p>
+ </tp:rationale>
+
+ <p>Channel classes with target handle type Handle_Type_Room or
+ Handle_Type_None indicate that if a channel matching the channel
+ class is created, then inviting the contact to that channel
+ can be expected to succeed.</p>
+
+ <tp:rationale>
+ <p>To support room-based XMPP protocols like
+ <a href="http://telepathy.freedesktop.org/wiki/Muji">Muji</a>
+ and MUC Tubes, it's necessary to be able to discover who can be
+ invited to a given room channel; most XMPP contacts won't
+ support being invited into a Muji conference call, at least
+ in the short to medium term.</p>
+ </tp:rationale>
+
+ <p>No interpretation is defined for channel classes with any other
+ target handle type, or for channel classes that do not fix a
+ target handle type, in this version of the Telepathy
+ specification.</p>
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:contact-attribute name="capabilities"
+ type="a(a{sv}as)" tp:type="Requestable_Channel_Class[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same structs that would be returned by
+ <tp:member-ref>GetContactCapabilities</tp:member-ref>.
+ Omitted from the result if the contact's capabilities
+ are not known; present in the result as an empty array if the
+ contact is known to have no capabilities at all.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Contact_Groups.xml b/qt4/spec/Connection_Interface_Contact_Groups.xml
new file mode 100644
index 000000000..5282a8272
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Contact_Groups.xml
@@ -0,0 +1,549 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contact_Groups" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ContactGroups">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.ContactList"/>
+ <tp:added version="0.21.0">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for connections in which contacts can be placed in
+ user-defined groups.</p>
+
+ <p>The most basic functionality of this interface is to list and monitor
+ a contact's set of groups. To do this, use the
+ <tp:member-ref>GroupsChanged</tp:member-ref> signal, and the
+ <tp:token-ref>groups</tp:token-ref> contact attribute (this should
+ usually be done by connecting to the GroupsChanged signal, then
+ calling <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref> with this interface
+ included in the Interfaces argument). Simple user interfaces can
+ limit themselves to displaying that information, and ignore the rest
+ of this interface: to ensure that this works,
+ <tp:member-ref>GroupsChanged</tp:member-ref> is emitted for every
+ change, even if that change could be inferred from another signal
+ such as <tp:member-ref>GroupsRemoved</tp:member-ref>.</p>
+
+ <p>Looking at contacts' lists of groups is sufficient to present a
+ user interface resembling XMPP's data model, in which groups behave
+ like tags applied to contacts, and so an empty group cannot exist
+ or is not interesting. However, some protocols model groups as
+ objects in their own right. User interfaces may either track
+ the set of groups via the <tp:member-ref>Groups</tp:member-ref>
+ property and the <tp:member-ref>GroupsCreated</tp:member-ref> and
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals, or ignore
+ this extra information.</p>
+
+ <p>Similarly, in some protocols it is possible to rename a group as
+ a single atomic operation. Simpler user interfaces will
+ see the new name being created, the old name being removed, and the
+ members moving to the new name, via the signals described above.
+ More advanced user interfaces can optionally distinguish between an
+ atomic rename and a create/remove pair, and display renamed groups
+ differently, by monitoring the
+ <tp:member-ref>GroupRenamed</tp:member-ref> signal.</p>
+
+ <p>This interface also provides various methods to manipulate
+ user-defined groups, which can be expected to work if
+ <tp:member-ref>GroupStorage</tp:member-ref> is not None.</p>
+
+ <p>Depending on the protocol, some methods might be implemented by
+ more than one protocol operation; for instance, in a
+ "contact-centric" protocol like XMPP,
+ <tp:member-ref>SetContactGroups</tp:member-ref> is a single
+ protocol operation and <tp:member-ref>SetGroupMembers</tp:member-ref>
+ requires a protocol operation per contact, whereas in a more
+ "group-centric" protocol it might be the other way around. User
+ interfaces SHOULD call whichever method most closely resembles the
+ way in which the user's action was represented in the UI, and
+ let the connection manager deal with the details.</p>
+ </tp:docstring>
+
+ <property name="DisjointGroups" tp:name-for-bindings="Disjoint_Groups"
+ access="read" type="b">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>True if each contact can be in at most one group; false if each
+ contact can be in many groups.</p>
+
+ <p>This property cannot change after the connection has moved to the
+ Connected state. Until then, its value is undefined, and it may
+ change at any time, without notification.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="GroupStorage" tp:name-for-bindings="Group_Storage"
+ type="u" tp:type="Contact_Metadata_Storage_Type" access="read">
+ <tp:docstring>
+ <p>Indicates the extent to which contacts' groups can be set and
+ stored.</p>
+
+ <p>This property cannot change after the connection has moved to the
+ Connected state. Until then, its value is undefined, and it may
+ change at any time, without notification.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:contact-attribute name="groups" type="as">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The names of groups of which a contact is a member.</p>
+
+ <p>Change notification is via
+ <tp:member-ref>GroupsChanged</tp:member-ref>; clients can also
+ get extra context for group membership changes by receiving
+ <tp:member-ref>GroupRenamed</tp:member-ref> and
+ <tp:member-ref>GroupsRemoved</tp:member-ref>.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <signal name="GroupsChanged" tp:name-for-bindings="Groups_Changed">
+ <tp:docstring>
+ Emitted when contacts' groups change.
+ </tp:docstring>
+
+ <arg name="Contact" type="au" tp:type="Contact_Handle">
+ <tp:docstring>The relevant contacts.</tp:docstring>
+ </arg>
+
+ <arg name="Added" type="as">
+ <tp:docstring>The names of groups to which the contacts were
+ added.</tp:docstring>
+ </arg>
+
+ <arg name="Removed" type="as">
+ <tp:docstring>The names of groups from which the contacts were
+ removed.</tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Groups" type="as" access="read"
+ tp:name-for-bindings="Groups">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The names of all groups that currently exist. This may be a
+ larger set than the union of all contacts' <code>groups</code>
+ contact attributes, if the connection allows groups to be
+ empty.</p>
+
+ <p>Change notification is via
+ <tp:member-ref>GroupsCreated</tp:member-ref> and
+ <tp:member-ref>GroupsRemoved</tp:member-ref>; clients can also
+ distinguish between a create/remove pair and a renamed group by
+ receiving <tp:member-ref>GroupRenamed</tp:member-ref>.</p>
+
+ <p>This property's value is not meaningful until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> has become Success.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="GroupsCreated" tp:name-for-bindings="Groups_Created">
+ <tp:docstring>
+ Emitted when new, empty groups are created. This will often be
+ followed by <tp:member-ref>GroupsChanged</tp:member-ref> signals that
+ add some members.
+ </tp:docstring>
+
+ <arg name="Names" type="as">
+ <tp:docstring>The names of the new groups.</tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="GroupRenamed" tp:name-for-bindings="Group_Renamed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when a group is renamed, in protocols where this can
+ be distinguished from group creation, removal and membership
+ changes.</p>
+
+ <p>Immediately after this signal is emitted,
+ <tp:member-ref>GroupsCreated</tp:member-ref> MUST signal the
+ creation of a group with the new name, and
+ <tp:member-ref>GroupsRemoved</tp:member-ref> MUST signal the
+ removal of a group with the old name.</p>
+
+ <tp:rationale>
+ <p>Emitting these extra signals, in this order, means that clients
+ that are interested in the set of groups that exist (but treat a
+ rename and a create/remove pair identically) can ignore the
+ GroupRenamed signal entirely.</p>
+ </tp:rationale>
+
+ <p>If the group was not empty, immediately after those signals are
+ emitted, <tp:member-ref>GroupsChanged</tp:member-ref> MUST signal
+ that the members of that group were removed from the old name
+ and added to the new name.</p>
+
+ <p>On connection managers where groups behave like tags, renaming a
+ group MAY be signalled as a set of
+ <tp:member-ref>GroupsCreated</tp:member-ref>,
+ <tp:member-ref>GroupsRemoved</tp:member-ref> and
+ <tp:member-ref>GroupsChanged</tp:member-ref> signals, instead of
+ emitting this signal.</p>
+
+ <tp:rationale>
+ <p>On protocols like XMPP, another resource "renaming a group" is
+ indistinguishable from changing contacts' groups individually.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Old_Name" type="s">
+ <tp:docstring>The old name of the group.</tp:docstring>
+ </arg>
+
+ <arg name="New_Name" type="s">
+ <tp:docstring>The new name of the group.</tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="GroupsRemoved" tp:name-for-bindings="Groups_Removed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when one or more groups are removed. If they had members at
+ the time that they were removed, then immediately after this signal
+ is emitted, <tp:member-ref>GroupsChanged</tp:member-ref> MUST signal
+ that their members were removed.</p>
+
+ <tp:rationale>
+ <p>Emitting the signals in this order allows for two modes of
+ operation. A client interested only in a contact's set of groups
+ can ignore <tp:member-ref>GroupsRemoved</tp:member-ref> and rely
+ on the <tp:member-ref>GroupsChanged</tp:member-ref> signal that
+ will follow; a more elaborate client wishing to distinguish between
+ all of a group's members being removed, and the group itself
+ being removed, can additionally watch for
+ <tp:member-ref>GroupsRemoved</tp:member-ref> and use it to
+ disambiguate.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Names" type="as">
+ <tp:docstring>The names of the groups.</tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="SetContactGroups" tp:name-for-bindings="Set_Contact_Groups">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Add the given contact to the given groups (creating new groups
+ if necessary), and remove them from all other groups.</p>
+
+ <tp:rationale>
+ <p>This is the easiest and most correct way to implement user
+ interfaces that display a single contact with a list of groups,
+ resulting in a user expectation that when they apply the changes,
+ the contact's set of groups will become exactly what was
+ displayed.</p>
+ </tp:rationale>
+
+ <p>If the user is removed from a group of which they were the only
+ member, the group MAY be removed automatically.</p>
+
+ <tp:rationale>
+ <p>In protocols like XMPP where groups behave like tags, a group
+ with no members has no protocol representation.</p>
+ </tp:rationale>
+
+ <p>Any <tp:member-ref>GroupsCreated</tp:member-ref>,
+ <tp:member-ref>GroupsChanged</tp:member-ref> and
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals that result from
+ this method call MUST be emitted before the method returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> changes to Success.
+ If the ContactListState is Failure, this method SHOULD raise the
+ same error as
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle" direction="in">
+ <tp:docstring>The contact to alter.</tp:docstring>
+ </arg>
+
+ <arg name="Groups" type="as" direction="in">
+ <tp:docstring>The set of groups which the contact should be
+ in.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>Raised if <tp:member-ref>DisjointGroups</tp:member-ref>
+ is true and the list of groups has more than one
+ member.</tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised if <tp:member-ref>GroupStorage</tp:member-ref>
+ is Contact_Metadata_Storage_Type_None, i.e. groups cannot be edited.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="SetGroupMembers" tp:name-for-bindings="Set_Group_Members">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Add the given members to the given group (creating it if necessary),
+ and remove all other members.</p>
+
+ <tp:rationale>
+ <p>This is the easiest and most correct way to implement user
+ interfaces that display a single group with a list of contacts,
+ resulting in a user expectation that when they apply the changes,
+ the groups's set of members will become exactly what was
+ displayed.</p>
+ </tp:rationale>
+
+ <p>If <tp:member-ref>DisjointGroups</tp:member-ref> is true,
+ this will also remove each member from their previous group.</p>
+
+ <p>If the user is removed from a group of which they were the only
+ member, the group MAY be removed automatically.</p>
+
+ <p>Any <tp:member-ref>GroupsCreated</tp:member-ref>,
+ <tp:member-ref>GroupsChanged</tp:member-ref> and
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals that result from
+ this method call MUST be emitted before the method returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> changes to Success.
+ If the ContactListState is Failure, this method SHOULD raise the
+ same error as
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Group" type="s" direction="in">
+ <tp:docstring>The group to alter.</tp:docstring>
+ </arg>
+
+ <arg name="Members" type="au" tp:type="Contact_Handle[]" direction="in">
+ <tp:docstring>The set of members for the group. If this set is
+ empty, this method MAY remove the group.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised if <tp:member-ref>GroupStorage</tp:member-ref>
+ is Contact_Metadata_Storage_Type_None, i.e. groups cannot be edited.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="AddToGroup" tp:name-for-bindings="Add_To_Group">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Add the given members to the given group, creating it if
+ necessary.</p>
+
+ <p>If <tp:member-ref>DisjointGroups</tp:member-ref> is true,
+ this will also remove each member from their previous group.</p>
+
+ <tp:rationale>
+ <p>This is good for user interfaces in which you can edit groups
+ via drag-and-drop.</p>
+ </tp:rationale>
+
+ <p>Any <tp:member-ref>GroupsCreated</tp:member-ref>,
+ <tp:member-ref>GroupsChanged</tp:member-ref> and
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals that result from
+ this method call MUST be emitted before the method returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> changes to Success.
+ If the ContactListState is Failure, this method SHOULD raise the
+ same error as
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Group" type="s" direction="in">
+ <tp:docstring>The group to alter.</tp:docstring>
+ </arg>
+
+ <arg name="Members" type="au" tp:type="Contact_Handle[]" direction="in">
+ <tp:docstring>The set of members to include in the group.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised if <tp:member-ref>GroupStorage</tp:member-ref>
+ is Contact_Metadata_Storage_Type_None, i.e. groups cannot be edited.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RemoveFromGroup" tp:name-for-bindings="Remove_From_Group">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Remove the given members from the given group.</p>
+
+ <tp:rationale>
+ <p>This is good for user interfaces in which you can edit groups
+ via drag-and-drop.</p>
+ </tp:rationale>
+
+ <p>Any <tp:member-ref>GroupsChanged</tp:member-ref> or
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals that result from
+ this method call MUST be emitted before the method returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> changes to Success.
+ If the ContactListState is Failure, this method SHOULD raise the
+ same error as
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Group" type="s" direction="in">
+ <tp:docstring>The group to alter. If it does not exist, then it has
+ no members by definition, so this method SHOULD return
+ successfully.</tp:docstring>
+ </arg>
+
+ <arg name="Members" type="au" tp:type="Contact_Handle[]" direction="in">
+ <tp:docstring>The set of members to remove from the group. It is not
+ an error to remove members who are already not in the group.
+ If there are no members left in the group afterwards, the group MAY
+ itself be removed.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised if <tp:member-ref>GroupStorage</tp:member-ref>
+ is Contact_Metadata_Storage_Type_None, i.e. groups cannot be edited.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RemoveGroup" tp:name-for-bindings="Remove_Group">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Remove all members from the given group, then remove the group
+ itself. If the group already does not exist, this method SHOULD
+ return successfully.</p>
+
+ <p>Any <tp:member-ref>GroupsChanged</tp:member-ref> or
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals that result from
+ this method call MUST be emitted before the method returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> changes to Success.
+ If the ContactListState is Failure, this method SHOULD raise the
+ same error as
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Group" type="s" direction="in">
+ <tp:docstring>The group to remove.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised if <tp:member-ref>GroupStorage</tp:member-ref>
+ is Contact_Metadata_Storage_Type_None, i.e. groups cannot be edited.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RenameGroup" tp:name-for-bindings="Rename_Group">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Rename the given group.</p>
+
+ <p>On protocols where groups behave like tags, this is an API
+ short-cut for adding all of the group's members to a group with
+ the new name, then removing the old group.</p>
+
+ <tp:rationale>
+ <p>Otherwise, clients can't perform this operation atomically, even
+ if the connection could.</p>
+ </tp:rationale>
+
+ <p>Any <tp:member-ref>GroupRenamed</tp:member-ref> or
+ <tp:member-ref>GroupsRemoved</tp:member-ref> signals that result from
+ this method call MUST be emitted before the method returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >ContactListState</tp:dbus-ref> changes to Success.
+ If the ContactListState is Failure, this method SHOULD raise the
+ same error as
+ <tp:dbus-ref namespace="ofdT.Connection.Interface.ContactList"
+ >GetContactListAttributes</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Old_Name" type="s" direction="in">
+ <tp:docstring>The group to rename.</tp:docstring>
+ </arg>
+
+ <arg name="New_Name" type="s" direction="in">
+ <tp:docstring>The new name for the group.</tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Raised if <tp:member-ref>GroupStorage</tp:member-ref>
+ is Contact_Metadata_Storage_Type_None, i.e. groups cannot be edited.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.DoesNotExist">
+ <tp:docstring>Raised if there is no group with that
+ name.</tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>Raised if there is already a group with the new
+ name.</tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet"/>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Contact_Info.xml b/qt4/spec/Connection_Interface_Contact_Info.xml
new file mode 100644
index 000000000..527d32522
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Contact_Info.xml
@@ -0,0 +1,550 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contact_Info" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2008 Nokia Corporation </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ContactInfo">
+ <tp:added version="0.19.4">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:struct name="Contact_Info_Field" array-name="Contact_Info_Field_List">
+ <tp:member type="s" name="Field_Name">
+ <tp:docstring>
+ The name of the field; this is the lowercased name of a vCard field.
+ For example, a field representing a contact's address would be named
+ "adr".
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="as" name="Parameters">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of vCard type parameters applicable to this field, with their
+ values. The type parameter names, and any values that are
+ case-insensitive in vCard, MUST be in lower case. For example, a
+ contact's preferred home address would have parameters
+ 'type=home' and 'type=pref'.</p>
+
+ <tp:rationale>
+ The type parameter 'type' is likely to be the most common, but
+ there can be others, such as 'language=en'.
+ </tp:rationale>
+
+ <p>Characters which are required to be escaped in vCard type
+ parameters should not be escaped in this list. For instance,
+ a field "X-FOO;SEMICOLON=\;:bar" in a vCard would become
+ ('x-foo', ['semicolon=;'], ['bar']) in this interface.</p>
+
+ <tp:rationale>
+ This avoids Telepathy UIs having to understand the escaping and
+ unescaping rules for vCards. The type parameter name is not
+ allowed (by RFC 2425) to contain an '=' character, so no ambiguity
+ is introduced.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="as" name="Field_Value">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For unstructured vCard fields (such as 'fn', a formatted name
+ field), a single-element array containing the field's value.</p>
+
+ <p>For structured fields (such as 'adr', an address field), an array
+ corresponding to the semicolon-separated elements of the field (with
+ empty strings for empty elements).</p>
+
+ <p>A vCard field with multiple comma-separated values, such as
+ 'nickname', should be represented by several
+ <tp:type>Contact_Info_Field</tp:type>s.</p>
+
+ <p>Characters which are required to be escaped in vCard values, such as
+ semi-colons and newlines, should not be escaped in this list (e.g. if
+ a value contains a newline, the data passed over D-Bus should
+ contain a literal newline character).</p>
+
+ <tp:rationale>
+ An earlier draft of this interface split structured vCard fields
+ into multiple Telepathy-level fields; for example, 'n' became
+ 'family-name', 'given-name', etc. But under this representation,
+ omitting empty components leads to difficulty identifying where one
+ name ends and another begins. Consider the fields ['given-name',
+ 'honorific-suffixes', 'family-name', 'honorific-prefixes']: does
+ this represent two 'n' fields, or one with incorrect component
+ ordering?
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Represents one piece of information about a contact, as modelled by
+ a single vCard field. Of the fields defined in RFC 2426, common
+ examples include:</p>
+
+ <dl>
+ <dt>fn</dt>
+ <dd>The contact's full name, formatted to their liking</dd>
+
+ <dt>n</dt>
+ <dd>The contact's full name, divided into five parts: family name,
+ given name, additional names, honorific prefixes, and honorific
+ suffixes</dd>
+
+ <dt>org</dt>
+ <dd>The contact's organisation, divided into the organization's name
+ possibly followed by one or more organizational unit names.</dd>
+
+ <dt>adr</dt>
+ <dd>A street address for the contact, divided into seven components:
+ post office box, extended address, street address, locality (e.g.,
+ city), region (e.g., state or province), the postal code, and the
+ country name.</dd>
+
+ <dt>label</dt>
+ <dd>A free-form street address for the contact, formatted as a
+ single value (with embedded newlines where necessary) suitable for
+ printing on an address label</dd>
+
+ <dt>tel</dt>
+ <dd>A telephone number for the contact.</dd>
+
+ <dt>email</dt>
+ <dd>An email address for the contact.</dd>
+ </dl>
+
+ <p>For example, the following vCard:</p>
+
+ <pre>
+ BEGIN:vCard
+ VERSION:3.0
+ FN:Wee Ninja
+ N;LANGUAGE=ja:Ninja;Wee;;;-san
+ ORG:Collabora, Ltd.;Management Division;Human Resources\; Company Policy Enforcement
+ ADR;TYPE=WORK,POSTAL,PARCEL:;;11 Kings Parade;Cambridge;Cambridgeshire
+ ;CB2 1SJ;UK
+ LABEL;TYPE=WORK,POSTAL,PARCEL:11 Kings Parade\nCambridge\nCambridgeshire\nUK\nCB2 1SJ
+ TEL;TYPE=VOICE,WORK:+44 1223 362967, +44 7700 900753
+ EMAIL;TYPE=INTERNET,PREF:wee.ninja@collabora.co.uk
+ EMAIL;TYPE=INTERNET:wee.ninja@example.com
+ URL:http://www.thinkgeek.com/geektoys/plush/8823/
+ NICKNAME:HR Ninja,Enforcement Ninja
+ END:vCard</pre>
+
+ <p>would be represented by (in Python-like syntax):</p>
+
+ <pre>
+[
+ ('fn', [], ['Wee Ninja']),
+ ('n', ['language=ja'], ['Ninja', 'Wee', '', '', '-san']),
+ ('org', [], ['Collabora, Ltd.', 'Management Division',
+ 'Human Resources; Company Policy Enforcement']),
+ ('adr', ['type=work','type=postal','type=parcel'],
+ ['','','11 Kings Parade','Cambridge', 'Cambridgeshire','CB2 1SJ','UK']),
+ ('label', ['type=work','type=postal','type=parcel'],
+ ['''11 Kings Parade
+ Cambridge
+ Cambridgeshire
+ UK
+ CB2 1SJ''']),
+ ('tel', ['type=voice','type=work'], ['+44 1223 362967']),
+ ('tel', ['type=voice','type=work'], ['+44 7700 900753']),
+ ('email', ['type=internet','type=pref'], ['wee.ninja@collabora.co.uk']),
+ ('email', ['type=internet'], ['wee.ninja@example.com']),
+ ('url', [], ['http://www.thinkgeek.com/geektoys/plush/8823/']),
+ ('nickname', [], ['HR Ninja']),
+ ('nickname', [], ['Enforcement Ninja'])
+]</pre>
+ </tp:docstring>
+ </tp:struct>
+
+ <tp:mapping name="Contact_Info_Map" array-name="">
+ <tp:docstring>A dictionary whose keys are contact handles and whose
+ values are contact information..</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle"/>
+ <tp:member type="a(sasas)" tp:type="Contact_Info_Field[]"
+ name="Contact_Info"/>
+ </tp:mapping>
+
+ <signal name="ContactInfoChanged" tp:name-for-bindings="Contact_Info_Changed">
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ An integer handle for the contact whose info has changed.
+ </tp:docstring>
+ </arg>
+ <arg name="ContactInfo" type="a(sasas)" tp:type="Contact_Info_Field[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of fields representing information about this contact.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when a contact's information has changed or been received for
+ the first time on this connection.
+ </tp:docstring>
+ </signal>
+
+ <method name="GetContactInfo"
+ tp:name-for-bindings="Get_Contact_Info">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of handles representing contacts.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="ContactInfo" type="a{ua(sasas)}"
+ tp:type="Contact_Info_Map">
+ <tp:docstring>
+ A dictionary mapping contact handles to information, whose keys are
+ the subset of the requested list of handles for which information was
+ cached.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request information on several contacts at once. This SHOULD only
+ return cached information, omitting handles for which no information is
+ cached from the returned map.
+ </tp:docstring>
+ </method>
+
+ <method name="RefreshContactInfo"
+ tp:name-for-bindings="Refresh_Contact_Info">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ Integer handles for contacts.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Retrieve information for the given contact, requesting it from the
+ network if an up-to-date version is not cached locally. This method
+ SHOULD return immediately, emitting
+ <tp:member-ref>ContactInfoChanged</tp:member-ref> when the contacts'
+ updated contact information is returned.
+
+ <tp:rationale>
+ This method allows a client with cached contact information to
+ update its cache after a number of days.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestContactInfo"
+ tp:name-for-bindings="Request_Contact_Info">
+ <arg direction="in" name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ An integer handle for a contact.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Contact_Info" type="a(sasas)"
+ tp:type="Contact_Info_Field[]">
+ <tp:docstring>
+ Information about that contact.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Retrieve information for a contact, requesting it from the network if
+ it is not cached locally.
+
+ <tp:rationale>
+ This method is appropriate for an explicit user request to show
+ a contact's information; it allows a UI to wait for the contact
+ info to be returned.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The contact's information could not be retrieved.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="SetContactInfo" tp:name-for-bindings="Set_Contact_Info">
+ <tp:docstring>
+ Set new contact information for this connection, replacing existing
+ information. This method is only suppported if
+ <tp:member-ref>ContactInfoFlags</tp:member-ref> contains
+ <code>Can_Set</code>, and may only be passed fields conforming to
+ <tp:member-ref>SupportedFields</tp:member-ref>.
+ </tp:docstring>
+ <arg direction="in" name="ContactInfo" type="a(sasas)"
+ tp:type="Contact_Info_Field[]">
+ <tp:docstring>
+ The new information to be set.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ Setting your own information is not supported on this protocol.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The supplied fields do not match the restrictions specified by
+ <tp:member-ref>SupportedFields</tp:member-ref>.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:flags name="Contact_Info_Flags" value-prefix="Contact_Info_Flag"
+ type="u">
+ <tp:docstring>
+ Flags defining the behaviour of contact information on this protocol.
+ Some protocols provide no information on contacts without an explicit
+ request; others always push information to the connection manager as
+ and when it changes.
+ </tp:docstring>
+
+ <tp:flag suffix="Can_Set" value="1">
+ <tp:docstring>
+ Indicates that <tp:member-ref>SetContactInfo</tp:member-ref> is
+ supported on this connection.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Push" value="2">
+ <tp:docstring>
+ Indicates that the protocol pushes all contacts' information to the
+ connection manager without prompting. If set,
+ <tp:member-ref>ContactInfoChanged</tp:member-ref> will be emitted
+ whenever contacts' information changes.
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:simple-type name="VCard_Field" type="s">
+ <tp:docstring>
+ A string naming a field in a vCard, such as "fn" or "adr". Although
+ these are case-insensitive in RFC 2425, in Telepathy they MUST be
+ normalized to lower case. In the terminology of RFC 2425 this is
+ called a "type name", and corresponds to the "name" production given
+ in the ABNF.
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="VCard_Type_Parameter" type="s"
+ array-name="VCard_Type_Parameter_List">
+ <tp:docstring>
+ A type parameter as defined by RFC 2426, such as "type=cell" or
+ "language=en".
+ </tp:docstring>
+ </tp:simple-type>
+
+ <property name="ContactInfoFlags" type="u" access="read"
+ tp:type="Contact_Info_Flags" tp:name-for-bindings="Contact_Info_Flags">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An integer representing the bitwise-OR of flags on this
+ connection.</p>
+
+ <p>This property MAY change, without change notification, at any time
+ before the connection moves to status Connection_Status_Connected.
+ It MUST NOT change after that point.</p>
+
+ <tp:rationale>
+ <p>Some XMPP servers, like Facebook Chat, do not allow the vCard to
+ be changed (and so would not have the Can_Set flag). Whether the
+ user's server is one of these cannot necessarily be detected until
+ quite late in the connection process.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </property>
+
+ <tp:struct name="Field_Spec" array-name="Field_Specs">
+ <tp:docstring>A struct describing a vCard field, with parameters, that
+ may be passed to <tp:member-ref>SetContactInfo</tp:member-ref> on this
+ Connection.</tp:docstring>
+
+ <tp:member type="s" name="Name" tp:type="VCard_Field">
+ <tp:docstring>A vCard field name, such as 'tel'.</tp:docstring>
+ </tp:member>
+
+ <tp:member type="as" name="Parameters" tp:type="VCard_Type_Parameter[]">
+ <tp:docstring>The set of vCard type parameters which may be set on this
+ field. If this list is empty and the
+ Contact_Info_Field_Flag_Parameters_Exact flag is not set, any vCard type
+ parameters may be used.</tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" name="Flags" tp:type="Contact_Info_Field_Flags">
+ <tp:docstring>Flags describing the behaviour of this
+ field.</tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" name="Max">
+ <tp:docstring>Maximum number of instances of this field which may be
+ set. MAXUINT32 is used to indicate that there is no
+ limit.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="SupportedFields" type="a(sasuu)" tp:type="Field_Spec[]"
+ access="read" tp:name-for-bindings="Supported_Fields">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of field specifications describing the kinds of fields which may
+ be passed to <tp:member-ref>SetContactInfo</tp:member-ref>. The empty
+ list indicates that arbitrary vCard fields are permitted. This
+ property SHOULD be the empty list, and be ignored by clients, if
+ <tp:member-ref>ContactInfoFlags</tp:member-ref> does not contain the
+ Can_Set flag.</p>
+
+ <p>For example, a protocol in which arbitrary vCards were stored
+ as-is would set this property to the
+ empty list. A protocol whose notion of contact information is one
+ each of personal phone number, mobile phone number, location, email
+ address and date of birth, with no attributes allowed on each piece
+ of information, would set this property to (in Python-like
+ syntax):</p>
+
+ <pre>
+[
+ ('tel', ['type=home'], Parameters_Exact, 1),
+ ('tel', ['type=cell'], Parameters_Exact, 1),
+ ('adr', [], Parameters_Exact, 1),
+ ('bday', [], Parameters_Exact, 1),
+ ('email', ['type=internet'], Parameters_Exact, 1),
+]</pre>
+
+ <p>A protocol which allows users to specify up to four phone numbers,
+ which may be labelled as personal and/or mobile, would set this
+ property to
+ <code>[ ('tel', ['type=home', 'type=cell'], 0, 4), ]</code>.</p>
+
+ <tp:rationale>
+ <p>Studying existing IM protocols shows that in practice protocols
+ allow either a very restricted set of fields (such as MSN, which
+ seems to correspond roughly to the largest example above), or
+ something mapping 1:1 to a large subset of vCard (such as XMPP's
+ XEP-0054).</p>
+ </tp:rationale>
+
+ <p>This property MAY change, without change notification, at any time
+ before the connection moves to status Connection_Status_Connected.
+ It MUST NOT change after that point.</p>
+
+ <tp:rationale>
+ <p>Some XMPP servers, like Google Talk, only allow a small subset of
+ the "vcard-temp" protocol. Whether the user's server is one of
+ these cannot be detected until quite late in the connection
+ process.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <tp:flags name="Contact_Info_Field_Flags"
+ value-prefix="Contact_Info_Field_Flag" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Flags describing the behaviour of a vCard field.
+ </tp:docstring>
+ <tp:flag suffix="Parameters_Exact" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If present, exactly the parameters indicated must be set on this
+ field; in the case of an empty list of parameters, this implies that
+ parameters may not be used.</p>
+
+ <p>If absent, and the list of allowed parameters is non-empty,
+ any (possibly empty) subset of that list may be
+ used.</p>
+
+ <p>If absent, and the list of allowed parameters is empty,
+ any parameters may be used.</p>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:flag suffix="Overwritten_By_Nickname" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Indicates that this field will be overwritten when the user's alias
+ is changed with <tp:dbus-ref
+ namespace="ofdT.Connection.Interface.Aliasing">SetAliases</tp:dbus-ref>
+ or when the Account's <tp:dbus-ref
+ namespace="ofdT.Account">Nickname</tp:dbus-ref>
+ is updated. Clients that allow the editing of the Alias and the
+ ContactInfo in the same location should hide fields with this flag.</p>
+ <tp:rationale>
+ <p>If a client allowed the user to edit both the nickname and the
+ ContactInfo field at the same time, the user could set them to two
+ different values even though they map to the same property. This
+ would result in surprising behavior where the second value would
+ win over the first.</p>
+ </tp:rationale>
+ <p>In addition to hiding this field when editing ContactInfo together
+ with the user's nickname, it is recommended that clients call
+ <tp:member-ref>SetContactInfo</tp:member-ref> before setting the
+ user's nickname.</p>
+ <tp:rationale>
+ <p>This ensures that if the user changes the nickname, the correct
+ value will get set even if the stale nickname is mistakenly sent
+ along with <tp:member-ref>SetContactInfo</tp:member-ref>.</p>
+ </tp:rationale>
+ <p>If used, this flag typically appears on either the 'nickname' or
+ 'fn' field.</p>
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for requesting information about a contact on a given
+ connection. Information is represented as a list of
+ <tp:type>Contact_Info_Field</tp:type>s forming a
+ structured representation of a vCard (as defined by RFC 2426), using
+ field names and semantics defined therein.</p>
+
+ <p>On some protocols, information about your contacts is pushed to you,
+ with change notification; on others, like XMPP, the client must
+ explicitly request the avatar, and has no way to tell whether it has
+ changed without retrieving it in its entirety. This distinction is
+ exposed by <tp:member-ref>ContactInfoFlags</tp:member-ref> containing
+ the Push flag.</p>
+
+ <p>On protocols with the Push flag set, UIs can connect to
+ <tp:member-ref>ContactInfoChanged</tp:member-ref>, call
+ <tp:member-ref>GetContactInfo</tp:member-ref> once at login for the set
+ of contacts they are interested in, and then be sure they will receive
+ the latest contact info. On protocols like XMPP, clients can do the
+ same, but will receive (at most) opportunistic updates if the info is
+ retrieved for other reasons. Clients may call
+ <tp:member-ref>RequestContactInfo</tp:member-ref> or
+ <tp:member-ref>RefreshContactInfo</tp:member-ref> to force a contact's
+ info to be updated, but MUST NOT do so unless this is either in
+ response to direct user action, or to refresh their own cache after a
+ number of days.</p>
+
+ <tp:rationale>
+ <p>We don't want clients to accidentally cause a ridiculous amount of
+ network traffic.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:contact-attribute name="info"
+ type="a(sasas)" tp:type="Contact_Info_Field[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same value that would be returned by
+ <tp:member-ref>GetContactInfo</tp:member-ref> for this contact.
+ Omitted from the result if the contact's info
+ is not known.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Contact_List.xml b/qt4/spec/Connection_Interface_Contact_List.xml
new file mode 100644
index 000000000..033c64d1d
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Contact_List.xml
@@ -0,0 +1,1085 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contact_List" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ContactList">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.21.0">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for connections that have any concept of a list of
+ known contacts (roster, buddy list, friends list etc.)</p>
+
+ <tp:rationale>
+ <p>On many protocols, there's a server-side roster (as in XMPP),
+ or a set of server-side lists that can be combined to form a
+ roster (as in MSN).</p>
+
+ <p>In some protocols (like link-local XMPP), while there might not be
+ any server or roster, it's possible to list "nearby" contacts.</p>
+
+ <p>In Telepathy 0.20 and older, we represented contact lists as a
+ collection of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type"
+ >ContactList</tp:dbus-ref> channels. This is remarkably difficult to
+ work with in practice - every client that cares about contact lists
+ has to take the union of some hard-to-define set of these
+ channels - and conflicts with the idea that channels that cannot
+ be dispatched to a handler should be closed.</p>
+ </tp:rationale>
+
+ <p>The list of contacts is not exposed as a D-Bus property; it can be
+ fetched using <tp:member-ref>GetContactListAttributes</tp:member-ref>.
+ </p>
+
+ <tp:rationale>
+ <p>In some protocols, such as XMPP, the contact list may not be
+ available immediately. The
+ <tp:member-ref>GetContactListAttributes</tp:member-ref> method
+ will fail until the contact list is available.
+ Using a method also allows extra attributes to be retrieved at
+ the same time.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:enum name="Contact_List_State" type="u">
+ <tp:docstring>
+ The progress made in retrieving the contact list.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>The connection has not started to retrieve the contact
+ list. If <tp:member-ref>GetContactListAttributes</tp:member-ref> is
+ called in this state, it will raise NotYet.</tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Waiting" value="1">
+ <tp:docstring>The connection has started to retrieve the contact
+ list, but has not yet succeeded or failed.
+ If <tp:member-ref>GetContactListAttributes</tp:member-ref> is called
+ in this state, it will raise NotYet.</tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Failure" value="2">
+ <tp:docstring>
+ <p>The connection has tried and failed to retrieve the contact
+ list. If <tp:member-ref>GetContactListAttributes</tp:member-ref>
+ is called in this state, it will immediately raise an error
+ indicating the reason for failure.</p>
+
+ <p>The connection manager SHOULD try again to obtain the contact
+ list, if appropriate for the protocol. If it succeeds later,
+ the <tp:member-ref>ContactListState</tp:member-ref> MUST advance
+ to Success.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Success" value="3">
+ <tp:docstring>The connection has successfully retrieved the contact
+ list. If <tp:member-ref>GetContactListAttributes</tp:member-ref>
+ is called in this state, it will return successfully.</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="ContactListState" tp:name-for-bindings="Contact_List_State"
+ type="u" tp:type="Contact_List_State" access="read">
+ <tp:docstring>
+ The progress made in retrieving the contact list.
+ Change notification is via
+ <tp:member-ref>ContactListStateChanged</tp:member-ref>.
+ </tp:docstring>
+ </property>
+
+ <signal name="ContactListStateChanged"
+ tp:name-for-bindings="Contact_List_State_Changed">
+ <tp:docstring>
+ Emitted when <tp:member-ref>ContactListState</tp:member-ref>
+ changes.
+ </tp:docstring>
+
+ <arg name="Contact_List_State" type="u" tp:type="Contact_List_State">
+ <tp:docstring>
+ The new value of <tp:member-ref>ContactListState</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="GetContactListAttributes"
+ tp:name-for-bindings="Get_Contact_List_Attributes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Return some contact attributes for a list of contacts
+ associated with the user. This list MUST include at least:</p>
+
+ <ul>
+ <li>all contacts whose <tp:token-ref>subscribe</tp:token-ref>
+ attribute is not No</li>
+ <li>all contacts whose <tp:token-ref>publish</tp:token-ref>
+ attribute is not No</li>
+ </ul>
+
+ <p>but MAY contain other contacts.</p>
+
+ <tp:rationale>
+ <p>For instance, on XMPP, all contacts on the roster would appear
+ here even if they have subscription="none", unless there's
+ reason to believe the user does not want to see them (such as
+ having been blocked).</p>
+ </tp:rationale>
+
+ <p>This list does not need to contain every visible contact: for
+ instance, contacts seen in XMPP or IRC chatrooms SHOULD NOT appear
+ here. Blocked contacts SHOULD NOT appear here, unless they still
+ have a non-<tt>No</tt> <tp:token-ref>subscribe</tp:token-ref> or
+ <tp:token-ref>publish</tp:token-ref> attribute
+ for some reason.</p>
+
+ <tp:rationale>
+ <p>It's reasonable to assume that blocked contacts should not be
+ visible to the user unless they specifically go looking for them,
+ at least in protocols like XMPP where blocking a contact
+ suppresses presence.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Interfaces" type="as"
+ tp:type="DBus_Interface[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of strings indicating which D-Bus interfaces the calling
+ process is interested in. Equivalent to the corresponding argument
+ to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Contacts"
+ >GetContactAttributes</tp:dbus-ref>,
+ except that if this list does not contain the ContactList
+ interface itself, it is treated as though that interface was also
+ requested.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Hold" type="b">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, all handles that appear as keys in the result have been
+ held on behalf of the calling process, as if by a call to
+ <tp:dbus-ref namespace="ofdT">Connection.HoldHandles</tp:dbus-ref>.
+ (If <tp:dbus-ref namespace="ofdT.Connection"
+ >HasImmortalHandles</tp:dbus-ref> is true, which SHOULD be the
+ case in all new connection managers, this has no effect.)</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="a{ua{sv}}" name="Attributes"
+ tp:type="Contact_Attributes_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary mapping the contact handles to contact attributes,
+ equivalent to the result of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Contacts"
+ >GetContactAttributes</tp:dbus-ref>.</p>
+
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.ServiceBusy"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The <tp:member-ref>ContactListState</tp:member-ref> is
+ None or Waiting. In particular, this error is raised if the
+ <tp:dbus-ref namespace="ofdT.Connection">Status</tp:dbus-ref>
+ is not yet Connection_Status_Connected.</p>
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:enum name="Subscription_State" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An enumeration indicating whether presence subscription is denied,
+ denied but pending permission, or allowed. The exact semantics
+ vary according to where this type is used: see the
+ <tp:token-ref>subscribe</tp:token-ref> and
+ <tp:token-ref>publish</tp:token-ref> contact attributes for
+ details.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>The presence subscription state is
+ unknown.</tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="No" value="1">
+ <tp:docstring>Presence information cannot be seen, and either the
+ subscription state Removed_Remotely does not apply, or it is
+ not known whether that state applies.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Removed_Remotely" value="2">
+ <tp:docstring>Presence information cannot be seen because the
+ remote contact took action: either the local user's request to
+ see the remote contact's presence was denied, or the remote
+ contact requested to see the local user's presence but then
+ cancelled their request.</tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Ask" value="3">
+ <tp:docstring>Presence information cannot be seen. Permission
+ to see presence information has been requested, and the request
+ has not yet been declined or accepted.</tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Yes" value="4">
+ <tp:docstring>Presence information can be seen.</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:contact-attribute name="subscribe"
+ type="u" tp:type="Subscription_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If this attribute on a contact is Yes, this connection can
+ expect to receive their presence, along with any other information
+ that has the same access control.</p>
+
+ <tp:rationale>
+ <p>This is subscription="from" or subscription="both" in XMPP,
+ the "forward list" on MSN, or the contact being "added to
+ the local user's buddy list" in ICQ, for example.</p>
+ </tp:rationale>
+
+ <p>If this attribute is not Yes, the local user cannot generally
+ expect to receive presence from this contact. Their presence status
+ as returned by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.SimplePresence">GetPresences</tp:dbus-ref>
+ is likely to be (Unknown, "unknown", ""), unless the local user
+ can temporarily see their presence for some other reason (for
+ instance, on XMPP, contacts seen in chatrooms will temporarily
+ have available presence).</p>
+
+ <p>If this attribute is Ask, this indicates that the local user has
+ asked to receive the contact's presence at some time. It is
+ implementation-dependent whether contacts' subscribe attributes
+ can remain set to Ask, or are reset to No, when the connection
+ disconnects.</p>
+
+ <tp:rationale>
+ <p>Some protocols store the fact that we wishes to see a contact's
+ presence; on these protocols, this attribute can remain Ask
+ indefinitely. On other protocols, only contacts who have been
+ asked during the current session will ever have Ask status.</p>
+ </tp:rationale>
+
+ <p>If this attribute is Removed_Remotely, this indicates that the
+ local user has asked to receive the contact's presence at some time,
+ but the remote contact has rejected that request, and a local
+ user interface has not yet acknowledged this. It is
+ implementation-dependent whether contacts' subscribe attributes can
+ remain set to Removed_Remotely, or are reset to No, when the
+ connection disconnects.</p>
+
+ <p>After notifying the user, user interfaces MAY acknowledge a change
+ to <tt>subscribe</tt>=Removed_Remotely by calling either
+ <tp:member-ref>Unsubscribe</tp:member-ref> or
+ <tp:member-ref>RemoveContacts</tp:member-ref>, which will set
+ <tt>subscribe</tt> to No (and perhaps remove the contact). This
+ allows user interfaces to detect that the user has been notified
+ about the rejected request.</p>
+
+ <p>This attribute's value will be Unknown or omitted until the
+ <tp:member-ref>ContactListState</tp:member-ref> has changed to
+ Success.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:contact-attribute name="publish"
+ type="u" tp:type="Subscription_State">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If this attribute on a contact is Yes, the local user's presence
+ is published to that contact, along with any other information that
+ shares an access-control mechanism with presence (depending on
+ protocol, server configuration and/or user configuration, this may
+ include avatars, "rich presence" such as location, etc.).</p>
+
+ <tp:rationale>
+ <p>This is subscription="to" or subscription="both" in XMPP,
+ the "reverse list" on MSN, or the state of "being added to
+ the contact's buddy list" in ICQ, for example.</p>
+ </tp:rationale>
+
+ <p>If this attribute is not Yes, the
+ local user's presence is not published to that contact; however,
+ if it is Ask, the contact has requested that the local user's
+ presence is made available to them.</p>
+
+ <p>It is implementation-dependent whether contacts' <tt>publish</tt>
+ attributes can remain set to Ask, or are reset to No, when the
+ connection disconnects.</p>
+
+ <tp:rationale>
+ <p>Some protocols store the fact that a contact wishes to see our
+ presence; on these protocols, this attribute can remain Ask
+ indefinitely. On other protocols, only contacts who have asked
+ during the current session will ever have Ask status.</p>
+ </tp:rationale>
+
+ <p>If this attribute is Removed_Remotely, this indicates that the
+ remote contact has asked to receive the user's presence at some time,
+ but has then cancelled that request before a response was given by
+ the local user. User interfaces MAY reset <tt>publish</tt> from
+ Removed_Remotely to No, by calling either
+ <tp:member-ref>Unpublish</tp:member-ref> or
+ <tp:member-ref>RemoveContacts</tp:member-ref>.</p>
+
+ <p>If multiple factors affect whether a contact can receive the local
+ user's presence, this attribute SHOULD reflect the overall
+ result. For instance, an XMPP contact with subscription="to" or
+ subscription="both", but who has been blocked via
+ <a href="http://xmpp.org/extensions/xep-0016.html">XEP-0016 Privacy
+ Lists</a>, SHOULD have publish=No.</p>
+
+ <p>This attribute's value will be Unknown or omitted until the
+ <tp:member-ref>ContactListState</tp:member-ref> has changed to
+ Success.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:contact-attribute name="publish-request" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If the <tp:token-ref>publish</tp:token-ref> attribute is Ask, an
+ optional message that was sent by the contact asking to receive the
+ local user's presence; omitted if none was given.</p>
+
+ <tp:rationale>
+ <p>If the contact asking to receive our presence is also using
+ Telepathy, this is the message they supplied as the Message
+ argument to <tp:member-ref>RequestSubscription</tp:member-ref>.</p>
+ </tp:rationale>
+
+ <p>Otherwise, this SHOULD be omitted.</p>
+
+ <p>This attribute will also be omitted until the
+ <tp:member-ref>ContactListState</tp:member-ref> has changed to
+ Success.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <property name="ContactListPersists"
+ tp:name-for-bindings="Contact_List_Persists" type="b" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, presence subscriptions (in both directions) on this
+ connection are stored by the server or other infrastructure.</p>
+
+ <tp:rationale>
+ <p>XMPP, MSN, ICQ, etc. all behave like this.</p>
+ </tp:rationale>
+
+ <p>If false, presence subscriptions on this connection are not
+ stored.</p>
+
+ <tp:rationale>
+ <p>In SIMPLE (SIP), <em>clients</em> are expected to keep a record
+ of subscriptions, as described below. In link-local XMPP,
+ subscriptions are implicit (everyone on the local network receives
+ presence from everyone else) so nothing is ever stored.</p>
+ </tp:rationale>
+
+ <p>If <tp:member-ref>CanChangeContactList</tp:member-ref>
+ is true, Telepathy clients (e.g. user interfaces or address books)
+ MAY keep a record of permission to publish and requests to subscribe
+ locally, and attempt to restore it for each Connection. If
+ ContactListPersists is false, clients MAY do this for all contacts;
+ if ContactListPersists is true, clients SHOULD NOT change the state
+ of contacts that were not changed locally.</p>
+
+ <tp:rationale>
+ <p>In SIMPLE (SIP), ContactListPersists is false, but
+ CanChangeContactList is true. Presence will not be received
+ unless clients renew any subscriptions they have for each
+ connection, in the way described. There is no server-side storage,
+ so clients have no alternative but to maintain independent contact
+ lists.</p>
+
+ <p>In protocols like XMPP and MSN, it may be useful for clients to
+ queue up subscription requests or removals made while offline and
+ process them next time the connection is online. However, clients
+ should only replay the changes, rather than resetting the contact
+ list to match a stored copy, to avoid overwriting changes that
+ were made on the server.</p>
+ </tp:rationale>
+
+ <p>Clients that replay requests like this SHOULD do so by calling
+ AuthorizePublication to pre-approve publication of presence to the
+ appropriate contacts, followed by RequestSubscription to request the
+ appropriate contacts' presences.</p>
+
+ <p>This property cannot change after the connection has moved to the
+ Connected state. Until then, its value is undefined, and it may
+ change at any time, without notification.</p>
+ </tp:docstring>
+ </property>
+
+ <tp:enum name="Contact_Metadata_Storage_Type" type="u">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Values of this enumeration indicate the extent to which metadata
+ such as aliases and group memberships can be stored for the contacts
+ on a particular connection.</p>
+
+ <p>On some protocols, certain metadata (for instance, contact aliases)
+ can only be stored for contacts on the contact list, or contacts
+ with a particular contact list state.</p>
+
+ <p>To make it easier to deal with such protocols, if clients set
+ metadata on a contact who is not in the required state, the
+ Connection MUST cache the metadata for the duration of the session.
+ If clients request the attributes of that contact after the
+ appropriate "set" method has returned successfully, the Connection
+ MUST return the new (cached) value.</p>
+
+ <p>If the contact is later placed in the required state to store
+ metadata (for instance, if subscription to the contact's presence
+ is requested, on a protocol like MSN where the alias has storage type
+ Subscribed_Or_Pending), the connection MUST store the cached
+ metadata at that time.</p>
+
+ <tp:rationale>
+ <p>If the Connection didn't cache changes in this way, a client
+ intending to change the alias on MSN would have to wait until
+ the server acknowledged the subscription request; in the meantime,
+ other clients would still display the old alias.</p>
+ </tp:rationale>
+
+ <p>The only exception to that general rule is that if the Connection
+ cannot store particular metadata at all (i.e. the
+ storage type is None), it MUST reject attempts to set it.</p>
+
+ <tp:rationale>
+ <p>If the implementation knows that metadata can't be stored at
+ all, it's useful to report that, which can be done
+ synchronously. In general, user interfaces should detect
+ storage type None and not display editing controls at all.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This connection cannot store this type of metadata at all, and
+ attempting to do so will fail with NotImplemented.</p>
+
+ <tp:rationale>
+ <p>Link-local XMPP can't store aliases or group memberships at
+ all, and subscription and presence states are implicit (all
+ contacts on the local network have subscribe = publish = Yes
+ and no other contacts exist).</p>
+
+ <p>As of April 2010, the XMPP server for Facebook Chat provides a
+ read-only view of the user's Facebook contacts, so it could also
+ usefully have this storage type.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Subscribed_Or_Pending" value="1">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This type of metadata can only be stored permanently for contacts
+ whose subscribe attribute is Ask or Yes.</p>
+
+ <tp:rationale>
+ <p>Contact aliases and groups on MSN have this behaviour.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Subscribed" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This type of metadata can only be stored permanently for contacts
+ whose subscribe attribute is Yes.</p>
+
+ <tp:rationale>
+ <p>No service with this behaviour is currently known, but it's a
+ stricter form of Subscribed_Or_Pending.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue suffix="Anyone" value="3">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The user can set this metadata for any valid contact identifier,
+ whether or not they have any presence subscription relationship
+ to it, and it will be stored on their contact list.</p>
+
+ <tp:rationale>
+ <p>Contact aliases and groups on XMPP have this behaviour; it
+ is possible to put a contact in a group, or assign an alias
+ to them, without requesting that presence be shared.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Contact_Subscriptions" array-name="">
+ <tp:docstring>
+ A single contact's subscribe, publish and publish-request attributes.
+ </tp:docstring>
+
+ <tp:member name="Subscribe" type="u" tp:type="Subscription_State">
+ <tp:docstring>
+ The new value of the contact's "subscribe" attribute.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Publish" type="u" tp:type="Subscription_State">
+ <tp:docstring>
+ The new value of the contact's "publish" attribute.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Publish_Request" type="s">
+ <tp:docstring>
+ The new value of the contact's "publish-request" attribute,
+ or the empty string if that attribute would be omitted.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Contact_Subscription_Map" array-name="">
+ <tp:docstring>
+ A map from contacts to their subscribe, publish and publish-request
+ attributes.
+ </tp:docstring>
+
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact's handle.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="States" type="(uus)" tp:type="Contact_Subscriptions">
+ <tp:docstring>
+ The contact's subscribe, publish and publish-request attributes.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <signal name="ContactsChangedWithID"
+ tp:name-for-bindings="Contacts_Changed_With_ID">
+ <tp:added version="0.21.8"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the contact list becomes available, when contacts'
+ basic stored properties change, when new contacts are added to the
+ list that would be returned by
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>,
+ or when contacts are removed from that list.</p>
+
+ <tp:rationale>
+ <p>This provides change notification for that list, and for
+ contacts' <tp:token-ref>subscribe</tp:token-ref>,
+ <tp:token-ref>publish</tp:token-ref> and
+ <tp:token-ref>publish-request</tp:token-ref> attributes.</p>
+ </tp:rationale>
+
+ <p>Connection managers SHOULD also emit this signal when a contact
+ requests that the user's presence is published to them, even if
+ that contact's <tp:token>publish</tp:token> attribute is already
+ Ask and the <tp:token>publish-request</tp:token> has not changed.</p>
+
+ <tp:rationale>
+ <p>If the same contact sends 10 identical requests, 10 identical
+ signals should be emitted.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg type="a{u(uus)}" name="Changes" tp:type="Contact_Subscription_Map">
+ <tp:docstring>
+ The new <tp:token-ref>subscribe</tp:token-ref>,
+ <tp:token-ref>publish</tp:token-ref> and
+ <tp:token-ref>publish-request</tp:token-ref> attributes of all the
+ contacts that have been added, and all the contacts for which those
+ attributes have changed.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Identifiers" type="a{us}" tp:type="Handle_Identifier_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The identifiers of the contacts in the <var>Changes</var> map.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Removals" type="a{us}" tp:type="Handle_Identifier_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The contacts that have been removed from the list that would be
+ returned by
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.
+ This also implies that they have subscribe = No and publish = No;
+ contacts MUST NOT be listed both here and in <var>Changes</var>.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <signal name="ContactsChanged"
+ tp:name-for-bindings="Contacts_Changed">
+ <tp:deprecated version="0.21.8">Connection managers MUST still
+ emit this signal, but clients SHOULD listen for the
+ <tp:member-ref>ContactsChangedWithID</tp:member-ref> signal in
+ addition, and ignore this signal after ContactsChangedWithID has been
+ emitted at least once.
+ </tp:deprecated>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted immediately after
+ <tp:member-ref>ContactsChangedWithID</tp:member-ref>, under the same
+ circumstances.</p>
+
+ <p>If clients receive this signal without first receiving a
+ corresponding <tp:member-ref>ContactsChangedWithID</tp:member-ref>,
+ they MUST assume that only this signal will be emitted.</p>
+ </tp:docstring>
+
+ <arg type="a{u(uus)}" name="Changes" tp:type="Contact_Subscription_Map">
+ <tp:docstring>
+ The same as the corresponding argument to
+ <tp:member-ref>ContactsChangedWithID</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Removals" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The same as the corresponding argument to
+ <tp:member-ref>ContactsChangedWithID</tp:member-ref>, except that it
+ only includes handles and not identifiers.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="CanChangeContactList" type="b" access="read"
+ tp:name-for-bindings="Can_Change_Contact_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, presence subscription and publication can be changed
+ using the
+ <tp:member-ref>RequestSubscription</tp:member-ref>,
+ <tp:member-ref>AuthorizePublication</tp:member-ref> and
+ <tp:member-ref>RemoveContacts</tp:member-ref> methods.</p>
+
+ <p>If false, all of those methods will always fail; they SHOULD raise
+ the error org.freedesktop.Telepathy.Error.NotImplemented.</p>
+
+ <tp:rationale>
+ <p>In XEP-0174 "Serverless Messaging" (link-local XMPP), presence is
+ implicitly published to everyone in the local subnet, so the user
+ cannot control their presence publication.</p>
+ </tp:rationale>
+
+ <p>This property cannot change after the connection has moved to the
+ Connected state. Until then, its value is undefined, and it may
+ change at any time, without notification.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="RequestSubscription" tp:name-for-bindings="Request_Subscription">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the given contacts allow the local user to
+ subscribe to their presence, i.e. that their subscribe attribute
+ becomes Yes.</p>
+
+ <p>Connection managers SHOULD NOT attempt to enforce a
+ mutual-subscription policy (i.e. when this method is called, they
+ should not automatically allow the contacts to see the local user's
+ presence). User interfaces that require mutual subscription
+ MAY call <tp:member-ref>AuthorizePublication</tp:member-ref>
+ at the same time as this method.</p>
+
+ <tp:rationale>
+ <p>Whether to enforce mutual subscription is a matter of policy,
+ so it is left to the user interface and/or the server.</p>
+ </tp:rationale>
+
+ <p>Before calling this method on a connection where <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Aliasing"
+ >GetAliasFlags</tp:dbus-ref> returns the <code>User_Set</code> flag,
+ user interfaces SHOULD obtain, from the user, an alias to
+ identify the contact in future, and store it using <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Aliasing"
+ >SetAliases</tp:dbus-ref>.</p>
+
+ <p>The user MAY be
+ prompted using the contact's current self-assigned nickname, or
+ something derived from the contact's (presumably self-assigned)
+ identifier, as a default, but these names chosen by the contact
+ SHOULD NOT be used without user approval.</p>
+
+ <tp:rationale>
+ <p>This is a generalization of
+ <a href="http://xmpp.org/extensions/xep-0165.html"
+ >XEP-0165 "Best Practices to Discourage JID Mimicking"</a>)
+ to protocols other than XMPP. A reasonable user interface for
+ this, as used in many XMPP clients, is to have a text entry
+ for the alias adjacent to the text entry for the identifier
+ to add.</p>
+ </tp:rationale>
+
+ <p>For contacts with subscribe=Yes, this method has no effect.
+ It MUST return successfully if all contacts are in this state.</p>
+
+ <p>For contacts with subscribe=Ask, this method SHOULD send a new
+ request, with the given message, if allowed by the underlying
+ protocol.</p>
+
+ <p>For contacts with subscribe=No or subscribe=Rejected, this method
+ SHOULD request that the contact allows the local user to subscribe
+ to their presence; in general, this will change their publish
+ attribute to Ask (although it could change directly to Yes in some
+ situations).</p>
+
+ <p>Any state changes that immediately result from this request MUST
+ be signalled via <tp:member-ref>ContactsChanged</tp:member-ref>
+ before this method returns.</p>
+
+ <tp:rationale>
+ <p>This makes it easy for user interfaces to see what practical
+ effect this method had.</p>
+ </tp:rationale>
+
+ <p>If the remote contact accepts the request, their subscribe
+ attribute will later change from Ask to Yes.</p>
+
+ <p>If the remote contact explicitly rejects the request (in protocols
+ that allow this), their subscribe attribute will later change from
+ Ask to Rejected.</p>
+
+ <p>If the subscription request is cancelled by the local user, the
+ contact's subscribe attribute will change from Ask to No.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:member-ref>ContactListState</tp:member-ref> changes to Success.
+ If the <tp:member-ref>ContactListState</tp:member-ref> changes to
+ Failure, this method SHOULD raise the same error as
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" direction="in"
+ type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>One or more contacts to whom requests are to be sent.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Message" type="s" direction="in">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An optional plain-text message from the user, to send to those
+ contacts with the subscription request. The
+ <tp:member-ref>RequestUsesMessage</tp:member-ref> property
+ indicates whether this message will be used or ignored.</p>
+
+ <p>Clients SHOULD NOT send a non-empty message without first giving
+ the user an opportunity to edit it.</p>
+
+ <tp:rationale>
+ <p>These messages are typically presented to the remote contact
+ as if the user had typed them, so as a minimum, the user should be
+ allowed to see what the UI will be saying on their behalf.</p>
+ </tp:rationale>
+
+ <p>Connections where this message is not useful MUST still allow it to
+ be non-empty.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet">
+ <tp:docstring>
+ The <tp:member-ref>ContactListState</tp:member-ref> is None
+ or Waiting.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ It was not possible to perform the requested action, because
+ <tp:member-ref>CanChangeContactList</tp:member-ref> is false.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <property name="RequestUsesMessage" type="b" access="read"
+ tp:name-for-bindings="Request_Uses_Message">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, the Message parameter to
+ <tp:member-ref>RequestSubscription</tp:member-ref> is likely to be
+ significant, and user interfaces SHOULD prompt the user for a
+ message to send with the request; a message such as "I would like
+ to add you to my contact list", translated into the local user's
+ language, might make a suitable default.</p>
+
+ <tp:rationale>
+ <p>This matches user expectations in XMPP and ICQ, for instance.</p>
+ </tp:rationale>
+
+ <p>If false, the parameter is ignored; user interfaces SHOULD avoid
+ prompting the user, and SHOULD pass an empty string to
+ RequestSubscription.</p>
+
+ <tp:rationale>
+ <p><em>FIXME: is there any such protocol?</em></p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <method name="AuthorizePublication"
+ tp:name-for-bindings="Authorize_Publication">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>For each of the given contacts, request that the local user's
+ presence is sent to that contact, i.e. that their publish attribute
+ becomes Yes.</p>
+
+ <p>Connection managers SHOULD NOT attempt to enforce a
+ mutual-subscription policy (i.e. when this method is called, they
+ should not automatically request that the contacts allow the user to
+ subscribe to their presence). User interfaces that require mutual
+ subscription MAY call
+ <tp:member-ref>RequestSubscription</tp:member-ref> at the same time
+ as this method.</p>
+
+ <tp:rationale>
+ <p>Whether to enforce mutual subscription is a matter of policy,
+ so it is left to the user interface and/or the server.</p>
+ </tp:rationale>
+
+ <p>For contacts with publish=Yes, this method has no effect; it
+ MUST return successfully if all contacts given have this state.</p>
+
+ <p>For contacts with publish=Ask, this method accepts the
+ contact's request to see the local user's presence, changing
+ their publish attribute from Ask to Yes.</p>
+
+ <p>For contacts with publish=No, if the protocol allows it, this
+ method allows the contacts to see the local user's presence even
+ though they have not requested it, changing their publish attribute
+ from No to Yes. Otherwise, it merely records the fact that
+ presence publication to those contacts is allowed; if any of
+ those contacts ask to receive the local user's presence
+ later in the lifetime of the connection, the connection SHOULD
+ immediately allow them to do so, changing their publish
+ attribute directly from No to Yes.</p>
+
+ <tp:rationale>
+ <p>This makes it easy to implement the common UI policy that if
+ the user attempts to subscribe to a contact's presence, requests
+ for reciprocal subscription are automatically approved.</p>
+ </tp:rationale>
+
+ <p>Any state changes that immediately result from this request MUST
+ be signalled via <tp:member-ref>ContactsChanged</tp:member-ref>
+ before this method returns.</p>
+
+ <tp:rationale>
+ <p>This makes it easy for user interfaces to see what practical
+ effect this method had.</p>
+ </tp:rationale>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:member-ref>ContactListState</tp:member-ref> changes to Success.
+ If the <tp:member-ref>ContactListState</tp:member-ref> changes to
+ Failure, this method SHOULD raise the same error as
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" direction="in"
+ type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>One or more contacts to authorize.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ It was not possible to perform the requested action, because
+ <tp:member-ref>CanChangeContactList</tp:member-ref> is false.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet">
+ <tp:docstring>
+ The <tp:member-ref>ContactListState</tp:member-ref> is None
+ or Waiting.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RemoveContacts" tp:name-for-bindings="Remove_Contacts">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Remove the given contacts from the contact list entirely. It is
+ protocol-dependent whether this works, and under which
+ circumstances.</p>
+
+ <p>If possible, this method SHOULD set the contacts' subscribe and
+ publish attributes to No, remove any stored aliases for those
+ contacts, and remove the contacts from the result of
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.</p>
+
+ <p>This method SHOULD succeed even if it was not possible to carry out
+ the request entirely or for all contacts (for instance, if there is an
+ outstanding request to subscribe to the contact's presence, and it's
+ not possible to cancel such requests). However, all signals that
+ immediately result from this method call MUST be emitted before it
+ returns, so that clients can interpret the result.</p>
+
+ <tp:rationale>
+ <p>User interfaces removing a contact from the contact list are
+ unlikely to want spurious failure notifications resulting from
+ limitations of a particular protocol. However, emitting the
+ signals first means that if a client does want to check exactly
+ what happened, it can wait for the method to return (while
+ applying change-notification signals to its local cache of the
+ contact list's state), then consult its local cache of the
+ contact list's state to see whether the contact is still there.</p>
+ </tp:rationale>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:member-ref>ContactListState</tp:member-ref> changes to Success.
+ If the <tp:member-ref>ContactListState</tp:member-ref> changes to
+ Failure, this method SHOULD raise the same error as
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" direction="in"
+ type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>One or more contacts to remove.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ It was not possible to perform the requested action because
+ <tp:member-ref>CanChangeContactList</tp:member-ref> is false.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet">
+ <tp:docstring>
+ The <tp:member-ref>ContactListState</tp:member-ref> is None
+ or Waiting.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Unsubscribe" tp:name-for-bindings="Unsubscribe">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Attempt to set the given contacts' subscribe attribute to No,
+ i.e. stop receiving their presence.</p>
+
+ <p>For contacts with subscribe=Ask, this attempts to cancel
+ an earlier request to subscribe to the contact's presence; for
+ contacts with subscribe=Yes, this attempts to
+ unsubscribe from the contact's presence.</p>
+
+ <p>As with <tp:member-ref>RemoveContacts</tp:member-ref>, this method
+ SHOULD succeed even if it was not possible to carry out the request
+ entirely or for all contacts; however, all signals that
+ immediately result from this method call MUST be emitted before it
+ returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:member-ref>ContactListState</tp:member-ref> changes to Success.
+ If the <tp:member-ref>ContactListState</tp:member-ref> changes to
+ Failure, this method SHOULD raise the same error as
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" direction="in"
+ type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>One or more contacts to remove.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ It was not possible to perform the requested action because
+ <tp:member-ref>CanChangeContactList</tp:member-ref> is false.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="Unpublish" tp:name-for-bindings="Unpublish">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Attempt to set the given contacts' publish attribute to No,
+ i.e. stop sending presence to them.</p>
+
+ <p>For contacts with publish=Ask, this method explicitly rejects the
+ contact's request to subscribe to the user's presence; for
+ contacts with publish=Yes, this method attempts to prevent the
+ user's presence from being received by the contact.</p>
+
+ <p>As with <tp:member-ref>RemoveContacts</tp:member-ref>, this method
+ SHOULD succeed even if it was not possible to carry out the request
+ entirely or for all contacts; however, all signals that
+ immediately result from this method call MUST be emitted before it
+ returns.</p>
+
+ <p>This method SHOULD NOT be called until the
+ <tp:member-ref>ContactListState</tp:member-ref> changes to Success.
+ If the <tp:member-ref>ContactListState</tp:member-ref> changes to
+ Failure, this method SHOULD raise the same error as
+ <tp:member-ref>GetContactListAttributes</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <arg name="Contacts" direction="in"
+ type="au" tp:type="Contact_Handle[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>One or more contacts to remove.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ It was not possible to perform the requested action because
+ <tp:member-ref>CanChangeContactList</tp:member-ref> is false.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotYet">
+ <tp:docstring>
+ The <tp:member-ref>ContactListState</tp:member-ref> is None
+ or Waiting.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Contacts.xml b/qt4/spec/Connection_Interface_Contacts.xml
new file mode 100644
index 000000000..1020190d4
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Contacts.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contacts" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005-2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or (at
+ your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public License
+ along with this library; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Contacts">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.17.9"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface allows many attributes of many contacts to be
+ obtained in a single D-Bus round trip.</p>
+
+ <p>Each contact attribute has an string identifier
+ (<tp:type>Contact_Attribute</tp:type>), which is namespaced
+ by the D-Bus interface which defines it.</p>
+ </tp:docstring>
+
+ <tp:simple-type name="Contact_Attribute" type="s">
+ <tp:docstring>
+ A <tp:type>DBus_Interface</tp:type>, followed by a slash '/' character
+ and an identifier for an attribute defined by that interface. The
+ attribute identifier SHOULD be in lower case.
+
+ <tp:rationale>
+ These aren't D-Bus core Properties, and we want them to look visibly
+ different.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:mapping name="Single_Contact_Attributes_Map">
+ <tp:docstring>
+ Some of the attributes of a single contact.
+ </tp:docstring>
+
+ <tp:member type="s" tp:type="Contact_Attribute" name="Attribute">
+ <tp:docstring>
+ The name of the attribute
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="v" name="Value">
+ <tp:docstring>
+ The value of the attribute
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:mapping name="Contact_Attributes_Map">
+ <tp:docstring>Mapping returned by
+ <tp:member-ref>GetContactAttributes</tp:member-ref>, representing a
+ collection of Contacts and their requested attributes.</tp:docstring>
+
+ <tp:member type="u" tp:type="Contact_Handle" name="Contact">
+ <tp:docstring>
+ A contact
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="a{sv}" tp:type="Single_Contact_Attributes_Map"
+ name="Attributes">
+ <tp:docstring>
+ Attributes of that contact
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="ContactAttributeInterfaces" access="read" type="as"
+ tp:type="DBus_Interface[]"
+ tp:name-for-bindings="Contact_Attribute_Interfaces">
+ <tp:docstring>
+ A list of D-Bus interfaces for which
+ <tp:member-ref>GetContactAttributes</tp:member-ref> is expected to work.
+ This cannot change during the lifetime of the Connection.
+ </tp:docstring>
+ </property>
+
+ <method name="GetContactAttributes"
+ tp:name-for-bindings="Get_Contact_Attributes">
+ <tp:docstring>
+ Return any number of contact attributes for the given handles.
+ </tp:docstring>
+
+ <arg direction="in" name="Handles" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of handles representing contacts.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Interfaces" type="as"
+ tp:type="DBus_Interface[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of strings indicating which D-Bus interfaces the calling
+ process is interested in. All supported attributes from these
+ interfaces, whose values can be obtained without additional network
+ activity, will be in the reply.</p>
+
+ <p>Connection managers SHOULD ignore interfaces requested which they
+ do not support (i.e. those not mentioned in the
+ <tp:member-ref>ContactAttributeInterfaces</tp:member-ref>
+ property.)</p>
+
+ <tp:rationale>
+ <p>This simplifies client-side code. Clients which care may
+ distinguish between unsupported interfaces (e.g. this Connection
+ does not support Avatars), and interfaces on which no information
+ is known for these contacts (e.g. we don't know the avatar tokens
+ of any of the contacts, so we omitted them all) by inspecting
+ <tp:member-ref>ContactAttributeInterfaces</tp:member-ref>.</p>
+ </tp:rationale>
+
+ <p>Attributes from the interface
+ <tp:dbus-ref>org.freedesktop.Telepathy.Connection</tp:dbus-ref>
+ are always returned, and need not be requested explicitly.</p>
+
+ <p>As well as returning cached information immediately, the
+ connection MAY start asynchronous requests to obtain better
+ values for the contact attributes. If better values are later
+ obtained by this process, they will be indicated with the usual
+ signals (such as <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Aliasing">AliasesChanged</tp:dbus-ref>).</p>
+
+ <tp:rationale>
+ For instance, an XMPP connection manager could download vCards
+ in response to a request for <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Aliasing</tp:dbus-ref>
+ attributes.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:changed version="0.19.2">
+ requesting information for interfaces not mentioned in
+ <tp:member-ref>ContactAttributeInterfaces</tp:member-ref> is no
+ longer an error. Be aware that older connection managers may still
+ consider this an error.
+ </tp:changed>
+ </arg>
+
+ <arg direction="in" name="Hold" type="b">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If true, all handles that appear as keys in the result have been
+ held on behalf of the calling process, as if by a call to
+ <tp:dbus-ref namespace="ofdT">Connection.HoldHandles</tp:dbus-ref>.
+ (If <tp:dbus-ref namespace="ofdT.Connection"
+ >HasImmortalHandles</tp:dbus-ref> is true, which SHOULD be the
+ case in all new connection managers, this has no effect.)</p>
+
+ <tp:rationale>
+ <p>For further round-trip avoidance.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" type="a{ua{sv}}" name="Attributes"
+ tp:type="Contact_Attributes_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary mapping the contact handles to contact attributes.
+ If any of the requested handles are in fact invalid, they are
+ simply omitted from this mapping. If contact attributes are not
+ immediately known, the behaviour is defined by the interface;
+ the attribute should either be omitted from the result or
+ replaced with a default value.</p>
+
+ <p>Each contact's attributes will always include at least the
+ identifier that would be obtained by inspecting the handle
+ (<code>org.freedesktop.Telepathy.Connection/contact-id</code>).</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ </tp:possible-errors>
+ </method>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Forwarding.xml b/qt4/spec/Connection_Interface_Forwarding.xml
new file mode 100644
index 000000000..e5457b1ea
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Forwarding.xml
@@ -0,0 +1,346 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Forwarding"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2005-2010 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright © 2005-2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Forwarding.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.6">(draft version, not API-stable)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This connection interface is for protocols that are capable of
+ signaling to remote contacts that incoming communication channels
+ should be instead sent to a separate contact. This might apply to
+ things such as call forwarding, for example.</p>
+
+ <p>In some cases, a CM may register forwarding rules with an external
+ service; in those cases, it will never see the incoming channel, and
+ the forwarding will happen automatically.</p>
+
+ <p>In other cases, the CM will handle the forwarding itself. When an
+ incoming channel is detected, the status of the local user will
+ determine whether or not a forwarding rule is matched. For some
+ rules, this MAY happen immediately (ie, if the user is Busy); for
+ others, there MAY be a timeout (in seconds) that must expire
+ before the forwarding rule is matched (the timeout is specified
+ by the first element in the <tp:type>Forwarding_Rule_Entry</tp:type> list).</p>
+
+ <p>Once a forwarding rule is matched and any necessary timeouts have
+ expired, the CM can forward the incoming channel to the specified
+ handle. If for whatever reason the remote handle does not accept
+ the channel AND the CM supports multiple forwarding entries AND
+ any necessary timeouts have expired (specified by the next entry
+ in the list), the CM can forward the incoming channel to the next
+ handle in the entry list. This continues until the list is
+ exhausted, or the incoming channel is accepted.</p>
+
+ <p>Note that the rule matches are only for the first entry in the
+ in the forwarding rule list. Once the incoming channel has been
+ forwarded, the next entry in the list (assuming one exists and
+ the contact that the channel has been forwarded to does not respond
+ after any necessary timeouts) is used regardless of the status of
+ the forwarded channel. The initial match rule might have been
+ Busy, whereas the contact that the channel has been forwarded to
+ might be offline. Even in this case, the Busy list is still
+ traversed until the channel is handled (or there are no more
+ forwarding entries in the list).</p>
+
+ <p>For example, assuming the following dict for Forwarding_Rules:</p>
+ <pre>
+ ForwardingRules = {
+ Busy: ( initial-timeout: 30, [
+ (handle: 3, timeout: 15),
+ (handle: 5, timeout: 20)
+ ]),
+ NoReply: ( initial-timeout: 15, [
+ (handle: 5, timeout: 30),
+ (handle: 3, timeout: 20)
+ ])
+ }</pre>
+
+ <p>We can imagine a scenario where an incoming channel is detected,
+ the media stream is available (ie, not Busy),
+ and the local user is online. While the CM is waiting for the local user to
+ accept the channel, it looks at NoReply's first timeout value. After 15s if
+ the local user hasn't accepted, the CM forwards the channel to Handle #5. The
+ CM then waits 30s for Handle #5 to accept the channel. If after 30s it does
+ not, the CM forwards the incoming channel to Handle #3, which will have
+ 20s to accept the channel.</p>
+
+ <p>When an unanswered <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>StreamedMedia</tp:dbus-ref> call is
+ forwarded, both the contact and the self handle should be removed from
+ the group with the self handle as the actor, and
+ <tp:type>Channel_Group_Change_Reason</tp:type> <code>No_Answer</code> or
+ <code>Busy</code>, as appropriate. For <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>Call.DRAFT</tp:dbus-ref> channels, the
+ <tp:type>Call_State_Change_Reason</tp:type> <code>Forwarded</code>
+ should be used.</p>
+ </tp:docstring>
+
+ <tp:enum name="Forwarding_Condition" type="u">
+ <tp:docstring>
+ The various forwarding conditions that are supported by this interface.
+ In general, the conditions should not overlap; it should be very clear
+ which rule would be chosen given a CM's behavior with an incoming
+ channel. The exception to this is Unconditional,
+ which will override all other rules.
+ </tp:docstring>
+
+ <tp:enumvalue value="0" suffix="Unconditional">
+ <tp:docstring>
+ Incoming channels should always be forwarded. Note that setting this
+ will override any other rules. If not set, other rules will
+ be checked when an incoming communication channel is detected.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="1" suffix="Busy">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The incoming channel should be forwarded if a busy signal is
+ detected. What defines "Busy" is CM-specific (perhaps a single
+ resource is already in use, or a user's status is set to Busy
+ <tp:type>Connection_Presence_Type</tp:type>).</p>
+
+ <p>If initial timeout is specified for Busy condition and call
+ waiting is not supported by the service, the timeout will be
+ ignored.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="2" suffix="No_Reply">
+ <tp:docstring>
+ The incoming channel should be forwarded if the local user doesn't
+ accept it within the specified amount of time.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="3" suffix="Not_Reachable">
+ <tp:docstring>
+ The incoming channel should be forwarded if the user is offline.
+ This could be a manual setting (the user has chosen to set their
+ presence to offline or invisible) or something specified by the
+ underlying network (the user is not within range of a cell tower).
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Forwarding_Rule_Entry"
+ array-name="Forwarding_Rule_Entry_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A forwarding rule entry. These MAY be chained together
+ for CMs that support chaining of forwards (in other words,
+ a forwarding rule may have multiple entries; if the contact
+ in the first entry doesn't respond, the incoming channel
+ might be forwarded to the contact in the second entry).</p>
+
+ <p>For CMs and protocols that don't support chaining of
+ entries, only the first entry would be used.</p>
+ </tp:docstring>
+
+ <tp:member type="u" name="Timeout">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The length of time (in seconds) to wait the contact to respond
+ to the forwarded channel. This MAY be ignored by the CM if it
+ isn't supported by the underlying network/protocol for the
+ specific status of the remote contact (for example, a GSM call
+ that is forwarded may return Not_Reachable immediately without
+ waiting for the timeout value to expire).</p>
+
+ <p>A value of 0 means the condition can match immediately. A
+ value of MAX_UINT32 means that the CM's default should be
+ used.</p>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" tp:type="Contact_Handle" name="Handle">
+ <tp:docstring>
+ The contact to forward an incoming channel to. If the handle
+ doesn't point to anything (e.g. points to a phone number that
+ doesn't exist), the entry SHOULD be skipped.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Forwarding_Rule_Chain">
+ <tp:docstring>
+ A chain of forwarding rules and an initial timeout after which
+ the rules are applied.
+ </tp:docstring>
+
+ <tp:member type="u" name="InitialTimeout">
+ <tp:docstring>Initial timeout for the rule.</tp:docstring>
+ </tp:member>
+
+ <tp:member type="a(uu)" name="Rules" tp:type="Forwarding_Rule_Entry[]">
+ <tp:docstring>The forwarding targets (an array of type
+ <tp:type>Forwarding_Rule_Entry</tp:type>).
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Forwarding_Rule_Map" array-name="">
+ <tp:docstring>A dictionary whose keys are forwarding conditions and
+ whose values are <tp:type>Forwarding_Rule_Chain</tp:type> structs.
+ </tp:docstring>
+
+ <tp:member type="u" tp:type="Forwarding_Condition" name="Condition" />
+ <tp:member type="(ua(uu))" tp:type="Forwarding_Rule_Chain"
+ name="Rule_Chain" />
+ </tp:mapping>
+
+ <tp:mapping name="Supported_Forwarding_Conditions_Map" array-name="">
+ <tp:docstring>A dictionary whose keys are forwarding conditions and
+ whose values are maximum number of <tp:type>Forwarding_Rule_Entry</tp:type>
+ for the condition.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Forwarding_Condition" name="Condition" />
+ <tp:member type="u" name="Chain_Length" />
+ </tp:mapping>
+
+ <property name="SupportedForwardingConditions" type="a{uu}" access="read"
+ tp:type="Supported_Forwarding_Conditions_Map"
+ tp:name-for-bindings="Supported_Forwarding_Conditions">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map of forwarding conditions supported on this connection to
+ maximum number of <tp:type>Forwarding_Rule_Entry</tp:type>
+ supported for the specific condition.</p>
+
+ <tp:rationale>
+ <p>When forwarding is done by the provider, different providers
+ might support different chain sizes, or provider and local
+ implementation chain sizes might differ.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="ForwardingRules" type="a{u(ua(uu))}" access="read"
+ tp:type="Forwarding_Rule_Map" tp:name-for-bindings="Forwarding_Rules">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current forwarding rules that are enabled for this connection.
+ Forwarding rules each contain an array of type
+ <tp:type>Forwarding_Rule_Entry</tp:type>.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="ForwardingRuleChanged"
+ tp:name-for-bindings="Forwarding_Rule_Changed">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the <tp:member-ref>ForwardingRules</tp:member-ref> property changes.</p>
+
+ <p>By the time this is emitted, the property MUST have been updated
+ with the new rules being active. If any protocol/network
+ requests must be made, they should be completed before the signal
+ is emitted.</p>
+ </tp:docstring>
+
+ <arg name="Condition" type="u" tp:type="Forwarding_Condition">
+ <tp:docstring>
+ The condition of the forwarding rule that's been changed.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Timeout" type="u">
+ <tp:docstring>
+ The new initial timeout for the rule.
+ </tp:docstring>
+ </arg>
+
+ <arg name="Forwards" type="a(uu)" tp:type="Forwarding_Rule_Entry[]">
+ <tp:docstring>
+ The new (and as of the emission of the signal, currently active)
+ forwards. The order is relevant; those at the lowest array index
+ are used first.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="SetForwardingRule" tp:name-for-bindings="Set_Forwarding_Rule">
+ <tp:docstring>
+ Update the forwarding rules.
+ </tp:docstring>
+
+ <arg direction="in" name="Condition" type="u" tp:type="Forwarding_Condition">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The forwarding rule to override. Note that this SHOULD not affect
+ other rules; setting a rule that overrides others (such as
+ Forwarding_Rule_Unconditional) will not modify other rules. This
+ means that when a client sets Forwarding_Rule_Busy and then
+ temporarily sets Forwarding_Rule_Unconditional, the
+ Forwarding_Rule_Busy rule will retain settings after
+ Forwarding_Rule_Unconditional, has been unset.</p>
+
+ <p>If the CM has no choice but to adjust multiple rules after a call
+ to this function (ie, due to the network or protocol forcing such
+ behavior), the CM MUST emit multiple <tp:member-ref>ForwardingRuleChanged</tp:member-ref>
+ signals for each changed rule. The order of the signals is
+ implementation-dependent, with the only requirement that the
+ last signal is for the rule that was originally requested to have
+ been changed (e.g. if Unconditional automatically modifies
+ Busy and NoReply, three
+ separate <tp:member-ref>ForwardingRuleChanged</tp:member-ref> signals should be raised with the
+ last signal being for Forwarding_Rule_Unconditional).</p>
+
+ <p>Each forwarding condition will occur no more than once in
+ the rule array. Setting a rule will overwrite the old rule
+ with the same <tp:type>Forwarding_Condition</tp:type> in its entirety.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="Forwards" type="a(uu)" tp:type="Forwarding_Rule_Entry[]">
+ <tp:docstring>
+ The forwarding targets (an array of type <tp:type>Forwarding_Rule_Entry</tp:type>) to
+ activate for the rule. An empty array will effectively disable the
+ rule.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Old_Forwards" type="a(uu)" tp:type="Forwarding_Rule_Entry[]">
+ <tp:docstring>
+ The old forwarding targets (an array of type <tp:type>Forwarding_Rule_Entry</tp:type>).
+ This is the list of entries that is being replaced with the call to
+ <tp:member-ref>SetForwardingRule</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The specified Condition is not supported by this connection,
+ or the number of chained
+ <tp:member-ref>SupportedForwardingConditions</tp:member-ref> should
+ be checked prior to calling
+ <tp:member-ref>SetForwardingRule</tp:member-ref>.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ A Handle that has been supplied is invalid.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Keepalive.xml b/qt4/spec/Connection_Interface_Keepalive.xml
new file mode 100644
index 000000000..9f4ac6833
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Keepalive.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Keepalive"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright © 2010 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Keepalive.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.21.2">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Most messaging protocols allow the client to send periodic
+ content-less pings to the server when the connection is otherwise idle,
+ to reassure both itself and the server that its connection is still
+ alive. Depending on the nature of the network connection, and the
+ device running the client, the desired interval between such pings may
+ vary.</p>
+
+ <tp:rationale>
+ <p>For instance, on a mobile handset connected via 3G,
+ overly-frequent keepalives can drain the battery through needlessly
+ waking up the radio, and a relatively high interval is appropiate. By
+ contrast, a desktop computer is less likely to be asleep in the first
+ place, and users expect dropped connections to be noticed as soon as
+ possible.</p>
+ </tp:rationale>
+
+ <p>This interface provides a
+ <tp:member-ref>KeepaliveInterval</tp:member-ref> property which
+ controls the frequency of keepalive pings, if any. Connection managers
+ implementing this property should also include it in <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy'>Protocol.Parameters</tp:dbus-ref>
+ with the <code>DBus_Property</code> flag, allowing the desired value to
+ be stored in <tp:dbus-ref
+ namespace='org.freedesktop.Telepathy'>Account.Parameters</tp:dbus-ref>
+ and passed onto the connection by the account manager.</p>
+ </tp:docstring>
+
+ <property name="KeepaliveInterval" type="u" access="readwrite"
+ tp:name-for-bindings="Keepalive_Interval"
+ tp:is-connection-parameter='och aye'>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time in seconds between pings sent to the server to ensure that
+ the connection is still alive, or <tt>0</tt> to disable such
+ pings.</p>
+
+ <p>This property (and parameter) supersedes the older
+ <tt>keepalive-interval</tt>
+ <tp:type>Connection_Parameter_Name</tp:type>.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Location.xml b/qt4/spec/Connection_Interface_Location.xml
new file mode 100644
index 000000000..fe5492345
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Location.xml
@@ -0,0 +1,464 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Location"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2008 Collabora Ltd.</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Location">
+ <tp:added version="0.17.27">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface on connections to support protocols which allow users to
+ publish their current geographical location, and subscribe to the
+ current location of their contacts.</p>
+
+ <p>This interface is geared strongly towards automatic propagation and
+ use of this information, so focuses on latitude, longitude and
+ altitude which can be determined by GPS, although provision is also
+ included for an optional human-readable description of locations. All
+ co-ordinate information is required to be relative to the WGS84
+ datum.</p>
+
+ <p>The information published through this interface is intended to have
+ the same scope as presence information, so will normally be made
+ available to those individuals on the user's "publish" contact list.
+ Even so, user interfaces should not automatically publish location
+ information without the consent of the user, and it is recommended
+ that an option is made available to reduce the accuracy of the
+ reported information to allow the user to maintain their privacy.</p>
+
+ <p>Location information is represented using the terminology of XMPP's
+ <a href="http://www.xmpp.org/extensions/xep-0080.html">XEP-0080</a>
+ or the XEP-0080-derived
+ <a href="http://geoclue.freedesktop.org/">Geoclue</a> API where
+ possible.</p>
+
+ <p>Clients of this interface SHOULD register an interest in it by calling
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.AddClientInterest</tp:dbus-ref> with an argument
+ containing the name of this interface,
+ before calling any Location method. If they do so, they SHOULD also call
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.RemoveClientInterest</tp:dbus-ref> after use to allow
+ the CM to release resources associated with this interface.</p>
+ </tp:docstring>
+
+ <!-- Potentially to be reinstated later:
+ http://bugs.freedesktop.org/show_bug.cgi?id=19585
+ <tp:enum name="Location_Accuracy_Level" type="i">
+ <tp:docstring>
+ A location accuracy level. This should be kept in sync with
+ GeoclueAccuracyLevel in the Geoclue project.
+ </tp:docstring>
+
+ <tp:enumvalue suffix="None" value="0">
+ <tp:docstring>
+ The accuracy is unspecified.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Country" value="1">
+ <tp:docstring>
+ The location indicates the contact's country.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Region" value="2">
+ <tp:docstring>
+ The location indicates the contact's region within a country.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Locality" value="3">
+ <tp:docstring>
+ The location indicates the contact's locality within a region
+ (e.g. the correct city).
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Postal_Code" value="4">
+ <tp:docstring>
+ The location indicates the correct postal code.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Street" value="5">
+ <tp:docstring>
+ The location indicates the correct street.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Detailed" value="6">
+ <tp:docstring>
+ The location's accuracy is given by the accuracy key.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ -->
+
+ <tp:mapping name="Location">
+ <tp:docstring>
+ A user's location, represented as an extensible mapping.
+ </tp:docstring>
+
+ <tp:member name="Key" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+
+ <p>Civic addresses are represented by the following well-known
+ keys (all of which have string values), which should be kept in
+ sync with those used in XEP-0080 and in the Geoclue project:</p>
+
+ <ul>
+ <li>countrycode - s: an ISO-3166-1 alpha-2 (two-letter) country
+ code, e.g. "us", "gb", "fr"</li>
+ <li>country - s: a country name in unspecified locale, e.g.
+ "USA"</li>
+ <li>region - s: an administrative region of the nation, such as a
+ state or province</li>
+ <li>locality - s: a locality within the administrative region, such
+ as a town or city</li>
+ <li>area - s: a named area such as a campus or neighborhood</li>
+ <li>postalcode - s: a code used for postal delivery</li>
+ <li>street - s: a thoroughfare within the locality, or a crossing of
+ two thoroughfares</li>
+ </ul>
+
+ <p>The following address keys are defined in XEP-0080 but not by
+ Geoclue, and are also allowed:</p>
+
+ <ul>
+ <li>building - s: a specific building on a street or in an area</li>
+ <li>floor - s: a particular floor in a building</li>
+ <li>room - s: a particular room in a building</li>
+ <li>text - s: any more specific information, e.g.
+ "Northwest corner of the lobby"</li>
+ <li>description - s: A natural-language name for or description of
+ the location, e.g. "Bill's house"</li>
+ <li>uri - s: a URI representing the location or pointing to more
+ information about it</li>
+ </ul>
+
+ <p>Since the previous strings have data intended to be read by users,
+ the language used should be stated using:</p>
+
+ <ul>
+ <li>language - s: a specific language or locale of location
+ information in a format compatible to RFC 4646. Note that UTF-8
+ is the only allowed encoding, e.g. "en" or "fr-CA".</li>
+ </ul>
+
+ <p>Positions are represented by the following well-known keys:</p>
+
+ <ul>
+ <li>lat - d: latitude in decimal degrees north, -90 to +90,
+ relative to the WGS-84 datum
+ <tp:rationale>
+ This is from XEP-0080; the XEP allows use of a different
+ datum, but recommends this one. We enforce sanity by requiring
+ a consistent datum: a minimal compliant implementation of this
+ specification in terms of XEP-0080 would simply ignore the
+ &lt;lat&gt; and &lt;lon&gt; elements if &lt;datum&gt; exists
+ and has a value other than WGS-84, while an advanced
+ implementation might correct for the different datum.
+ </tp:rationale>
+ </li>
+ <li>lon - d: Longitude in decimal degrees east, -180 to +180,
+ relative to the WGS-84 datum
+ <tp:rationale>
+ Same rationale as 'lat'
+ </tp:rationale>
+ </li>
+ <li>alt - d: altitude in metres above sea level (negative
+ if below sea level)
+ <tp:rationale>
+ This is from XEP-0080
+ </tp:rationale>
+ </li>
+
+ <!-- Potentially to be reinstated later:
+ http://bugs.freedesktop.org/show_bug.cgi?id=19585
+ <li>accuracy-level - i (<tp:type>Location_Accuracy_Level</tp:type>):
+ an indication of accuracy, which SHOULD be omitted if it would be
+ Location_Accuracy_Level_None or
+ Location_Accuracy_Level_Detailed
+ <tp:rationale>
+ This is a struct field in GeoClue; the name is new in this
+ specification, and was chosen in an attempt to avoid clashing
+ with any future XEP-0080 terminology.
+ </tp:rationale>
+ </li>
+ -->
+
+ <li>accuracy - d: horizontal position error in metres if
+ known
+ <tp:rationale>
+ This is from XEP-0080
+ </tp:rationale>
+ </li>
+ </ul>
+
+ <p>Velocities are represented by the following well-known keys:</p>
+
+ <ul>
+ <li>speed - d: speed in metres per second
+ <tp:rationale>
+ This is from XEP-0080
+ </tp:rationale>
+ </li>
+ <li>bearing - d: direction of movement in decimal degrees,
+ where North is 0 and East is 90
+ <tp:rationale>
+ This is from XEP-0080, and is equivalent to the struct field
+ called "direction" in GeoClue
+ </tp:rationale>
+ </li>
+ </ul>
+
+ <p>Other well-known keys:</p>
+
+ <ul>
+ <li>timestamp - x (<tp:type>Unix_Timestamp64</tp:type>): the time
+ that the contact was at this location, in seconds since
+ 1970-01-01T00:00:00Z (i.e. the beginning of 1970 in UTC)
+ <tp:rationale>
+ XEP-0080 uses an ISO 8601 string for this, but a number of
+ seconds since the epoch is probably easier to work with.
+ </tp:rationale>
+ </li>
+ </ul>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Value" type="v">
+ <tp:docstring>
+ The value corresponding to the well-known key.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:mapping name="Contact_Locations" type="a{ua{sv}}">
+ <tp:docstring>
+ A map from contacts to their locations.
+ </tp:docstring>
+ <tp:member name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>A contact</tp:docstring>
+ </tp:member>
+ <tp:member name="Location" type="a{sv}" tp:type="Location">
+ <tp:docstring>The contact's location, which MAY be empty to indicate
+ that the contact's location is unknown</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <method name="GetLocations" tp:name-for-bindings="Get_Locations">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Return the current locations of the given contacts, if they are
+ already known. If any of the given contacts' locations are not known,
+ request their current locations, but return immediately without waiting
+ for a reply; if a reply with a non-empty location is later received
+ for those contacts, the <tp:member-ref>LocationUpdated</tp:member-ref>
+ signal will be emitted for them.</p>
+
+ <tp:rationale>
+ <p>This method is appropriate for "lazy" location finding, for instance
+ displaying the location (if available) of everyone in your contact
+ list.</p>
+ </tp:rationale>
+
+ <p>For backwards compatibility, if this method is called by a client
+ whose "interest count" for this interface, as defined by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.AddClientInterest</tp:dbus-ref>, is zero, the
+ Connection SHOULD behave as if AddClientInterest had been called for
+ this interface just before that method call. Clients that do not
+ explicitly call AddClientInterest SHOULD NOT call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.RemoveClientInterest</tp:dbus-ref> either.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The contacts whose locations should be returned or signalled.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Locations" type="a{ua{sv}}"
+ tp:type="Contact_Locations">
+ <tp:docstring>
+ The contacts' locations, if already known. Contacts whose locations
+ are not already known are omitted from the mapping; contacts known
+ to have no location information appear in the mapping with an empty
+ Location dictionary.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestLocation" tp:name-for-bindings="Request_Location">
+ <tp:docstring>
+ Return the current location of the given contact. If necessary, make
+ a request to the server for up-to-date information, and wait for a
+ reply.
+
+ <tp:rationale>
+ This method is appropriate for use in a "Contact Information..."
+ dialog; it can be used to show progress information (while waiting
+ for the method to return), and can distinguish between various error
+ conditions.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact whose location should be returned.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Location" type="a{sv}" tp:type="Location">
+ <tp:docstring>
+ The contact's location. It MAY be empty, indicating that no location
+ information was found.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied">
+ <tp:docstring>
+ The requested contact does not allow the local user to see their
+ location information.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="LocationUpdated" tp:name-for-bindings="Location_Updated">
+ <tp:docstring>
+ Emitted when a contact's location changes or becomes known.
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact
+ </tp:docstring>
+ </arg>
+ <arg name="Location" type="a{sv}" tp:type="Location">
+ <tp:docstring>
+ The contact's location, or empty to indicate that nothing is known
+ about the contact's location.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="SetLocation" tp:name-for-bindings="Set_Location">
+ <tp:docstring>
+ Set the local user's own location.
+ </tp:docstring>
+
+ <arg direction="in" name="Location" type="a{sv}">
+ <tp:docstring>
+ The location to advertise. If the user wants to obscure their
+ exact location by reducing the precision or accuracy, clients
+ MUST do this themselves, rather than relying on the connection
+ manager to do so. Clients that interact with more than one
+ connection SHOULD advertise the same reduced-accuracy location
+ to all of them, so that contacts cannot obtain an undesirably
+ accurate location by assuming that random errors have been added
+ and averaging the locations advertised on multiple connections.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The user's server does not support publishing their own location.
+ If it is possible to determine this ahead of time, the
+ <code>Can_Set</code> flag will not be set in
+ <tp:member-ref>SupportedLocationFeatures</tp:member-ref>.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="LocationAccessControlTypes" type="au" access="read"
+ tp:type="Rich_Presence_Access_Control_Type[]" tp:name-for-bindings="Location_Access_Control_Types">
+ <tp:docstring>The types of access control that are supported by this
+ connection.</tp:docstring>
+ </property>
+
+ <property name="LocationAccessControl" type="(uv)" access="readwrite"
+ tp:type="Rich_Presence_Access_Control" tp:name-for-bindings="Location_Access_Control">
+ <tp:docstring>The current access control mechanism and settings
+ for this connection. Before publishing location for the first time,
+ if this has not been set by a client, implementations SHOULD
+ set it to be as restrictive as possible (an empty whitelist, if
+ supported).</tp:docstring>
+ </property>
+
+ <property name="SupportedLocationFeatures"
+ tp:name-for-bindings="Supported_Location_Features"
+ type="u" tp:type="Location_Features" access="read">
+ <tp:added version="0.19.6"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Indicates the Location features supported by this connection. This
+ property MAY be undefined before <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">Status</tp:dbus-ref>
+ becomes <code>Connected</code>, but MUST remain constant thereafter.
+ </tp:docstring>
+ </property>
+
+ <tp:flags name="Location_Features" type="u" value-prefix="Location_Feature">
+ <tp:flag suffix="Can_Set" value="1">
+ <tp:docstring>
+ Indicates that setting your own location with
+ <tp:member-ref>SetLocation</tp:member-ref> is supported on this
+ connection.
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Flags describing the Location features which may be supported on any
+ given connection.
+ </tp:docstring>
+ </tp:flags>
+
+ <tp:contact-attribute name="location"
+ type="a{sv}" tp:type="Location">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same mapping that would be returned by
+ <tp:member-ref>GetLocations</tp:member-ref> for this contact.
+ Omitted from the result if the contact's location
+ is not known.</p>
+
+ <p>For backwards compatibility, if contact attributes that include
+ this interface are requested
+ by a client whose "interest count" for this interface, as defined by
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.AddClientInterest</tp:dbus-ref>, is zero, the
+ Connection SHOULD behave as if AddClientInterest was called for this
+ interface just before that request. Clients that do not explicitly
+ call AddClientInterest SHOULD NOT call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.RemoveClientInterest</tp:dbus-ref> either.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Mail_Notification.xml b/qt4/spec/Connection_Interface_Mail_Notification.xml
new file mode 100644
index 000000000..395e1019d
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Mail_Notification.xml
@@ -0,0 +1,624 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Mail_Notification"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ >
+ <tp:copyright> Copyright (C) 2007 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Connection.Interface.MailNotification">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.21.3">(as stable API)</tp:added>
+
+ <tp:client-interest>
+ <tp:docstring>
+ A client MUST notify interest in this feature before it will be
+ enabled.
+ </tp:docstring>
+ </tp:client-interest>
+
+ <tp:flags name="Mail_Notification_Flags" value-prefix="Mail_Notification_Flag" type="u" >
+ <tp:flag suffix="Supports_Unread_Mail_Count" value="1">
+ <tp:docstring>
+ This Connection provides the number of unread e-mails (or e-mail
+ threads) in the main folder of your e-mail account, as the
+ <tp:member-ref>UnreadMailCount</tp:member-ref> property. The
+ connection manager will update this value by emitting the
+ <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Supports_Unread_Mails" value="2">
+ <tp:docstring>
+ This Connection provides a detailed list of unread e-mails, as the
+ <tp:member-ref>UnreadMails</tp:member-ref> property. If this flag
+ is set, <tt>Supports_Unread_Mail_Count</tt> MUST be set, and
+ <tt>Emits_Mails_Received</tt> MUST NOT be set.
+ The Connection will update the list by emitting the
+ <tp:member-ref>UnreadMailsChanged</tp:member-ref> signals.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Emits_Mails_Received" value="4">
+ <tp:docstring>
+ This Connection emits the <tp:member-ref>MailsReceived</tp:member-ref>
+ signal, which provides details about newly arrived e-mails but does
+ not maintain their read/unread status afterwards. This flag MUST NOT
+ be combined with <tt>Supports_Unread_Mails</tt>.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Supports_Request_Inbox_URL" value="8">
+ <tp:docstring>
+ This Connection can provide a URL (with optional POST data) to
+ open the the inbox of the e-mail account in a web-based client, via
+ the <tp:member-ref>RequestInboxURL</tp:member-ref> method.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Supports_Request_Mail_URL" value="16">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This Connection can provide a URL (with optional POST data) to open
+ a specific mail in a web-based client, via the
+ <tp:member-ref>RequestMailURL</tp:member-ref> method. This feature
+ is not useful unless either Emits_Mails_Received or
+ Supports_Unread_Mails is set.</p>
+
+ <p>If this flag is not set, clients SHOULD fall back to using
+ <tp:member-ref>RequestInboxURL</tp:member-ref> if available.</p>
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Thread_Based" value="32">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Each <tp:type>Mail</tp:type> represents a thread of e-mails, which
+ MAY have more than one sender.</p>
+
+ <tp:rationale>
+ <p>Google Talk notifies users about new mail in terms of unread
+ threads, rather than unread e-mails.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:flag>
+
+ <tp:docstring>
+ <p>Flags representing capabilities provided by a connection manager.
+ Those values can be used as bitfield. Some flags depend on, or
+ conflict with, each other.</p>
+
+ <p>Connections SHOULD implement as many of these features as the
+ underlying protocol allows, preferring to implement
+ Supports_Unread_Mails instead of Emits_Mails_Received if both are
+ possible.</p>
+ </tp:docstring>
+ </tp:flags>
+
+ <tp:enum name="HTTP_Method" type="u">
+ <tp:enumvalue suffix="Get" value="0">
+ <tp:docstring>
+ Use the GET method when opening the URL.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Post" value="1">
+ <tp:docstring>
+ Use the POST method when opening the URL. Refer to
+ <tp:type>HTTP_Post_Data</tp:type> for more details.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:docstring>
+ The HTTP Method with which to request a URL.
+ </tp:docstring>
+ </tp:enum>
+
+ <tp:struct name="HTTP_Post_Data" array-name="HTTP_Post_Data_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A pair (key, value) representing POST data compatible with the
+ application/x-www-form-urlencoded MIME type. The strings MUST be
+ valid UTF-8 strings, and the characters used in the key MUST obey
+ the requirements of the
+ <a href="http://www.w3.org/TR/html401/types.html#type-cdata">
+ HTML CDATA type</a>. The value MUST NOT be
+ encoded with HTML entities.</p>
+
+ <p>For example, if the POST data should contain a key "less-than" with value
+ "&lt;", and a key "percent" with value "%", this should be represented as
+ two HTTP_Post_Data structures, ("less-than", "&lt;") and ("percent", "%"),
+ resulting in a POST request whose request body is "less-than=&amp;lt;&amp;percent=%25".
+ If a client passes this to a browser by writing it into an HTML form, it
+ could do so by representing it as:</p>
+
+ <pre>
+ &lt;input type="hidden" name="less-than"&gt;&amp;lt;&lt;/input&gt;
+ &lt;input type="hidden" name="percent"&gt;%&lt;/input&gt;
+ </pre>
+
+ <tp:rationale>
+ <p>This data can be used to generate a HTML file that will
+ automatically load the URL with appropriate POST data, in which case
+ the client MUST convert any characters that are special within HTML
+ into HTML entities. Alternatively, it can be used in an API that will
+ instruct the browser how to load the URL (like the Netscape Plug-in
+ API), in which case the client MUST escape
+ <a href="http://www.ietf.org/rfc/rfc1738.txt">characters that are
+ reserved in URLs</a>, if appropriate for that API.</p>
+
+ <p>An array of pairs is used instead of a map from keys to values,
+ because it's valid to repeat keys in both HTML and
+ x-www-form-urlencoded data.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:member type="s" name="Key">
+ <tp:docstring>The key, corresponding to a HTML control
+ name</tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Value">
+ <tp:docstring>The value</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Mail_Address" array-name="Mail_Address_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A pair (name, address) representing an e-mail address,
+ such as ("Nicolas Dufresne", "nicolas.dufresne@collabora.co.uk"). At
+ least one of name and address MUST be provided. A missing element will
+ be represented by the empty string.</p>
+ <tp:rationale>
+ <p>The CM should provide as much information as possible, but not all
+ protocols provide both the displayed name and the address. (If a
+ protocol doesn't provide either, it should omit the appropriate
+ field from the <tp:type>Mail</tp:type> entirely.)</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:member type="s" name="Name">
+ <tp:docstring>The displayed name corresponding to the e-mail
+ address</tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Address">
+ <tp:docstring>The actual e-mail address</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Mail_URL">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A structure containing the required information to open a web-based
+ e-mail UI, without needing re-authentication (if possible).</p>
+
+ <p>Because the URL and POST data frequently contain short-lived
+ credential tokens, a new URL should be requested (by calling one of
+ the methods that returns a Mail_URL) for each visit to the web-based
+ UI, and the URL should be visited soon after it is returned.</p>
+ </tp:docstring>
+ <tp:member type="s" name="URL">
+ <tp:docstring>
+ The URL to which to send a request.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Method" tp:type="HTTP_Method">
+ <tp:docstring>
+ The HTTP method of the request.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="a(ss)" name="Post_Data" tp:type="HTTP_Post_Data[]">
+ <tp:docstring>
+ An array of name-value pairs containing the POST data to use when
+ opening the URL. This MUST be an empty array if the Method is not
+ POST.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Mail" array-name="Mail_List">
+ <tp:docstring>
+ An extensible map representing a mail, or (on protocols where
+ <tt>Thread_Based</tt> appears in
+ <tp:member-ref>MailNotificationFlags</tp:member-ref>) a thread of
+ mails. All keys are optional where not otherwise stated; however, at
+ least one of "senders" and "subject" must be included.
+ </tp:docstring>
+
+ <tp:member type="s" name="Key">
+ <tp:docstring>
+ <p>A key providing information about the mail or thread. Well-known
+ keys are as follows:</p>
+
+ <dl>
+ <dt>id &#8212; s</dt>
+ <dd>
+ <p>A unique ID for this e-mail. CMs with
+ <tt>Supports_Unread_Mails</tt> set in
+ <tp:member-ref>MailNotificationFlags</tp:member-ref> MUST provide
+ this key in each <tp:type>Mail</tp:type>.</p>
+
+ <p>If provided, the ID SHOULD be unique to a Mail at least until
+ that mail is removed with the
+ <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal
+ (in protocols with <tt>Supports_Unread_Emails</tt>), or
+ unique for the duration of a session (otherwise).</p>
+
+ <tp:rationale>
+ <p>In protocols with Supports_Unread_Mails, this key is used to
+ indicate which mail was removed. In protocols without that
+ feature, it's impossible to tell when a mail has been removed
+ (and hence how long the identifier will remain valid for use
+ with <tp:member-ref>RequestMailURL</tp:member-ref>).</p>
+ </tp:rationale>
+ </dd>
+
+ <dt>url-data &#8212; any type</dt>
+ <dd>An opaque identifier (typically a string or list of strings)
+ provided to the Connection when calling
+ <tp:member-ref>RequestMailURL</tp:member-ref>,
+ containing information used by the Connection to build the URL.
+ </dd>
+
+ <dt>senders &#8212; a(ss) (<tp:type>Mail_Address</tp:type>)</dt>
+ <dd>
+ An array of sender display name and e-mail address pairs. Note that
+ only e-mails represented as a thread can have multiple senders.
+ </dd>
+
+ <dt>to-addresses &#8212; a(ss) (<tp:type>Mail_Address</tp:type>)</dt>
+ <dd>
+ An array of display name and e-mail address pairs representing
+ the recipients.
+ </dd>
+
+ <dt>cc-addresses &#8212; a(ss) (<tp:type>Mail_Address</tp:type>)</dt>
+ <dd>
+ An array of display name and e-mail address pairs representing
+ the carbon-copy recipients.
+ </dd>
+
+ <dt>sent-timestamp &#8212; x (<tp:type>Unix_Timestamp64</tp:type>)</dt>
+ <dd>A UNIX timestamp indicating when the message was sent, or for
+ a thread, when the most recent message was sent.
+ </dd>
+
+ <dt>received-timestamp &#8212; x (<tp:type>Unix_Timestamp64</tp:type>)</dt>
+ <dd>A UNIX timestamp indicating when the message was received, or for
+ a thread, when the most recent message was received.
+ </dd>
+
+ <dt>has-attachments &#8212; b</dt>
+ <dd>If true, this mail has attachments.</dd>
+
+ <dt>subject &#8212; s</dt>
+ <dd>
+ The subject of the message. This MUST be encoded in UTF-8.
+ </dd>
+
+ <dt>content-type &#8212; s</dt>
+ <dd>
+ <p>The MIME type of the message content. Two types are currently
+ supported: "text/plain" for plain text, and "text/html" for a
+ HTML document. If omitted, "text/plain" MUST be assumed.
+ Regardless of MIME type, the content MUST be valid UTF-8 (which
+ may require that the Connection transcodes it from a legacy
+ encoding).</p>
+
+ <tp:rationale>
+ <p>All strings on D-Bus must be UTF-8.</p>
+ </tp:rationale>
+ </dd>
+
+ <dt>truncated &#8212; b</dt>
+ <dd>
+ If true, the content is only a partial message; if false or
+ omitted, the content is the entire message.
+ </dd>
+
+ <dt>content &#8212; s</dt>
+ <dd>
+ The body of the message, possibly truncated, encoded as appropriate
+ for "content-type".
+ </dd>
+
+ <dt>folder &#8212; s</dt>
+ <dd>
+ The name of the folder containing this e-mails.
+ If omitted, the inbox SHOULD be assumed.
+ </dd>
+ </dl>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Value" type="v">
+ <tp:docstring>The value, of whatever type is appropriate for the
+ key.</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="MailNotificationFlags" type="u" access="read"
+ tp:type="Mail_Notification_Flags"
+ tp:name-for-bindings="Mail_Notification_Flags">
+ <tp:docstring>
+ Integer representing the bitwise-OR of supported features for e-mails
+ notification on this server. This property MUST NOT change after the
+ Connection becomes CONNECTED.
+
+ <tp:rationale>
+ This property indicates the behavior and availability
+ of the other properties and signals within this interface. A
+ connection manager that cannot at least set one of the flags
+ in the <tp:type>Mail_Notification_Flags</tp:type>
+ SHOULD NOT provide this interface.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="UnreadMailCount" type="u" access="read"
+ tp:name-for-bindings="Unread_Mail_Count">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The number of unread messages in the Inbox. Change notification is
+ via <tp:member-ref>UnreadMailsChanged</tp:member-ref>.</p>
+
+ <p>This property is only useful if <tt>Supports_Unread_Mail_Count</tt>
+ is set in the <tp:member-ref>MailNotificationFlags</tp:member-ref>;
+ otherwise, it MUST be zero.</p>
+
+ <p>If <tt>Thread_Based</tt> appears in the
+ <tp:member-ref>MailNotificationFlags</tp:member-ref>, this property
+ counts the number of threads, not the number of mails.</p>
+
+ <p>Note that this count MAY be bigger than the number of items in
+ <tp:member-ref>UnreadMails</tp:member-ref>. See
+ <tp:member-ref>UnreadMails</tp:member-ref> for more details.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="UnreadMails" type="aa{sv}" tp:type="Mail[]"
+ tp:name-for-bindings="Unread_Mails" access="read">
+ <tp:docstring>
+ <p>An array of unread <tp:type>Mail</tp:type>s. Change notification is
+ via <tp:member-ref>UnreadMailsChanged</tp:member-ref>. This property
+ is only useful if <tt>Supports_Unread_Mails</tt> is set in
+ <tp:member-ref>MailNotificationFlags</tp:member-ref>; otherwise, it
+ MUST be an empty list.</p>
+ <p>The array size MAY be shorter than
+ <tp:member-ref>UnreadMailCount</tp:member-ref>.</p>
+ <tp:rationale>
+ <p>Some servers may limits the amount of detailed e-mails sent. This
+ can significantly reduce the network traffic for large inbox. For
+ this reason, it is normal that
+ <tp:member-ref>UnreadMailCount</tp:member-ref> be bigger or equal
+ to the size of this array.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="MailAddress" type="s"
+ tp:name-for-bindings="Mail_Address" access="read">
+ <tp:docstring>
+ A string representing the e-mail address of the account. The CMs MUST
+ provide this information.
+ <tp:rationale>
+ In close integration of MailNotification with other e-mail services,
+ the e-mail address can be used has a unique identifier for the
+ account. Possible integration could be between Telepathy and
+ Evolution where the e-mail address is the common information in
+ both interfaces.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="MailsReceived" tp:name-for-bindings="Mails_Received">
+ <arg name="Mails" type="aa{sv}" tp:type="Mail[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of <tp:type>Mail</tp:type>s. Those e-mail MUST NOT have
+ the "id" key.</p>
+
+ <tp:rationale>
+ <p>On connections that emit this signal, it's impossible to tell
+ when a mail has been removed, and hence when "id" has become
+ invalid.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <tp:docstring>
+ Emitted when new e-mails messages arrive to the inbox associated with
+ this connection. This signal is used for protocols that are not able
+ to maintain the <tp:member-ref>UnreadMails</tp:member-ref> list, but
+ do provide real-time notification about newly arrived e-mails. It MUST
+ NOT be emitted unless <tt>Emits_Mails_Received</tt> is set in
+ <tp:member-ref>MailNotificationFlags</tp:member-ref>.
+ </tp:docstring>
+ </signal>
+
+ <signal name="UnreadMailsChanged"
+ tp:name-for-bindings="Unread_Mails_Changed">
+ <arg name="Count" type="u">
+ <tp:docstring>
+ Number of unread messages in the inbox (the new value of
+ <tp:member-ref>UnreadMailCount</tp:member-ref>).
+ </tp:docstring>
+ </arg>
+ <arg name="Mails_Added" type="aa{sv}" tp:type="Mail[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of <tp:type>Mail</tp:type> that are being added or updated
+ in <tp:member-ref>UnreadMails</tp:member-ref>.</p>
+
+ <tp:rationale>
+ <p>Mails may be updated when the URL information (URL and POST data)
+ have changed, or senders were added or removed from an e-mail
+ thread.</p>
+ </tp:rationale>
+
+ <p>If the <tt>Supports_Unread_Mails</tt> flag is not set, this list
+ MUST be empty, even if Count has increased.</p>
+ </tp:docstring>
+ </arg>
+ <arg name="Mails_Removed" type="as">
+ <tp:docstring>
+ A list of e-mail IDs that are being removed from
+ <tp:member-ref>UnreadMails</tp:member-ref>.
+ If the <tt>Supports_Unread_Mails</tt> flag is not set, this list
+ MUST be empty, even if Count has decreased.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when <tp:member-ref>UnreadMails</tp:member-ref> or
+ <tp:member-ref>UnreadMailCount</tp:member-ref> have changed. It MUST
+ NOT be emited if <tt>Supports_Unread_Mail_Count</tt> flag is not set
+ in <tp:member-ref>MailNotificationFlags</tp:member-ref>.</p>
+
+ <p><tt>Mails_Added</tt> and
+ <tt>Mails_Removed</tt> MUST be empty if the
+ <tt>Supports_Unread_Mails</tt> flag is not set.</p>
+ </tp:docstring>
+ </signal>
+
+ <method name="RequestInboxURL"
+ tp:name-for-bindings="Request_Inbox_URL">
+ <arg direction="out" name="URL" type="(sua(ss))" tp:type="Mail_URL" >
+ <tp:docstring>
+ A struture containing a URL and optional additional data to open a
+ webmail client, without re-authentication if possible.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ This method creates and returns a URL and an optional POST data that
+ allow opening the Inbox folder of a webmail account. This URL MAY
+ contain tokens with a short lifetime, so clients SHOULD request a new
+ URL for each visit to the webmail interface. This method is implemented
+ only if the <tt>Supports_Request_Inbox_URL</tt> flag is set in
+ <tp:member-ref>MailNotificationFlags</tp:member-ref>.
+
+ <tp:rationale>
+ We are not using properties here because the tokens are unsuitable
+ for sharing between clients, and network round-trips may be required
+ to obtain the information that leads to authentication free webmail
+ access.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RequestMailURL"
+ tp:name-for-bindings="Request_Mail_URL">
+ <arg direction="in" name="ID" type="s">
+ <tp:docstring>
+ The mail's <tt>id</tt> as found in the <tp:type>Mail</tp:type>
+ structure, or the empty string if no <tt>id</tt> key was provided.
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="URL_Data" type="v">
+ <tp:docstring>
+ Whatever <tt>url-data</tt> was found in the <tp:type>Mail</tp:type>
+ structure, or the boolean value False (D-Bus type 'b') if no
+ <tt>url-data</tt> was provided in the Mail.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="URL" type="(sua(ss))" tp:type="Mail_URL" >
+ <tp:docstring>
+ A struture that contains a URL and optional additional data to open a
+ webmail client, without re-authentication if possible.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ This method creates and returns a URL and optional POST data that
+ allow opening a specific mail in a webmail interface. This
+ method is implemented only if <tt>Supports_Request_Mail_URL</tt> flag
+ is set in <tp:member-ref>MailNotificationFlags</tp:member-ref>.
+ <tp:rationale>
+ See <tp:member-ref>RequestInboxURL</tp:member-ref> for design
+ rationale.
+ </tp:rationale>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface to support receiving notifications about a e-mail
+ account associated with this connection.</p>
+
+ <p>In protocols where this is possible, this interface also allows the
+ connection manager to provide the necessary information for clients
+ to open a web-based mail client without having to re-authenticate.</p>
+
+ <p>To use this interface, a client MUST first subscribe by passing the
+ name of this interface to the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.AddClientInterest</tp:dbus-ref> method. The subscription
+ mechanic aims at reducing network traffic and memory footprint in the
+ situation where nobody is currently interesting in provided
+ information. When done with this interface, clients SHOULD call
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.RemoveClientInterest</tp:dbus-ref> to allow the CM to
+ release resources.</p>
+
+ <p>Protocols have various different levels of Mail Notification support.
+ To describe the level of support, the interface provides a property
+ called <tp:member-ref>MailNotificationFlags</tp:member-ref>.
+ Not all combinations are valid; protocols can be divided into four
+ categories as follows.</p>
+
+ <p>Connections to the most capable protocols, such as Google's XMPP Mail
+ Notification extension, have the Supports_Unread_Mails flag (this
+ implies that they must also have Supports_Unread_Mail_Count, but not
+ Emits_Mails_Received). On these connections, clients
+ requiring change notification MUST monitor the
+ <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal, and
+ either recover the initial state from the
+ <tp:member-ref>UnreadMails</tp:member-ref> property (if they require
+ details other than the number of mails) or the
+ <tp:member-ref>UnreadMailCount</tp:member-ref> property (if they
+ are only interested in the number of unread mails). The
+ <tp:member-ref>MailsReceived</tp:member-ref> signal is never emitted
+ on these connections, so clients that will display a short-term
+ notification for each new mail MUST do so in response to emission of
+ the <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal.</p>
+
+ <p>The most common situation, seen in protocols like MSN and Yahoo, is
+ that the number of unread mails is provided and kept up-to-date,
+ and a separate notification is emitted with some details of each new
+ mail. This is a combination of the following two features, and clients
+ SHOULD implement one or both as appropriate for their requirements.</p>
+
+ <p>On protocols that have the Emits_Mails_Received flag (which implies
+ that they do not have Supports_Unread_Mails), the CM does not keep
+ track of any mails; it simply emits a notification whenever new mail
+ arrives. Those events may be used for short term display (like a
+ notification popup) to inform the user. No protocol is known to support
+ only this feature, but it is useful for integration with libraries that
+ that do not implement tracking of the number of mails. Clients
+ requiring these notifications MUST monitor the
+ <tp:member-ref>MailsReceived</tp:member-ref> signal on any connections
+ with this flag.</p>
+
+ <p>On protocols that have the Supports_Unread_Mail_Count flag but not
+ the Supports_Unread_Mails flag, clients cannot display complete
+ details of unread email, but can display an up-to-date count of the
+ <em>number</em> of unread mails. To do this, they must monitor the
+ <tp:member-ref>UnreadMailsChanged</tp:member-ref> signal, and
+ retrieve the initial state from the
+ <tp:member-ref>UnreadMailCount</tp:member-ref> property.</p>
+
+ <p>
+ Orthogonal features described by the
+ <tp:member-ref>MailNotificationFlags</tp:member-ref> property include the
+ RequestSomethingURL methods, which are used to obtain URLs allowing
+ clients to open a webmail client. Connections SHOULD support as many
+ of these methods as possible.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
+
diff --git a/qt4/spec/Connection_Interface_Power_Saving.xml b/qt4/spec/Connection_Interface_Power_Saving.xml
new file mode 100644
index 000000000..571bf6d51
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Power_Saving.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Power_Saving"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ >
+ <tp:copyright> Copyright © 2007-2010 Collabora Limited </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface
+ name="org.freedesktop.Telepathy.Connection.Interface.PowerSaving">
+ <tp:added version="0.21.5">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Some protocols support mechanisms for reducing bandwidth usage—and
+ hence power usage, on mobile devices—when the user is not directly
+ interacting with their IM client. For instance, Google Talk's XMPP
+ server supports queueing incoming presence updates at the client's
+ instruction; the client can instruct the server to deliver all
+ outstanding presence updates at a later time. This interface may be
+ used to instruct the connection manager to enable and disable such
+ protocol-level features when a screensaver is activated, the device
+ screen is locked, and so on, by calling the
+ <tp:member-ref>SetPowerSaving</tp:member-ref> method.</p>
+
+ <p>Enabling power saving SHOULD NOT change behaviour in any way
+ that is noticable to a user not actively interacting with their client.
+ For example, delaying presence updates somewhat is unlikely to be
+ noticed by a user not staring at their device waiting for a contact to
+ come online; on the other hand, requesting that the server queue
+ incoming messages would be noticable by the user, so is not an
+ acceptable effect of calling
+ <tp:member-ref>SetPowerSaving</tp:member-ref>.</p>
+ </tp:docstring>
+
+ <method name="SetPowerSaving" tp:name-for-bindings="Set_Power_Saving">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Turn power saving mode on or off.</p>
+
+ <tp:rationale>
+ <p>Depending on the device's activity level, the
+ connection can have its power saving mode turned on or off.</p>
+ </tp:rationale>
+
+ <p>Errors raised by this method indicate that power saving could not be
+ enabled, which SHOULD NOT generally be treated as fatal.</p>
+
+ <tp:rationale>
+ If the CM cannot switch modes, either because of the
+ protocol (<code>NotImplemented</code>), or because of the service
+ (<code>NotAvailable</code>), Mission Control (or whoever manages this)
+ should be made aware. The error could be ignored or, in the extreme,
+ be fascist and disconnect the account.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Activate" type="b">
+ <tp:docstring>
+ <code>True</code> if protocol-level power saving features should be
+ activated; <code>False</code> if they should be de-activated.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The current connection has no power saving features.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="PowerSavingActive" type="b" access="read"
+ tp:name-for-bindings="Power_Saving_Active">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p><code>True</code> if protocol-level power saving features are
+ currently activated. This property can be changed using the
+ <tp:member-ref>SetPowerSaving</tp:member-ref> method; change
+ notifications is via the
+ <tp:member-ref>PowerSavingChanged</tp:member-ref> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="PowerSavingChanged"
+ tp:name-for-bindings="Power_Saving_Changed">
+ <arg name="Active" type="b">
+ <tp:docstring>
+ The new state of the power saving feature.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The <tp:member-ref>PowerSavingActive</tp:member-ref>
+ property changed.
+ </tp:docstring>
+ </signal>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Presence.xml b/qt4/spec/Connection_Interface_Presence.xml
new file mode 100644
index 000000000..8a344d416
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Presence.xml
@@ -0,0 +1,346 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Presence" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>
+ Copyright (C) 2005, 2006 Collabora Limited
+ </tp:copyright>
+ <tp:copyright>
+Copyright (C) 2005, 2006 Nokia Corporation
+ </tp:copyright>
+ <tp:copyright>
+Copyright (C) 2006 INdT
+ </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Presence">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection.Interface.SimplePresence"/>
+
+ <tp:mapping name="Multiple_Status_Map">
+ <tp:docstring>Mapping used in
+ <tp:type>Last_Activity_And_Statuses</tp:type> and passed to
+ <tp:member-ref>SetStatus</tp:member-ref>, representing a collection of
+ statuses. Use of this mapping with more than one member is
+ deprecated.</tp:docstring>
+ <tp:member type="s" name="Status"/>
+ <tp:member type="a{sv}" tp:type="String_Variant_Map" name="Parameters"/>
+ </tp:mapping>
+ <tp:struct name="Last_Activity_And_Statuses" array-name="">
+ <tp:docstring>Structure representing a contact's presence, containing
+ a last-activity time (deprecated) and a Multiple_Status_Map.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Unix_Timestamp" name="Last_Activity"/>
+ <tp:member type="a{sa{sv}}" tp:type="Multiple_Status_Map"
+ name="Statuses"/>
+ </tp:struct>
+ <tp:mapping name="Contact_Presences">
+ <tp:docstring>Mapping returned by
+ <tp:member-ref>GetPresence</tp:member-ref> and signalled by
+ <tp:member-ref>PresenceUpdate</tp:member-ref>, where the keys are
+ contacts and the values represent their presences.</tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Contact"/>
+ <tp:member type="(ua{sa{sv}})" tp:type="Last_Activity_And_Statuses"
+ name="Presence"/>
+ </tp:mapping>
+ <tp:struct name="Status_Spec" array-name="">
+ <tp:member type="u" tp:type="Connection_Presence_Type" name="Type"/>
+ <tp:member type="b" name="May_Set_On_Self"/>
+ <tp:member type="b" name="Exclusive"/>
+ <tp:member type="a{ss}" tp:type="String_String_Map"
+ name="Parameter_Types"/>
+ </tp:struct>
+ <tp:mapping name="Status_Spec_Map">
+ <tp:member type="s" name="Identifier"/>
+ <tp:member type="(ubba{ss})" tp:type="Status_Spec" name="Spec"/>
+ </tp:mapping>
+
+ <method name="AddStatus" tp:name-for-bindings="Add_Status">
+ <arg direction="in" name="Status" type="s">
+ <tp:docstring>
+ The string identifier of the desired status
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Parameters" type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring>
+ A dictionary of optional parameter names mapped to their variant-boxed values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that a single presence status is published for the user, along
+ with any desired parameters. Changes will be indicated by
+ <tp:member-ref>PresenceUpdate</tp:member-ref> signals being emitted.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+ <method name="ClearStatus" tp:name-for-bindings="Clear_Status">
+ <tp:docstring>
+ Request that all of a user's presence statuses be removed. Be aware
+ that this request may simply result in the statuses being replaced by a
+ default available status. Changes will be indicated by
+ <tp:member-ref>PresenceUpdate</tp:member-ref> signals being emitted.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+ <method name="GetPresence" tp:name-for-bindings="Get_Presence">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of the contacts whose presence should be obtained
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Presence" type="a{u(ua{sa{sv}})}"
+ tp:type="Contact_Presences">
+ <tp:docstring>
+ Presence information in the same format as for the
+ <tp:member-ref>PresenceUpdate</tp:member-ref> signal
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get presence previously emitted by
+ <tp:member-ref>PresenceUpdate</tp:member-ref> for the given contacts.
+ Data is returned in the same structure as the PresenceUpdate signal.
+ Using this method in favour of
+ <tp:member-ref>RequestPresence</tp:member-ref> has the advantage that
+ it will not wake up each client connected to the PresenceUpdate signal.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+ <method name="GetStatuses" tp:name-for-bindings="Get_Statuses">
+ <arg direction="out" type="a{s(ubba{ss})}" tp:type="Status_Spec_Map"
+ name="Available_Statuses">
+ <tp:docstring>
+ A dictionary of string identifiers mapped to a struct for each status, containing:
+ <ul>
+ <li>a type value from one of the values above</li>
+ <li>a boolean to indicate if this status may be set on yourself</li>
+ <li>a boolean to indicate if this is an exclusive status which you
+ may not set alongside any other</li>
+ <li>a dictionary of valid optional string argument names mapped to
+ their types</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get a dictionary of the valid presence statuses for this connection.
+ This is only available when online because only some statuses will
+ be available on some servers.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+ <signal name="PresenceUpdate" tp:name-for-bindings="Presence_Update">
+ <arg name="Presence" type="a{u(ua{sa{sv}})}" tp:type="Contact_Presences">
+ <tp:docstring>
+ A dictionary of contact handles mapped to a struct containing
+ a UNIX timestamp of the last activity time (in UTC), and
+ a dictionary mapping the contact's current status identifiers to
+ a dictionary of optional parameter names mapped to their
+ variant-boxed values
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ This signal should be emitted when your own presence has been changed,
+ or the presence of the member of any of the connection's channels has
+ been changed, or when the presence requested by
+ <tp:member-ref>RequestPresence</tp:member-ref> is available.
+ </tp:docstring>
+ </signal>
+ <method name="RemoveStatus" tp:name-for-bindings="Remove_Status">
+ <arg direction="in" name="Status" type="s">
+ <tp:docstring>
+ The string identifier of the status not to publish anymore for the user
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that the given presence status is no longer published for the
+ user. Changes will be indicated by
+ <tp:member-ref>PresenceUpdate</tp:member-ref> signals being emitted. As
+ with <tp:member-ref>ClearStatus</tp:member-ref>, removing a status may
+ actually result in it being replaced by a default available status.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>The status requested is not currently set</tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+ <method name="RequestPresence" tp:name-for-bindings="Request_Presence">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of the contacts whose presence should be obtained
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request the presence for contacts on this connection. A <tp:member-ref>PresenceUpdate</tp:member-ref>
+ signal will be emitted when they are received. This is not the same as
+ subscribing to the presence of a contact, which must be done using the
+ 'subscription' <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">ContactList</tp:dbus-ref>,
+ and on some protocols presence information may not be available unless
+ a subscription exists.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The presence of the requested contacts is not reported to this connection
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+ <method name="SetLastActivityTime"
+ tp:name-for-bindings="Set_Last_Activity_Time">
+ <arg direction="in" name="Time" type="u" tp:type="Unix_Timestamp">
+ <tp:docstring>
+ A UNIX timestamp of the user's last activity time (in UTC)
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that the recorded last activity time for the user be updated on
+ the server.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ This protocol has no concept of idle time
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+ <method name="SetStatus" tp:name-for-bindings="Set_Status">
+ <arg direction="in" name="Statuses" type="a{sa{sv}}" tp:type="Multiple_Status_Map">
+ <tp:docstring>
+ A dictionary mapping status identifiers to dictionaries, which
+ map optional parameter names to their variant-boxed values
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the user's presence be changed to the given statuses
+ and desired parameters. Changes will be reflected by
+ <tp:member-ref>PresenceUpdate</tp:member-ref>
+ signals being emitted.</p>
+
+ <p>Statuses whose <tp:type>Connection_Presence_Type</tp:type>
+ is Offline, Error or Unknown MUST NOT be passed to this
+ function. Connection managers SHOULD reject these statuses.</p>
+
+ <tp:rationale>
+ <p>The same rationale as for <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">SimplePresence.SetPresence</tp:dbus-ref>
+ applies.</p>
+ </tp:rationale>
+
+ <p>On certain protocols, this method may be
+ called on a newly-created connection which is still in the
+ DISCONNECTED state, and will sign on with the requested status.
+ If the requested status is not available after signing on,
+ NotAvailable will be returned and the connection will remain
+ offline, or if the protocol does not support signing on with
+ a certain status, Disconnected will be returned.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:deprecated version="0.17.21">Client implementations
+ SHOULD use <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">SimplePresence</tp:dbus-ref>
+ instead.</tp:deprecated>
+ <tp:changed version="0.17.23">Connection managers implementing
+ Presence MUST implement SimplePresence too.</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+
+ <p>This interface is for services which have a concept of presence which
+ can be published for yourself and monitored on your contacts.
+ Telepathy's definition of presence is based on that used by
+ <a href="http://www.galago-project.org/">the Galago project</a>.</p>
+
+ <p>Presence on an individual (yourself or one of your contacts) is modelled as
+ a last activity time along with a set of zero or more statuses, each of
+ which may have arbitrary key/value parameters. Valid statuses are defined
+ per connection, and a list of them can be obtained with the
+ <tp:member-ref>GetStatuses</tp:member-ref> method.</p>
+
+ <p>(The SimplePresence interface which replaces this one restricts
+ presences to one status per contact, with an optional message, which is
+ in practice all that was implemented on this interface.)</p>
+
+ <p>Each status has an arbitrary string identifier which should have an agreed
+ meaning between the connection manager and any client which is expected to
+ make use of it. The well-known values defined by the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">SimplePresence</tp:dbus-ref>
+ interface SHOULD be used where possible</p>
+
+ <p>As well as these well-known status identifiers, every status also has a
+ numerical type value chosen from
+ <tp:type>Connection_Presence_Type</tp:type> which can be used by the client
+ to classify even unknown statuses into different fundamental types.</p>
+
+ <p>These numerical types exist so that even if a client does not understand
+ the string identifier being used, and hence cannot present the presence to
+ the user to set on themselves, it may display an approximation of the
+ presence if it is set on a contact.</p>
+
+ <p>The dictionary of variant types allows the connection manager to exchange
+ further protocol-specific information with the client. It is recommended
+ that the string (s) argument 'message' be interpreted as an optional
+ message which can be associated with a presence status.</p>
+
+ <p>If the connection has a 'subscribe' contact list,
+ <tp:member-ref>PresenceUpdate</tp:member-ref> signals should be emitted to
+ indicate changes of contacts on this list, and should also be emitted for
+ changes in your own presence. Depending on the protocol, the signal may
+ also be emitted for others such as people with whom you are communicating,
+ and any user interface should be updated accordingly.</p>
+
+ <p>On some protocols, <tp:member-ref>RequestPresence</tp:member-ref> may
+ only succeed on contacts on your 'subscribe' list, and other contacts will
+ cause a PermissionDenied error. On protocols where there is no 'subscribe'
+ list, and RequestPresence succeeds, a client may poll the server
+ intermittently to update any display of presence information.</p>
+ </tp:docstring>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Privacy.xml b/qt4/spec/Connection_Interface_Privacy.xml
new file mode 100644
index 000000000..b89d968f4
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Privacy.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Privacy" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Privacy"
+ tp:causes-havoc='not well-tested'>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <method name="GetPrivacyMode" tp:name-for-bindings="Get_Privacy_Mode">
+ <arg direction="out" type="s">
+ <tp:docstring>
+ A string representing the current privacy mode
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Return the current privacy mode, which must be one of the values
+ returned by GetPrivacyModes.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+ <method name="GetPrivacyModes" tp:name-for-bindings="Get_Privacy_Modes">
+ <arg direction="out" type="as">
+ <tp:docstring>
+ An array of valid privacy modes for this connection
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Returns the privacy modes available on this connection. The following
+ well-known names should be used where appropriate:
+ <dl>
+ <dt>allow-all</dt><dd>any contact may initiate communication</dd>
+ <dt>allow-specified</dt><dd>only contacts on your 'allow' list may initiate communication</dd>
+ <dt>allow-subscribed</dt><dd>only contacts on your subscription list may initiate communication</dd>
+ </dl>
+ </tp:docstring>
+ </method>
+ <signal name="PrivacyModeChanged"
+ tp:name-for-bindings="Privacy_Mode_Changed">
+ <arg name="Mode" type="s">
+ <tp:docstring>
+ The current privacy mode
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the privacy mode is changed or the value has been
+ initially received from the server.
+ </tp:docstring>
+ </signal>
+ <method name="SetPrivacyMode" tp:name-for-bindings="Set_Privacy_Mode">
+ <arg direction="in" name="Mode" type="s">
+ <tp:docstring>
+ The desired privacy mode
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that the privacy mode be changed to the given value, which
+ must be one of the values returned by GetPrivacyModes. Success is
+ indicated by the method returning and the PrivacyModeChanged
+ signal being emitted.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ </tp:possible-errors>
+ </method>
+ <tp:docstring>
+ An interface to support getting and setting privacy modes to configure
+ situations such as not being contactable by people who are not on your
+ subscribe list. If this interface is not implemented, the default can be
+ presumed to be allow-all (as defined in GetPrivacyModes).
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Renaming.xml b/qt4/spec/Connection_Interface_Renaming.xml
new file mode 100644
index 000000000..d08b748d9
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Renaming.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Renaming" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Renaming"
+ tp:causes-havoc='not well-tested'>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <signal name="Renamed" tp:name-for-bindings="Renamed">
+ <arg name="Original" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the original identifier
+ </tp:docstring>
+ </arg>
+ <arg name="New" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The handle of the new identifier
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the unique identifier of a contact on the server
+ changes.</p>
+
+ <p>Any channels associated with the contact's original handle will
+ continue to be to that handle, and so are no longer useful (unless
+ the contact renames back, or another contact connects with that
+ unique ID). Clients may open a similar channel associated with the
+ new handle to continue communicating with the contact.</p>
+
+ <p>For example, if a GUI client associates text
+ channels with chat windows, it should detach the old channel
+ from the chat window, closing it, and associate a channel to the
+ new handle with the same window.</p>
+
+ <p>If the contact's old handle is in any of the member lists of
+ a channel which has the groups interface, it will be removed from
+ the channel and the new handle will be added. The resulting
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.Group">MembersChanged</tp:dbus-ref>
+ signal must be emitted <em>after</em> the
+ <tp:member-ref>Renamed</tp:member-ref> signal; the reason should be
+ RENAMED.
+ </p>
+
+ <p>The handles may be either general-purpose or channel-specific.
+ If the original handle is general-purpose, the new handle must be
+ general-purpose; if the original handle is channel-specific, the
+ new handle must be channel-specific in the same channel.
+ </p>
+ </tp:docstring>
+ </signal>
+ <method name="RequestRename" tp:name-for-bindings="Request_Rename">
+ <arg direction="in" name="Identifier" type="s">
+ <tp:docstring>
+ The desired identifier
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the user's own identifier is changed on the server.
+ If successful, a <tp:member-ref>Renamed</tp:member-ref> signal will
+ be emitted for the current "self handle" as returned by <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">GetSelfHandle</tp:dbus-ref>.</p>
+ <p>It is protocol-dependent how the identifier that's actually
+ used will be derived from the supplied identifier; some sort of
+ normalization might take place.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+ <tp:docstring>
+ An interface on connections to support protocols where the unique
+ identifiers of contacts can change. Because handles are immutable,
+ this is represented by a pair of handles, that representing the
+ old name, and that representing the new one.
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Requests.xml b/qt4/spec/Connection_Interface_Requests.xml
new file mode 100644
index 000000000..2f233fa53
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Requests.xml
@@ -0,0 +1,628 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Requests"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ >
+ <tp:copyright>Copyright (C) 2008 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright (C) 2008 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Requests">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An enhanced version of the Telepathy connection interface, which can
+ represent bundles of channels that should be dispatched together, and
+ does not assume any particular properties by which channels are
+ uniquely identifiable.</p>
+
+ <p>If this interface is implemented on a connection, then
+ <tp:member-ref>NewChannels</tp:member-ref> MUST be emitted for
+ all new channels, even those created with <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection"
+ >RequestChannel</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <tp:struct name="Channel_Details" array-name="Channel_Details_List">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+
+ <tp:docstring>
+ Enough details of a channel that clients can work out how to dispatch
+ or handle it.
+ </tp:docstring>
+
+ <tp:member name="Channel" type="o">
+ <tp:docstring>
+ The object path of the channel.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Properties of the channel.</p>
+
+ <p>Connection managers MUST NOT include properties in this mapping
+ if their values can change. Clients MUST ignore properties
+ that appear in this mapping if their values can change.</p>
+
+ <tp:rationale>
+ <p>If properties that could change were included, the following
+ race condition would be likely to exist in some cases:</p>
+
+ <ul>
+ <li>NewChannels or Get("Channels") includes a property P with
+ value V1</li>
+ <li>Client creates a proxy object for the channel</li>
+ <li>The value of P changes to V2</li>
+ <li>Client connects to PChanged signal</li>
+ <li>Client should call Get("P") or GetAll here, to avoid the
+ race, but client's author has forgotten to do so</li>
+ <li>Proxy object thinks P == V1, but actually P == V2</li>
+ </ul>
+
+ <p>We've taken the opportunity to make the API encourage the
+ client author to get it right. Where possible, we intend that
+ properties whose value will be used in channel dispatching
+ or other "early" processing will be defined so that they are
+ immutable (can never change).</p>
+ </tp:rationale>
+
+ <p>Each dictionary MUST contain the keys
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.ChannelType</tp:dbus-ref>,
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.TargetHandleType</tp:dbus-ref>,
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.TargetHandle</tp:dbus-ref>,
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.TargetID</tp:dbus-ref>
+ and
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.Requested</tp:dbus-ref>.
+ </p>
+
+ <tp:rationale>
+ <p>We expect these to be crucial to the channel-dispatching
+ process.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <method name="CreateChannel" tp:name-for-bindings="Create_Channel">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:changed version="0.17.14">It is now guaranteed that
+ CreateChannel returns the channel before NewChannels announces it
+ (the reverse was previously guaranteed).</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that an entirely new channel is created.</p>
+
+ <tp:rationale>
+ <p>There is deliberately no flag corresponding to the
+ suppress_handler argument to
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.RequestChannel</tp:dbus-ref>,
+ because passing a FALSE value for that argument is deprecated.
+ Requests made using this interface always behave as though
+ suppress_handler was TRUE.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+
+ <arg direction="in" name="Request" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties, which MUST include
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref>.
+ Some properties
+ are defined such that only an exact match makes sense, and
+ connection managers MUST NOT satisfy a request with a channel
+ where that property does not match; some properties are defined
+ such that the connection manager MAY treat the request as merely
+ a hint, and make a best-effort attempt to satisfy it. This is
+ documented separately for each property.</p>
+
+ <p>If this dictionary contains a property whose semantics
+ are not known to the connection manager, this method MUST fail
+ without side-effects (in particular it must not create a new
+ channel).</p>
+
+ <tp:rationale>
+ <p>This is necessary if we want to be able to invent properties
+ in future that, when used in a request, are hard requirements
+ rather than just hints. A connection manager that did not know
+ the semantics of those properties could incorrectly return a
+ new channel that did not satisfy the requirements.</p>
+ </tp:rationale>
+
+ <p>The connection manager MUST NOT respond successfully,
+ and SHOULD NOT create a new channel or cause any other
+ side-effects, unless it can create a new channel that satisfies
+ the client's requirements.</p>
+
+ <p>Properties that will be set by this argument need not have write
+ access after the channel has been created - indeed, it is
+ expected that most will be read-only.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channel" direction="out" type="o">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The Channel object, which MUST NOT be signalled with
+ <tp:member-ref>NewChannels</tp:member-ref> until after this method
+ returns.</p>
+
+ <tp:rationale>
+ <p>This allows the requester to alter its handling of
+ NewChannels by knowing whether one of the channels satisfied
+ a request it made.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Properties" direction="out" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Properties of the channel that was produced, equivalent to
+ the properties in <tp:type>Channel_Details</tp:type>.
+ Connection managers MUST NOT include properties here whose
+ values can change, for the same reasons as in
+ <tp:type>Channel_Details</tp:type>.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The channel request was one that can never succeed,
+ such as requesting an unsupported channel type, or requesting
+ a channel type which this connection manager does not support with
+ the given target handle type.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ An invalid handle was requested as the value of a property whose
+ value is a handle (like
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.TargetHandle</tp:dbus-ref>),
+ or a syntactically invalid identifier was requested as the value
+ of a property whose value is the string corresponding to a handle
+ (like <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.TargetID</tp:dbus-ref>).
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The request matched the fixed properties of a
+ <tp:type>Requestable_Channel_Class</tp:type> in
+ <tp:member-ref>RequestableChannelClasses</tp:member-ref>, but the
+ allowed arguments did not make sense; for example, a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">RoomList</tp:dbus-ref>
+ was requested, but the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.RoomList">Server</tp:dbus-ref>
+ property provided was not a valid DNS name.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable">
+ <tp:docstring>
+ The requested channel cannot be created because the requested
+ contact is using a client that lacks a particular feature.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.Offline">
+ <tp:docstring>
+ The requested channel cannot be created because the target is
+ offline.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The requested channel cannot be created, but in
+ principle, a similar request might succeed in future.
+ For instance, this might be because:</p>
+
+ <ul>
+ <li>a channel matching the request already exists and the
+ protocol requires that only one such channel can exist at a
+ time</li>
+ <li>a channel matching the request has already been requested
+ (by a previous call to CreateChannel,
+ <tp:member-ref>EnsureChannel</tp:member-ref>,
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.RequestChannel</tp:dbus-ref>
+ or similar) and the protocol requires that only one such
+ channel can exist at a time</li>
+ </ul>
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Banned"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Full"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.InviteOnly"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="EnsureChannel" tp:name-for-bindings="Ensure_Channel">
+ <tp:added version="0.17.12"/>
+ <tp:changed version="0.17.14">It is now guaranteed that if
+ the channel was created by this call to EnsureChannel, it's returned
+ before NewChannels announces it (the reverse was previously
+ guaranteed).</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that channels are ensured to exist.</p>
+
+ <tp:rationale>
+ <p>The connection manager is in the best position to determine which
+ existing channels could satisfy which requests.</p>
+ </tp:rationale>
+
+ </tp:docstring>
+
+ <arg direction="in" name="Request" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary containing desirable properties, with the same
+ semantics as the corresponding parameter to
+ <tp:member-ref>CreateChannel</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Yours" direction="out" type="b">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>If false, the caller of EnsureChannel MUST assume that some
+ other process is handling this channel; if true, the caller of
+ EnsureChannel SHOULD handle it themselves or delegate it to another
+ client.</p>
+
+ <p>If the creation of a channel makes several calls to EnsureChannel
+ (and no other requests) successful, exactly one of those calls MUST
+ return a true value for this argument.</p>
+
+ <p>If the creation of a channel makes other requests successful,
+ the value returned for this argument MUST be such that exactly
+ one of the clients making requests ends up responsible for the
+ channel. In particular, if
+ <tp:member-ref>CreateChannel</tp:member-ref> returns a channel
+ <em>C</em>, any EnsureChannel calls that also return <em>C</em>
+ MUST return a false value for this argument.</p>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Channel" direction="out" type="o">
+ <tp:docstring>
+ The Channel object. If it was created as a result of this method
+ call, it MUST NOT be signalled by
+ <tp:member-ref>NewChannels</tp:member-ref> until after this method
+ returns.
+
+ <tp:rationale>
+ <p>This allows the requester to alter its handling of
+ NewChannels by knowing whether one of the channels satisfied
+ a request it made.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <arg name="Properties" direction="out" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Properties of the channel that was produced, equivalent to
+ the properties in <tp:type>Channel_Details</tp:type>.
+ Connection managers MUST NOT include properties here whose
+ values can change, for the same reasons as in
+ <tp:type>Channel_Details</tp:type>.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The channel request was one that can never succeed,
+ such as requesting an unsupported channel type, or requesting
+ a channel type which this connection manager does not support with
+ the given target handle type.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle">
+ <tp:docstring>
+ An invalid handle was requested as the value of a property whose
+ value is a handle (like
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.TargetHandle</tp:dbus-ref>),
+ or a syntactically invalid identifier was requested as the value
+ of a property whose value is the string corresponding to a handle
+ (like <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Channel.TargetID</tp:dbus-ref>).
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The request matched the fixed properties of a
+ <tp:type>Requestable_Channel_Class</tp:type> in
+ <tp:member-ref>RequestableChannelClasses</tp:member-ref>, but the
+ allowed arguments did not make sense; for example, a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">RoomList</tp:dbus-ref>
+ was requested, but the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.RoomList">Server</tp:dbus-ref>
+ property provided was not a valid DNS name.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotCapable">
+ <tp:docstring>
+ The requested channel cannot be created because the requested
+ contact is using a client that lacks a particular feature.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.Offline">
+ <tp:docstring>
+ The requested channel cannot be created because the target is
+ offline.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The requested channel cannot be created, but in
+ principle, a similar request might succeed in future.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Banned"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.Full"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.Channel.InviteOnly"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ </tp:possible-errors>
+ </method>
+
+ <signal name="NewChannels" tp:name-for-bindings="New_Channels">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:changed version="0.17.14">Added a guarantee of ordering
+ relative to NewChannel</tp:changed>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>New channels have been created. The connection manager SHOULD emit
+ a single signal for any group of closely related channels that are
+ created at the same time, so that the channel dispatcher can try to
+ dispatch them to a handler as a unit.</p>
+
+ <p>In particular, if additional channels are created as a side-effect
+ of a call to <tp:member-ref>CreateChannel</tp:member-ref>,
+ these channels SHOULD appear in the same NewChannels signal as
+ the channel that satisfies the request.</p>
+
+ <tp:rationale>
+ <p>Joining a MUC Tube in XMPP requires joining the corresponding
+ MUC (chatroom), so a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type">Text</tp:dbus-ref>
+ channel can be created as a side-effect.</p>
+ </tp:rationale>
+
+ <p>Every time NewChannels is emitted, it MUST be followed by
+ a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection.NewChannel</tp:dbus-ref>
+ signal for each channel.</p>
+
+ <tp:rationale>
+ <p>The double signal emission is for the benefit of older Telepathy
+ clients, which won't be listening for NewChannels.</p>
+
+ <p>The more informative NewChannels signal comes first so that
+ clients that did not examine the connection to find
+ out whether Requests is supported will see the more informative
+ signal for each channel first, and then ignore the less
+ informative signal because it announces a new channel of which
+ they are already aware.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Channels" type="a(oa{sv})" tp:type="Channel_Details[]">
+ <tp:docstring>
+ The channels and their details. All channels that are signalled
+ together like this MUST have the same
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.FUTURE">Bundle</tp:dbus-ref>
+ property, which may
+ either refer to an existing bundle, or establish a new bundle.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <property name="Channels" tp:name-for-bindings="Channels"
+ type="a(oa{sv})" access="read" tp:type="Channel_Details[]">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:docstring>
+ A list of all the channels which currently exist on this connection.
+ Change notification is via the
+ <tp:member-ref>NewChannels</tp:member-ref> and
+ <tp:member-ref>ChannelClosed</tp:member-ref> signals.
+ </tp:docstring>
+ </property>
+
+ <signal name="ChannelClosed" tp:name-for-bindings="Channel_Closed">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:docstring>
+ Emitted when a channel is closed and hence disappears from the
+ <tp:member-ref>Channels</tp:member-ref> property.
+
+ <tp:rationale>
+ This is redundant with the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel">Closed</tp:dbus-ref>
+ signal on the channel itself, but it does provide full change
+ notification for the Channels property.
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg name="Removed" type="o">
+ <tp:docstring>
+ The channel which has been removed from the Channels property
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:mapping name="Channel_Class" array-name="Channel_Class_List">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Mapping representing a class of channels that can be requested
+ from a connection manager, can be handled by a user interface,
+ are supported by a contact, etc.</p>
+
+ <p>Classes of channel are identified by the fixed values of
+ a subset of their properties.</p>
+
+ <p>Channel classes SHOULD always include the keys
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.ChannelType</tp:dbus-ref>
+ and
+ <tp:dbus-ref>org.freedesktop.Telepathy.Channel.TargetHandleType</tp:dbus-ref>.
+ </p>
+ </tp:docstring>
+
+ <tp:member type="s" name="Key" tp:type="DBus_Qualified_Member">
+ <tp:docstring>
+ A D-Bus interface name, followed by a dot and a D-Bus property name.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="v" name="Value">
+ <tp:docstring>
+ The value of the property.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Requestable_Channel_Class"
+ array-name="Requestable_Channel_Class_List">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Structure representing a class of channels that can be requested,
+ identified by a set of properties that identify that class of
+ channel.</p>
+
+ <tp:rationale>
+ <p>This will often just be the channel type and the handle type,
+ but can include other properties of the channel - for instance,
+ encrypted channels might require properties that
+ unencrypted channels do not, like an encryption key.</p>
+ </tp:rationale>
+
+ <p>In some cases, these classes of channel may overlap, in the sense
+ that one class fixes all the properties that another class does,
+ plus some more properties.</p>
+
+ <tp:rationale>
+ <p>For older clients to still be able to understand how to request
+ channels in the presence of a hypothetical "encryption" interface,
+ we'd need to represent it like this:</p>
+
+ <ul>
+ <li>class 1: ChannelType = Text, TargetHandleType = CONTACT</li>
+ <li>class 2: Channel.ChannelType = Text,
+ Channel.TargetHandleType = CONTACT,
+ Encryption.Encrypted = TRUE</li>
+ </ul>
+ </tp:rationale>
+ </tp:docstring>
+
+ <tp:member name="Fixed_Properties" type="a{sv}"
+ tp:type="Channel_Class">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The property values that identify this requestable channel class.
+ These properties MUST be included in requests for a channel of this
+ class, and MUST take these values.</p>
+
+ <p>Clients that do not understand the semantics of all the
+ Fixed_Properties MUST NOT request channels of this class, since
+ they would be unable to avoid making an incorrect request.</p>
+
+ <p>This implies that connection managers wishing to make channels
+ available to old or minimal clients SHOULD have a channel class
+ with the minimum number of Fixed_Properties, and MAY additionally
+ have channel classes with extra Fixed_Properties.</p>
+
+ <p>Interface designers SHOULD avoid introducing fixed properties
+ whose types are not serializable in a <code>.manager</code>
+ file.</p>
+
+ <tp:rationale>
+ <p>Connection managers with a fixed property that is not
+ serializable cannot have a complete <code>.manager</code>
+ file.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Allowed_Properties" type="as"
+ tp:type="DBus_Qualified_Member[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Properties that MAY be set when requesting a channel of this
+ channel type and handle type.</p>
+
+ <p>This array MUST NOT include properties that are in the
+ Fixed_Properties mapping.</p>
+
+ <p>Properties in this array may either be required or optional,
+ according to their documented semantics.</p>
+
+ <tp:rationale>
+ <p>For instance, if
+ TargetHandleType takes a value that is not Handle_Type_None,
+ one or the other of TargetHandle and TargetID is required.
+ Clients are expected to understand the documented relationship
+ between the properties, so we do not have separate arrays
+ of required and optional properties.</p>
+ </tp:rationale>
+
+ <p>If this array contains the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.FUTURE">Bundle</tp:dbus-ref>
+ property, then this class of channel can be combined with other
+ channels with that property in a request, or added to an existing
+ bundle. If not, this signifies that the connection manager is
+ unable to mark channels of this class as part of a bundle - this
+ means that to the remote contact they are likely to be
+ indistinguishable from channels requested separately.</p>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="RequestableChannelClasses" access="read"
+ type="a(a{sv}as)" tp:type="Requestable_Channel_Class[]"
+ tp:name-for-bindings="Requestable_Channel_Classes">
+ <tp:added version="0.17.11">(as stable API)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The classes of channel that are expected to be available on this
+ connection, i.e. those for which
+ <tp:member-ref>CreateChannel</tp:member-ref> can reasonably
+ be expected to succeed. User interfaces can use this information
+ to show or hide UI components.</p>
+
+ <p>This property cannot change after the connection has gone to
+ state Connection_Status_Connected, so there is no change
+ notification (if the connection has context-dependent capabilities,
+ it SHOULD advertise support for all classes of channel that it might
+ support during its lifetime). Before this state has been reached,
+ the value of this property is undefined.</p>
+
+ <tp:rationale>
+ <p>This is not on an optional interface, because connection
+ managers can always offer some sort of clue about the channel
+ classes they expect to support (at worst, they can announce
+ support for everything for which they have code).</p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Resources.xml b/qt4/spec/Connection_Interface_Resources.xml
new file mode 100644
index 000000000..716089cd6
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Resources.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Resources"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.Resources.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.1">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface on connections to show contact attributes for
+ specific resources of a contact, if the protocol supports
+ multiple resources. Resources are most common in XMPP, hence the
+ name of this interface, but they are also present in MSN, where
+ they are called points of presence.</p>
+
+ <p>When a client requests some attribute of a contact using its
+ handle on the connection, the CM uses an algorithm to choose the
+ most appropriate resource for the job. If there is only one
+ resource, then the choice is obvious. If, however, there is more
+ than one resource connected at any one time, the CM either
+ aggregates all appropriate information to return (in the case of
+ capabilities), or chooses one specific resource (in the case of
+ presence).</p>
+
+ <p>Resources in XMPP have names, and it can be extremely useful
+ for the user to be able to know which resources of a contact are
+ online, providing the names are human-readable. Before now,
+ resources have not been exposed in Telepathy, but this interface
+ attempts to change this.</p>
+
+ <p>When using this interface, it is a little like using the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface"
+ >Contacts</tp:dbus-ref> interface, but only resource-specific
+ attributes are ever returned. The resource-specific contact
+ attributes are decided on by the CM, but XMPP's are listed
+ below:</p>
+
+ <ul>
+ <li><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">SimplePresence/presence</tp:dbus-ref></li>
+ <li><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">ContactCapabilities/capabilities</tp:dbus-ref></li>
+ <li><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">ClientTypes/client-types</tp:dbus-ref></li>
+ </ul>
+
+ </tp:docstring>
+
+ <method name="GetResources" tp:name-for-bindings="Get_Resources">
+ <tp:docstring>
+ Return the resource information of the given contacts. If any
+ of the contact attributes for specific resources of the given
+ contacts' are not known return immediately without waiting for
+ a reply.
+ </tp:docstring>
+
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ The contacts whose resource attributes should be returned.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Resources" type="a{ua{sa{sv}}}"
+ tp:type="Resources_Attributes_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The contacts' resources and the contact attributes specific
+ to each resource. If contact attributes are not immediately
+ known, the behaviour is defined by the interface; the
+ attribute should either be omitted from the result or
+ replaced with a default value.</p>
+
+ <p>For every contact handle passed into this method, it is
+ guaranteed that there will be a key in the returned map
+ that corresponds to said handle. If there is no information
+ regarding the contact the resource information map will be
+ empty.</p>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ </tp:possible-errors>
+ </method>
+
+ <tp:enum name="Resources_Human_Readability" type="u">
+ <tp:enumvalue suffix="Never" value="0">
+ <tp:docstring>
+ The resource string is never human-readable.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Maybe" value="1">
+ <tp:docstring>
+ The resource string might be human-readable.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <property name="ResourcesHumanReadable" type="u" access="read"
+ tp:type="Resources_Human_Readability"
+ tp:name-for-bindings="Resources_Human_Readable">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Whether the resources returned from <tp:member-ref>GetResources</tp:member-ref>
+ are human readable or not.</p>
+
+ <p>If the connection manager knows that all resource names are
+ automatically generated, then the resource strings mean
+ nothing to the user. Showing these strings in the UI would
+ be confusing, so by setting this to
+ Resources_Human_Readability_Never, the UI is advised not to
+ show resources.</p>
+
+ <p>If on the other hand, all resources are set to nice names
+ (such as "office" or "home") then it might be wise to expose
+ these strings in the UI, so this property would be set to
+ Resources_Human_Readability_Maybe. This is the case in XMPP --
+ most resources are set in a way that the user can deduce some
+ information from them. The absence of an Always enum value is
+ because in the case of XMPP, the resource string could be
+ partially human-readable (as on Google Talk, where a resource
+ of "home" is changed by the server to a unique string like
+ "home_1234fdec") or not at all human-readable.</p>
+
+ </tp:docstring>
+ </property>
+
+ <signal name="ResourcesUpdated" tp:name-for-bindings="Resources_Updated">
+ <tp:docstring>
+ Emitted when a contact has a resource added or removed, or any
+ contact attribute for any resource changes.
+ </tp:docstring>
+
+ <arg name="Contact" type="u" tp:type="Contact_Handle">
+ <tp:docstring>
+ The contact.
+ </tp:docstring>
+ </arg>
+ <arg name="Resources" tp:type="Resource_Information_Map"
+ type="a{sa{sv}}">
+ <tp:docstring>
+ The contact's resource information. All resource information
+ is given, not just the details which have changed.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:mapping name="Resource_Information_Map">
+ <tp:docstring>
+ A map of a contact's resources to their resource-specific
+ information.
+ </tp:docstring>
+
+ <tp:member name="Key" type="s">
+ <tp:docstring>
+ <p>The name of the resource.</p>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member name="Contact_Attributes" type="a{sv}"
+ tp:type="Single_Contact_Attributes_Map">
+ <tp:docstring>
+ A map of contact attributes whose data is specific to this
+ resource.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:mapping name="Resources_Attributes_Map">
+ <tp:docstring>Mapping returned by
+ <tp:member-ref>GetResources</tp:member-ref>, representing a
+ collection of Contacts, their resources, and their
+ resource-specific contact attributes.</tp:docstring>
+
+ <tp:member type="u" tp:type="Contact_Handle" name="Contact">
+ <tp:docstring>
+ A contact.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="a{sa{sv}}" tp:type="Resource_Information_Map"
+ name="Resources">
+ <tp:docstring>
+ A map of the contact's resources to their resource-specific
+ information.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:contact-attribute name="resources" type="a{sa{sv}}"
+ tp:type="Resource_Information_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same mapping that would be returned by
+ <tp:member-ref>GetResources</tp:member-ref> for this contact.</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Service_Point.xml b/qt4/spec/Connection_Interface_Service_Point.xml
new file mode 100644
index 000000000..b135c04c7
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Service_Point.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Service_Point" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright © 2005-2010 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright © 2005-2010 Collabora Ltd </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.ServicePoint">
+ <tp:added version="0.19.7">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for connections whose channels may be able to indicate
+ specific they are connected to some form
+ of service station. For example, when
+ dialing 9-1-1 in the US, a GSM modem/network will recognize that as
+ an emergency call, and inform higher levels of the stack that the
+ call is being handled by an emergency service. In this example,
+ the call is handled by a Public Safety Answering Point (PSAP) which is labeled
+ as "urn:service:sos". Other networks and protocols may handle this
+ differently while still using this interface.</p>
+ </tp:docstring>
+
+ <tp:struct name="Service_Point_Info" array-name="Service_Point_Info_List">
+ <tp:member type="(us)" tp:type="Service_Point" name="Service_Point">
+ <tp:docstring>
+ The service point.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="as" name="Service_IDs">
+ <tp:docstring>
+ A list of IDs that are mapped to this service. This is provided as
+ a convenience for the UIs, but the preferred method for
+ requesting channel to a service is by setting the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Interface.ServicePoint">InitialServicePoint</tp:dbus-ref>
+ property in a channel request.
+ </tp:docstring>
+ </tp:member>
+ <tp:docstring>
+ <p>Description of a service point and IDs which are mapped to it.</p>
+
+ <p>An example Service Point info for GSM emergency calls (callable
+ through "911" and "112") could look like:</p>
+
+<pre>
+ ServicePointInfo = (
+ Service_Point: (
+ Service_Point_Type: 1 (Emergency),
+ Service_Point: "urn:service:sos"
+ ),
+ Service_IDs: [ "911", "112" ]
+ )
+</pre>
+ </tp:docstring>
+ </tp:struct>
+
+ <property name="KnownServicePoints" tp:name-for-bindings="Known_Service_Points"
+ type="a((us)as)" tp:type="Service_Point_Info[]" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ The list of all (known) service points.
+ </tp:docstring>
+ </property>
+
+ <signal name="ServicePointsChanged" tp:name-for-bindings="Service_Points_Changed">
+ <arg name="Service_Points" type="a((us)as)" tp:type="Service_Point_Info[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The new value of
+ <tp:member-ref>KnownServicePoints</tp:member-ref>.</p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when the list of known service points (or their IDs) has
+ changed.
+ </tp:docstring>
+ </signal>
+
+ <tp:struct name="Service_Point">
+ <tp:docstring>A service point.</tp:docstring>
+ <tp:member type="u" name="Service_Point_Type"
+ tp:type="Service_Point_Type">
+ <tp:docstring>
+ The service type.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Service">
+ <tp:docstring>
+ String representation of the service point. The representation is
+ service specific; it may be a 'service' Uniform Resource Name as
+ specified by <a
+ href="http://www.rfc-editor.org/rfc/rfc5031.txt">RFC 5031</a>,
+ or may be in some other form. Empty, unused or unknown value is
+ represented by "".
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:enum name="Service_Point_Type" type="u">
+ <tp:docstring>
+ The various types of service points a channel might connect to.
+ </tp:docstring>
+
+ <tp:enumvalue value="0" suffix="None">
+ <tp:docstring>
+ The channel is not communicating with a service point, or it is not
+ known whether it is communicating with a service point (e.g. an
+ ordinary call).
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="1" suffix="Emergency">
+ <tp:docstring>
+ The service point is a generic emergency point.
+ </tp:docstring>
+ </tp:enumvalue>
+
+ <tp:enumvalue value="2" suffix="Counseling">
+ <tp:docstring>
+ The service point is some kind of counseling service (ie, mental health
+ or child-services counseling).
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Interface_Simple_Presence.xml b/qt4/spec/Connection_Interface_Simple_Presence.xml
new file mode 100644
index 000000000..7788161e1
--- /dev/null
+++ b/qt4/spec/Connection_Interface_Simple_Presence.xml
@@ -0,0 +1,634 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Simple_Presence" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005-2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or (at
+ your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public License
+ along with this library; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Connection.Interface.SimplePresence">
+ <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+ <tp:struct name="Simple_Presence">
+ <tp:docstring>
+ A struct representing the presence of a contact.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Connection_Presence_Type" name="Type">
+ <tp:docstring>
+ The presence type, e.g. Connection_Presence_Type_Away.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Status">
+ <tp:docstring>
+ The string identifier of the status, e.g. "brb", as defined in the
+ <tp:member-ref>Statuses</tp:member-ref> property.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Status_Message">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The user-defined status message, e.g. "Back soon!".</p>
+
+ <p>Clients SHOULD set the status message for the local
+ user to the empty string, unless the user has actually provided
+ a specific message (i.e. one that conveys more information than the
+ Status).</p>
+
+ <p>User interfaces SHOULD regard an empty status message as unset,
+ and MAY replace it with a localized string corresponding to the
+ Status or Type.</p>
+
+ <tp:rationale>
+ Use case: Daf sets his status in Empathy by choosing the Welsh
+ translation of "Available" from a menu.
+ It is more informative for his English-speaking colleagues
+ to see the English translation of "Available" (as localized
+ by their own clients) than to see "Ar Gael" (which they don't
+ understand anyway).
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Simple_Contact_Presences">
+ <tp:docstring>
+ Mapping returned by <tp:member-ref>GetPresences</tp:member-ref>
+ and signalled by <tp:member-ref>PresencesChanged</tp:member-ref>,
+ indicating the presence of a number of contacts.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Contact_Handle" name="Contact">
+ <tp:docstring>
+ A contact
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="(uss)" tp:type="Simple_Presence" name="Presence">
+ <tp:docstring>
+ The contact's presence
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:struct name="Simple_Status_Spec">
+ <tp:docstring>
+ A struct containing information about a status.
+ </tp:docstring>
+ <tp:member type="u" tp:type="Connection_Presence_Type" name="Type">
+ <tp:docstring>
+ The type of a presence. This SHOULD NOT be used as a way to set
+ statuses that the client does not recognise (as explained in
+ <tp:member-ref>SetPresence</tp:member-ref>), but MAY be used to check
+ that the client's assumptions about a particular status name
+ match the connection manager's.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="b" name="May_Set_On_Self">
+ <tp:docstring>
+ If true, the user can set this status on themselves using
+ <tp:member-ref>SetPresence</tp:member-ref>.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="b" name="Can_Have_Message">
+ <tp:docstring>
+ If true, a non-empty message can be set for this status. Otherwise,
+ the empty string is the only acceptable message.
+
+ <tp:rationale>
+ On IRC you can be Away with a status message, but if you are
+ available you cannot set a status message.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:mapping name="Simple_Status_Spec_Map">
+ <tp:docstring>
+ A mapping describing possible statuses.
+ </tp:docstring>
+
+ <tp:member type="s" name="Identifier">
+ <tp:docstring>
+ The string identifier of this status.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="(ubb)" tp:type="Simple_Status_Spec" name="Spec">
+ <tp:docstring>
+ Details of this status.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <method name="SetPresence" tp:name-for-bindings="Set_Presence">
+ <arg direction="in" name="Status" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The string identifier of the desired status. Possible status
+ identifiers are defined in the
+ <tp:member-ref>Statuses</tp:member-ref> property.</p>
+
+ <p>Clients MUST NOT set a status whose string value they do not
+ recognise, even if its presence type in Statuses
+ matches what the user requested.</p>
+
+ <tp:rationale>
+ <p>Suppose a protocol has statuses that include 'phone' (of type
+ BUSY) and 'in-a-meeting' (of type BUSY), but there is no
+ generic 'busy' status.</p>
+
+ <p>If the user requests "Busy" status from a menu, a
+ client author might be tempted to pick an arbitrary status
+ that has type BUSY. However, on this protocol, neither of
+ the choices would be appropriate, and incorrect information
+ about the user would be conveyed.</p>
+ </tp:rationale>
+
+ <p>Statuses whose <tp:type>Connection_Presence_Type</tp:type>
+ is Offline, Error or Unknown MUST NOT be passed to this
+ function. Connection managers SHOULD reject these statuses.</p>
+
+ <tp:rationale>
+ <p>To go offline, call <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection">Disconnect</tp:dbus-ref>
+ instead. The "error" and "unknown" statuses make no sense.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Status_Message" type="s">
+ <tp:docstring>
+ The status message associated with the current status.
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request that the presence status and status message are published for
+ the connection. Changes will be indicated by
+ <tp:member-ref>PresencesChanged</tp:member-ref>
+ signals being emitted.</p>
+
+ <p>This method may be called on a newly-created connection while it
+ is still in the DISCONNECTED state, to request that when the
+ connection connects, it will do so with the selected status.</p>
+
+ <p>In DISCONNECTED state the
+ <tp:member-ref>Statuses</tp:member-ref>
+ property will indicate which statuses are allowed to be set
+ while DISCONNECTED (none, if the Connection Manager doesn't allow
+ this). This value MUST NOT be cached, as the set of allowed
+ presences might change upon connecting.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ Either the specified status is not supported, the specified
+ status cannot be set on the user themselves, or a non-empty
+ message was supplied for a status that does not
+ accept a message.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <method name="GetPresences" tp:name-for-bindings="Get_Presences">
+ <arg direction="in" name="Contacts" type="au" tp:type="Contact_Handle[]">
+ <tp:docstring>
+ An array of the contacts whose presence should be obtained.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" name="Presence" type="a{u(uss)}"
+ tp:type="Simple_Contact_Presences">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Presence information in the same format as for the
+ <tp:member-ref>PresencesChanged</tp:member-ref> signal.
+ The returned mapping MUST include an entry for each contact
+ in the method's argument.</p>
+
+ <p>The definition of the connection presence types Unknown
+ and Offline means that if a connection manager will return
+ Unknown for contacts not on the subscribe list, it MUST delay
+ the reply to this method call until it has found out which
+ contacts are, in fact, on the subscribe list.</p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get presence previously emitted by
+ <tp:member-ref>PresencesChanged</tp:member-ref> for the given
+ contacts. Data is returned in the same structure as the
+ PresencesChanged signal; no additional network requests are made.
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError">
+ <tp:docstring>
+ While discovering the subscribe list in order to distinguish
+ between Unknown and Offline statuses, a network error occurred.
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ </tp:possible-errors>
+ </method>
+
+ <property name="Statuses" tp:name-for-bindings="Statuses" access="read"
+ type="a{s(ubb)}" tp:type="Simple_Status_Spec_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A dictionary where the keys are the presence statuses that are
+ available on this connection, and the values are the corresponding
+ presence types.</p>
+
+ <p>While the connection is in the DISCONNECTED state, it contains
+ the set of presence statuses allowed to be set before connecting.
+ The connection manager will attempt to set the appropriate status
+ when the connection becomes connected, but cannot necessarily
+ guarantee it. The available statuses cannot change until the
+ connection status changes, so there is no change notification.</p>
+
+ <p>While the connection is in the CONNECTED state, this property
+ contains the set of presence statuses which are actually available
+ on this protocol. This set is constant for the remaining lifetime
+ of the connection, so again, there is no change notification.</p>
+
+ <p>While the connection is in the CONNECTING state, the value of
+ this property is undefined and SHOULD NOT be used. It can change
+ at any time without notification (in particular, any cached values
+ from when the connection was in the DISCONNECTED or CONNECTING
+ state MUST NOT be assumed to still be correct when the state has
+ become CONNECTED).</p>
+
+ <p>This property MUST include the special statuses "unknown" and
+ "error" if and only if the connection manager can emit them
+ as a contact's status.</p>
+
+ <tp:rationale>
+ For instance, connection managers for local-xmpp (XEP-0174) would
+ omit "unknown" since there is no such concept.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="PresencesChanged" tp:name-for-bindings="Presences_Changed">
+ <arg name="Presence" type="a{u(uss)}" tp:type="Simple_Contact_Presences">
+ <tp:docstring>
+ A dictionary of contact handles mapped to the status,
+ presence type and status message.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ This signal should be emitted when your own presence has been changed,
+ or the presence of the member of any of the connection's channels has
+ been changed.
+ </tp:docstring>
+ </signal>
+
+ <tp:enum name="Connection_Presence_Type" type="u">
+ <tp:enumvalue suffix="Unset" value="0">
+ <tp:docstring>
+ An invalid presence type used as a null value. This value MUST NOT
+ appear in the <tp:member-ref>Statuses</tp:member-ref> property,
+ or in the result of <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Presence">GetStatuses</tp:dbus-ref>
+ on the deprecated <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Presence</tp:dbus-ref>
+ interface.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Offline" value="1">
+ <tp:docstring>
+ Offline
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Available" value="2">
+ <tp:docstring>
+ Available
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Away" value="3">
+ <tp:docstring>
+ Away
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Extended_Away" value="4">
+ <tp:docstring>
+ Away for an extended time
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Hidden" value="5">
+ <tp:docstring>
+ Hidden (invisible)
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Busy" value="6">
+ <tp:added version="0.17.0"/>
+ <tp:docstring>
+ Busy, Do Not Disturb.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Unknown" value="7">
+ <tp:added version="0.17.8"/>
+ <tp:docstring>
+ Unknown, unable to determine presence for this contact, for example
+ if the protocol only allows presence of subscribed contacts.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Error" value="8">
+ <tp:added version="0.17.8"/>
+ <tp:docstring>
+ Error, an error occurred while trying to determine presence. The
+ message, if set, is an error from the server.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Access_Control_Type" type="u"
+ array-name="Access_Control_Type_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A type for communication access control. These control
+ policies are used in
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">CommunicationPolicy.DRAFT</tp:dbus-ref>
+ as well as most rich presence interfaces.</p>
+
+ <p>New interfaces should use this type, and NOT
+ <tp:type>Rich_Presence_Access_Control_Type</tp:type>.</p>
+ </tp:docstring>
+ <tp:enumvalue suffix="Whitelist" value="0">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Only allow contacts that are in a certain whitelist.</p>
+
+ <p>The associated variant
+ in <tp:type>Access_Control</tp:type> is a list of
+ <tp:type>Contact_Handle</tp:type> representing
+ the whitelist, with signature <code>au</code>.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Publish_List" value="1">
+ <tp:docstring>
+ Allow contacts in the user's 'publish' list. The associated
+ variant in <tp:type>Access_Control</tp:type> is ignored.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Group" value="2">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Only allow contacts that are in a certain group.</p>
+
+ <p>The associated variant in <tp:type>Access_Control</tp:type> is a
+ <tp:type>Group_Handle</tp:type> representing the permitted
+ group.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Open" value="3">
+ <tp:docstring>
+ Allow all contacts. The associated
+ variant in <tp:type>Access_Control</tp:type> is ignored.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Subscribe_Or_Publish_List" value="4">
+ <tp:docstring>
+ Allow all contacts in the user's 'subscribe' or 'publish'
+ list. The associated variant in <tp:type>Access_Control</tp:type> is
+ ignored.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Closed" value="5">
+ <tp:docstring>
+ Forbid all contacts. The associated variant in
+ <tp:type>Access_Control</tp:type> is ignored.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Not_Understood" value="6">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The access control rule is too complex to be represented
+ in the current Telepathy API. The associated variant is
+ meaningless. Setting this mode is never valid; the
+ connection manager MUST raise an error if this is attempted.</p>
+
+ <tp:rationale>
+ XEP-0016 Privacy Lists can easily produce access control
+ mechanisms that can't be expressed in a simpler API. We
+ need to be able to at least indicate that fact.
+ </tp:rationale>
+
+ <p>The associated variant in <tp:type>Access_Control</tp:type> is
+ ignored.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:enum name="Rich_Presence_Access_Control_Type" type="u"
+ array-name="Rich_Presence_Access_Control_Type_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A type of access control for Rich_Presence_Access_Control.
+ For most types, the exact access control is given by an associated
+ variant.</p>
+
+ <tp:rationale>
+ <p>These are the access control types from XMPP publish/subscribe
+ (XEP-0060).</p>
+ </tp:rationale>
+
+ <p><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">Location</tp:dbus-ref>
+ uses this for historical reasons, new interfaces will use
+ <tp:type>Access_Control_Type</tp:type>.</p>
+ </tp:docstring>
+
+ <tp:enumvalue suffix="Whitelist" value="0">
+ <tp:docstring>
+ The associated variant is a list of contacts (signature 'au',
+ Contact_Handle[]) who can see the extended presence information.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Publish_List" value="1">
+ <tp:docstring>
+ All contacts in the user's 'publish' contact list can see the
+ extended presence information. The associated variant is ignored.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Group" value="2">
+ <tp:docstring>
+ The associated variant is a handle of type Group (signature 'u',
+ Group_Handle) representing a group of contacts who can see the
+ extended presence information.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Open" value="3">
+ <tp:docstring>
+ Anyone with access to the service can see the extended presence
+ information.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Access_Control">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An access control mode for extended presence items like geolocation.
+ This type isn't actually used by the SimplePresence interface, but
+ it's included here so it can be referenced by rich presence
+ interfaces.</p>
+
+ <p>New interfaces should use this type, and NOT
+ <tp:type>Rich_Presence_Access_Control</tp:type>.</p>
+ </tp:docstring>
+
+ <tp:member name="Type" type="u" tp:type="Access_Control_Type">
+ <tp:docstring>
+ The type of access control to apply.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Detail" type="v">
+ <tp:docstring>
+ Any additional information required by the Type. The required
+ type and semantics are defined for each
+ <tp:type>Access_Control_Type</tp:type>.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Rich_Presence_Access_Control">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An access control mode for extended presence items like geolocation.
+ This type isn't actually used by the SimplePresence interface, but
+ it's included here so it can be referenced by rich presence interfaces
+ such as <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Location</tp:dbus-ref>.</p>
+
+ <p><tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">Location</tp:dbus-ref>
+ uses this for historical reasons, new interfaces will use
+ <tp:type>Access_Control_Type</tp:type>.</p>
+ </tp:docstring>
+
+ <tp:member name="Type" type="u" tp:type="Rich_Presence_Access_Control_Type">
+ <tp:docstring>
+ The type of access control to apply.
+ </tp:docstring>
+ </tp:member>
+ <tp:member name="Detail" type="v">
+ <tp:docstring>
+ Any additional information required by the Type. The required
+ type and semantics are defined for each
+ <tp:type>Rich_Presence_Access_Control_Type</tp:type>.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:contact-attribute name="presence"
+ type="(uss)" tp:type="Simple_Presence">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The same struct that would be returned by
+ <tp:member-ref>GetPresences</tp:member-ref>
+ (always present with some value if information from the
+ SimplePresence interface was requested)</p>
+ </tp:docstring>
+ </tp:contact-attribute>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This interface is for services which have a concept of presence which
+ can be published for yourself and monitored on your contacts.</p>
+
+ <p>Presence on an individual (yourself or one of your contacts) is
+ modelled as a status and a status message. Valid statuses are defined
+ per connection, and a list of those that can be set on youself
+ can be obtained from the
+ <tp:member-ref>Statuses</tp:member-ref>
+ property.</p>
+
+ <p>Each status has an arbitrary string identifier which should have an
+ agreed meaning between the connection manager and any client which is
+ expected to make use of it. The following well-known values should be
+ used where possible to allow clients to identify common choices:</p>
+
+ <table>
+ <tr>
+ <th>status identifier</th>
+ <th>Connection_Presence_Type</th>
+ <th>comments</th>
+ </tr>
+ <tr>
+ <td>available</td>
+ <td>Connection_Presence_Type_Available</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>away</td>
+ <td>Connection_Presence_Type_Away</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>brb</td>
+ <td>Connection_Presence_Type_Away</td>
+ <td>Be Right Back (a more specific form of Away)</td>
+ </tr>
+ <tr>
+ <td>busy</td>
+ <td>Connection_Presence_Type_Busy</td>
+ <td></td>
+ </tr>
+ <tr><td>dnd</td>
+ <td>Connection_Presence_Type_Busy</td>
+ <td>Do Not Disturb (a more specific form of Busy)</td>
+ </tr>
+ <tr>
+ <td>xa</td>
+ <td>Connection_Presence_Type_Extended_Away</td>
+ <td>Extended Away</td>
+ </tr>
+ <tr>
+ <td>hidden</td>
+ <td>Connection_Presence_Type_Hidden</td>
+ <td>Also known as "Invisible" or "Appear Offline"</td>
+ </tr>
+ <tr>
+ <td>offline</td>
+ <td>Connection_Presence_Type_Offline</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>unknown</td>
+ <td>Connection_Presence_Type_Unknown</td>
+ <td>special, see below</td>
+ </tr>
+ <tr>
+ <td>error</td>
+ <td>Connection_Presence_Type_Error</td>
+ <td>special, see below</td>
+ </tr>
+ </table>
+
+ <p>As well as these well-known status identifiers, every status also has
+ a numerical type value chosen from
+ <tp:type>Connection_Presence_Type</tp:type> which can be
+ used by the client to classify even unknown statuses into different
+ fundamental types.</p>
+
+ <p>These numerical types exist so that even if a client does not
+ understand the string identifier being used, and hence cannot present
+ the presence to the user to set on themselves, it may display an
+ approximation of the presence if it is set on a contact.</p>
+
+ <p>As well as the normal status identifiers, there are two special ones
+ that may be present: 'unknown' with type Unknown and 'error' with type
+ Error. 'unknown' indicates that it is impossible to determine the
+ presence of a contact at this time, for example because it's not on the
+ 'subscribe' list and the protocol only allows one to determine the
+ presence of contacts you're subscribed to. 'error' indicates that there
+ was a failure in determining the status of a contact.</p>
+
+ <p>If the connection has a 'subscribe' contact list,
+ <tp:member-ref>PresencesChanged</tp:member-ref>
+ signals should be emitted to indicate changes of contacts on this list,
+ and should also be emitted for changes in your own presence. Depending
+ on the protocol, the signal may also be emitted for others such as
+ people with whom you are communicating, and any user interface should
+ be updated accordingly.</p>
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Manager.xml b/qt4/spec/Connection_Manager.xml
new file mode 100644
index 000000000..3683fcadf
--- /dev/null
+++ b/qt4/spec/Connection_Manager.xml
@@ -0,0 +1,619 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Manager" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2005-2008 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright (C) 2005-2008 Nokia Corporation</tp:copyright>
+ <tp:copyright>Copyright (C) 2006 INdT</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.ConnectionManager">
+
+ <tp:simple-type name="Connection_Manager_Name" type="s">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of a connection manager, found in its well-known
+ bus name and object path. This must be a non-empty string of
+ ASCII letters, digits and underscores, starting with a letter.
+ This is typically the name of the executable with any "telepathy-"
+ prefix removed, and any hyphen/minus signs replaced by
+ underscores.</p>
+
+ <p>Connection manager names SHOULD NOT be the same as the name of
+ the protocol they implement.</p>
+
+ <tp:rationale>
+ <p>This is likely to lead to conflicts between different
+ implementations of the same protocol (or indeed inability
+ to distinguish between the different implementations!). The
+ Telepathy project traditionally uses some sort of pun (Haze is
+ based on libpurple, Salut implements a protocol often called
+ Bonjour, and Wilde implements the OSCAR protocol).</p>
+ </tp:rationale>
+
+ <p>Connection manager names SHOULD NOT be the same as the name of
+ a library on which they are based.</p>
+
+ <tp:rationale>
+ <p>We often abbreviate, for instance, <i>telepathy-haze</i> as
+ “Haze”, but abbreviating <i>telepathy-sofiasip</i>—since renamed to
+ <i>telepathy-rakia</i> for exactly this reason—to “Sofia-SIP”
+ caused confusion between the connection manager and the library it
+ uses. Please don't repeat that mistake.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:changed version="0.17.1">Prior to version 0.17.1, the allowed
+ characters were not specified</tp:changed>
+ </tp:simple-type>
+
+ <tp:simple-type name="Protocol" type="s" array-name="Protocol_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An instant messaging protocol. It must consist only of ASCII
+ letters, digits and hyphen/minus signs (U+002D "-"), and must start
+ with a letter. Where possible, this SHOULD be
+ chosen from the following well-known values:</p>
+
+ <ul>
+ <li>aim - AOL Instant Messenger (OSCAR or TOC)</li>
+ <li>gadugadu - Gadu-Gadu</li>
+ <li>groupwise - Novell Groupwise</li>
+ <li>icq - ICQ (OSCAR)</li>
+ <li>irc - Internet Relay Chat (RFC 1459, 2810-2813)</li>
+ <li>jabber - XMPP (RFC 3920, 3921) or Jabber</li>
+ <li>local-xmpp - Link-local XMPP (XEP-0174) (Bonjour, Salut)</li>
+ <li>msn - MSNP (Windows Live Messenger)</li>
+ <li>myspace - MySpaceIM</li>
+ <li>mxit - MXit</li>
+ <li>napster - Napster</li>
+ <li>qq - Tencent QQ</li>
+ <li>sametime - IBM Lotus Sametime</li>
+ <li>silc - SILC</li>
+ <li>sip - Session Initiation Protocol (SIP), with or without
+ SIMPLE support</li>
+ <li>skype - Skype</li>
+ <li>tel - telephony (the
+ <abbr title="Public Switched Telephone Network">PSTN</abbr>,
+ including GSM, CDMA and fixed-line telephony)</li>
+ <li>trepia - Trepia</li>
+ <li>yahoo - YMSG (Yahoo! Messenger)</li>
+ <li>yahoojp - Japanese version of YMSG</li>
+ <li>zephyr - Zephyr</li>
+ </ul>
+ </tp:docstring>
+ <tp:changed version="0.17.1">Prior to version 0.17.1, the allowed
+ characters were not specified</tp:changed>
+ </tp:simple-type>
+
+ <tp:struct name="Param_Spec" array-name="Param_Spec_List">
+ <tp:docstring>A struct representing an allowed parameter, as returned
+ by GetParameters on the ConnectionManager interface.</tp:docstring>
+ <tp:member type="s" name="Name">
+ <tp:docstring>A string parameter name</tp:docstring>
+ </tp:member>
+ <tp:member type="u" tp:type="Conn_Mgr_Param_Flags" name="Flags">
+ <tp:docstring>A bitwise OR of the parameter flags</tp:docstring>
+ </tp:member>
+ <tp:member type="s" tp:type="DBus_Signature" name="Signature">
+ <tp:docstring>A string containing the D-Bus type signature
+ for this parameter</tp:docstring>
+ </tp:member>
+ <tp:member type="v" name="Default_Value">
+ <tp:docstring>The default value (if the Has_Default flag is not
+ present, there is no default and this takes some dummy value,
+ which SHOULD be of the appropriate D-Bus type)</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:flags name="Conn_Mgr_Param_Flags" value-prefix="Conn_Mgr_Param_Flag" type="u">
+ <tp:flag suffix="Required" value="1">
+ <tp:docstring>
+ This parameter is required for connecting to the server.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Register" value="2">
+ <tp:docstring>
+ This parameter is required for registering an account on the
+ server.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Has_Default" value="4">
+ <tp:docstring>
+ This parameter has a default value, which is returned in
+ GetParameters; not providing this parameter is equivalent to
+ providing the default.
+ </tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Secret" value="8">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This parameter should be considered private or secret; for
+ instance, clients should store it in a "password safe" like
+ gnome-keyring or kwallet, omit it from debug logs, and use a
+ text input widget that hides the value of the parameter.</p>
+
+ <p>(Clients that support older connection managers may also treat
+ any parameter whose name contains "password" as though it had this
+ flag.)</p>
+ </tp:docstring>
+ <tp:added version="0.17.2"/>
+ </tp:flag>
+ <tp:flag suffix="DBus_Property" value="16">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>This parameter is also a D-Bus property on the resulting
+ <tp:dbus-ref
+ namespace="ofdT">Connection</tp:dbus-ref>; a
+ parameter named <code>com.example.Duck.Macaroni</code> with this
+ flag corresponds to the <code>Macaroni</code> property on the
+ <code>com.example.Duck</code> interface. Its value can be queried
+ and possibly changed on an existing Connection using methods on the
+ <code>org.freedesktop.DBus.Properties</code> interface.</p>
+
+ <p>When a new value for a parameter with this flag is passed to
+ <tp:dbus-ref namespace="ofdT">Account.UpdateParameters</tp:dbus-ref>,
+ the account manager will attempt to update its value on any running
+ connections. Similarly, if the parameter also has the
+ <code>Has_Default</code> flag, and is passed in the second argument
+ to <code>UpdateParameters</code>, the default value will be applied
+ to any running
+ connections. Thus, clients generally do not need to directly access
+ or update the connection property; instead, they SHOULD manipulate
+ <tp:dbus-ref namespace="ofdT">Account.Parameters</tp:dbus-ref>.</p>
+
+ <tp:rationale>
+ <p>This allows runtime-configurable options to be stored and
+ maintained by the <tp:dbus-ref
+ namespace='ofdT'>AccountManager</tp:dbus-ref>, without needing to
+ invent a separate account preference for “properties that should
+ be set on the connection as soon as it is created”. It was
+ originally invented to manage <tp:dbus-ref
+ namespace='ofdT.Connection.Interface'>Cellular</tp:dbus-ref>
+ preferences.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:added version="0.17.16"/>
+ </tp:flag>
+ </tp:flags>
+
+ <method name="GetParameters" tp:name-for-bindings="Get_Parameters">
+ <arg direction="in" name="Protocol" type="s" tp:type="Protocol">
+ <tp:docstring>
+ The required protocol name
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="a(susv)" tp:type="Param_Spec[]"
+ name="Parameters">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ An array of structs representing possible parameters.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get a list of the parameters which must or may be provided to the
+ <tp:member-ref>RequestConnection</tp:member-ref> method when connecting
+ to the given protocol,
+ or registering (the boolean &quot;register&quot; parameter is available,
+ and set to true).
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The requested protocol is not supported by this manager
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:mapping name="Protocol_Properties_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from protocol identifiers supported by a connection
+ manager to the immutable properties of the corresponding
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Protocol</tp:dbus-ref> objects.</p>
+ </tp:docstring>
+
+ <tp:member name="Protocol" type="s" tp:type="Protocol">
+ <tp:docstring>A protocol name</tp:docstring>
+ </tp:member>
+
+ <tp:member name="Properties" type="a{sv}"
+ tp:type="Qualified_Property_Value_Map">
+ <tp:docstring>The immutable properties of the corresponding
+ Protocol object</tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <property name="Protocols" tp:name-for-bindings="Protocols"
+ access="read" type="a{sa{sv}}" tp:type="Protocol_Properties_Map">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A map from protocol identifiers supported by this connection
+ manager to the immutable properties of the corresponding
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Protocol</tp:dbus-ref> objects.</p>
+
+ <tp:rationale>
+ <p>Providing the immutable properties here means that
+ when the API of Protocol objects has been finalized,
+ most clients will only need one D-Bus round trip to interrogate
+ the ConnectionManager about all its protocols.</p>
+ </tp:rationale>
+
+ <p>If this map is empty or missing, clients SHOULD fall back to
+ calling <tp:member-ref>ListProtocols</tp:member-ref> and
+ <tp:member-ref>GetParameters</tp:member-ref>.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="ListProtocols" tp:name-for-bindings="List_Protocols">
+ <arg direction="out" type="as" tp:type="Protocol[]" name="Protocols">
+ <tp:docstring>
+ The keys of the <tp:member-ref>Protocols</tp:member-ref> map.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Get a list of protocol identifiers that are implemented by this
+ connection manager.
+ </tp:docstring>
+ </method>
+
+ <signal name="NewConnection" tp:name-for-bindings="New_Connection">
+ <arg name="Bus_Name" type="s" tp:type="DBus_Bus_Name">
+ <tp:docstring>
+ The D-Bus service where the connection object can be found
+ </tp:docstring>
+ </arg>
+ <arg name="Object_Path" type="o">
+ <tp:docstring>
+ The object path of the Connection object on this service
+ </tp:docstring>
+ </arg>
+ <arg name="Protocol" type="s" tp:type="Protocol">
+ <tp:docstring>
+ The identifier for the protocol this connection uses
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when a new <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref> object
+ is created.
+ </tp:docstring>
+ </signal>
+
+ <method name="RequestConnection" tp:name-for-bindings="Request_Connection">
+ <arg direction="in" name="Protocol" type="s" tp:type="Protocol">
+ <tp:docstring>
+ The protocol identifier
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Parameters" type="a{sv}"
+ tp:type="String_Variant_Map">
+ <tp:docstring>
+ A dictionary mapping parameter names to values of the appropriate
+ type, as indicated by <tp:member-ref>GetParameters</tp:member-ref>
+ and the well-known list of names and value types documented on the
+ <tp:type>Connection_Parameter_Name</tp:type> type.
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="s" tp:type="DBus_Bus_Name" name="Bus_Name">
+ <tp:docstring>
+ A D-Bus service name where the new Connection object can be found
+ </tp:docstring>
+ </arg>
+ <arg direction="out" type="o" name="Object_Path">
+ <tp:docstring>
+ The D-Bus object path to the Connection on this service
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Request a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>
+ object representing a given account on a given
+ protocol with the given parameters. The method returns the bus name
+ and the object path where the new Connection object can be found,
+ which should have the status of Connection_Status_Disconnected, to
+ allow signal handlers to be attached before connecting is started
+ with the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection">Connect</tp:dbus-ref>
+ method.</p>
+
+ <p>The parameters which must and may be provided in the parameters
+ dictionary can be discovered with the
+ <tp:member-ref>GetParameters</tp:member-ref> method. These
+ parameters, their types, and their default values may be cached
+ in files so that all available connection managers do not need to be
+ started to discover which protocols are available.</p>
+
+ <p>To request values for these parameters from the user, a client must
+ have prior knowledge of the meaning of the parameter names, so the
+ well-known names and types defined by the
+ <tp:type>Connection_Parameter_Name</tp:type> type should be used where
+ appropriate.</p>
+
+ <p>Connection manager authors SHOULD avoid introducing parameters
+ whose default values would not be serializable in a
+ <code>.manager</code> file.</p>
+
+ <tp:rationale>
+ <p>The same serialization format is used in Mission Control
+ to store accounts.</p>
+ </tp:rationale>
+
+ <p>Every successful RequestConnection call will cause the emission of a
+ <tp:member-ref>NewConnection</tp:member-ref> signal for the same newly
+ created connection. The
+ requester can use the returned object path and service name
+ independently of the emission of that signal. In that case this signal
+ emission is most useful for, e.g. other processes that are monitoring
+ the creation of new connections.</p>
+ </tp:docstring>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The requested protocol is not supported by this manager
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable">
+ <tp:docstring>
+ The requested connection already appears to exist
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ Unrecognised connection parameters
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <tp:simple-type name="Connection_Parameter_Name" type="s">
+ <tp:added version="0.21.2"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Well-known connection parameter names, along with their expected
+ type. Where possible, connection managers should use names and types
+ from this list in the <tp:dbus-ref
+ namespace='ofdT.Protocol'>Parameters</tp:dbus-ref> that may be passed
+ to <tp:member-ref>RequestConnection</tp:member-ref>.</p>
+
+ <dl>
+ <dt>account (s)</dt>
+ <dd>The identifier for the user's account on the server</dd>
+
+ <dt>server (s)</dt>
+ <dd>A fully qualified domain name or numeric IPv4 or IPv6 address.
+ Using the fully-qualified domain name form is recommended whenever
+ possible. If this parameter is specified and the account for that
+ protocol also specifies a server, this parameter should override
+ that in the user id.</dd>
+
+ <dt>port (q)</dt>
+ <dd>A TCP or UDP port number. If this parameter is specified and the
+ account for that protocol also specifies a port, this parameter
+ should override that in the account.</dd>
+
+ <dt>password (s)</dt>
+ <dd>A password associated with the account.</dd>
+
+ <dt>require-encryption (b)</dt>
+ <dd>Require encryption for this connection. A connection should fail
+ to connect if require-encryption is set and an encrypted connection
+ is not possible.</dd>
+
+ <dt>register (b)</dt>
+ <dd>This account should be created on the server if it does not
+ already exist.</dd>
+
+ <dt>ident (s)</dt>
+ <dd>The local username to report to the server if necessary, such as
+ in IRC.</dd>
+
+ <dt>fullname (s)</dt>
+ <dd>The user's full name if the service requires this when
+ authenticating or registering.</dd>
+
+ <dt>stun-server (s)</dt>
+ <dd>The IP address or FQDN of a STUN server to use for NAT traversal,
+ without any ":port" suffix.</dd>
+
+ <dt>stun-port (q)</dt>
+ <dd>The UDP port number on the stun-server to use for STUN. Only
+ significant if the stun-server is also supplied.</dd>
+
+ <dt>keepalive-interval (u)</dt>
+ <dd>
+ <p>The time in seconds between pings sent to the server to ensure
+ that the connection is still alive, or <tt>0</tt> to disable such
+ pings.</p>
+
+ <p>This parameter is superseded by the <tp:dbus-ref
+ namespace='ofdT.Connection.Interface.Keepalive.DRAFT'>KeepaliveInterval</tp:dbus-ref>
+ property, which can be updated on an already-established
+ connection as well as being specified when requesting the
+ connection. Clients SHOULD provide that parameter instead, if
+ allowed; new connection managers SHOULD implement it in
+ preference to this one.</p>
+ </dd>
+ </dl>
+
+ <p>The following well-known parameter names correspond to D-Bus
+ properties, and thus their <tp:type>Conn_Mgr_Param_Flags</tp:type>
+ should include DBus_Property. See that flag for more details on this
+ kind of parameter.</p>
+
+ <tp:list-dbus-property-parameters/>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ type="as" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of the extra interfaces provided by this connection manager
+ (i.e. extra functionality that can be provided even before a
+ connection has been created).</p>
+
+ <p>No interfaces suitable for listing in this property are currently
+ defined; it's provided as a hook for possible future
+ functionality.</p>
+
+ <p>To be compatible with older connection managers, if retrieving
+ this property fails, clients SHOULD assume that its value is
+ an empty list.</p>
+
+ <p>Connection managers with a non-empty list of Interfaces MUST
+ represent them in the <code>.manager</code> file, if they have one,
+ as an <code>Interfaces</code> key in the
+ group headed <code>[ConnectionManager]</code>, whose value is a list
+ of strings each followed by a semicolon.</p>
+ </tp:docstring>
+ <tp:added version="0.17.8"/>
+ </property>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A D-Bus service which allows connections to be created. The manager
+ processes are intended to be started by D-Bus service activation.</p>
+
+ <p>For service discovery, each Telepathy connection manager must have
+ a <em>connection manager name</em> (see
+ <tp:type>Connection_Manager_Name</tp:type> for syntax).</p>
+
+ <p>The connection manager must then provide a well-known bus name of
+ <code>org.freedesktop.Telepathy.ConnectionManager.<em>cmname</em></code>
+ where <em>cmname</em> is its connection manager name. If it makes sense
+ to start the connection manager using D-Bus service activation, it
+ must register that well-known name for service activation by installing
+ a .service file.</p>
+
+ <p>Clients can list the running connection managers by calling the
+ ListNames method on the D-Bus daemon's org.freedesktop.DBus interface
+ and looking for names matching the above pattern; they can list the
+ activatable connection managers by calling ListActivatableNames, and
+ they should usually combine the two lists to get a complete list of
+ running or activatable connection managers.</p>
+
+ <p>When the connection manager is running, it must have an object
+ implementing the ConnectionManager interface at the object path
+ <code>/org/freedesktop/Telepathy/ConnectionManager/<em>cmname</em></code>.
+ </p>
+
+ <p>Connection managers' capabilities can be determined dynamically by
+ calling their <tp:member-ref>ListProtocols</tp:member-ref> method, then
+ for each protocol of interest, calling
+ <tp:member-ref>GetParameters</tp:member-ref> to discover the required and
+ optional parameters.
+ However, since it is inefficient to activate all possible connection
+ managers on the system just to find out what they can do, there
+ is a standard mechanism to store static information about CMs in
+ ".manager files".</p>
+
+ <p>To look up a connection manager's supported protocols, clients
+ should search the data directories specified by
+ <a href="http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html">the
+ freedesktop.org XDG Base Directory Specification</a> ($XDG_DATA_HOME,
+ defaulting to $HOME/.local/share if unset, followed by
+ colon-separated paths from $XDG_DATA_DIRS, defaulting to
+ /usr/local/share:/usr/share if unset) for the first file named
+ <code>telepathy/managers/<em>cmname</em>.manager</code> that can be
+ read without error. This file has the same syntax as a
+ <a href="http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html">freedesktop.org Desktop Entry file</a>.</p>
+
+ <p>Clients must still support connection managers for which no
+ <code>.manager</code> file can be found, which they can do by activating
+ the connection manager and calling its methods; the
+ <code>.manager</code> file is merely an optimization. Connection managers
+ whose list of protocols can change at any time (for instance, via
+ a plugin architecture) should not install a <code>.manager</code>
+ file.</p>
+
+ <p>The <code>.manager</code> file SHOULD have a group headed
+ <code>[ConnectionManager]</code>, containing a key
+ <code>Interfaces</code> representing
+ <tp:member-ref>Interfaces</tp:member-ref> as a sequence of strings
+ each followed by a semicolon (the "localestrings" type from the Desktop
+ Entry Specification).</p>
+
+ <p>The <code>[ConnectionManager]</code> group SHOULD NOT contain keys
+ <code>ObjectPath</code> or <code>BusName</code>. If it does, they MUST
+ be ignored.</p>
+
+ <tp:rationale>
+ <p>The object path and bus name are derivable from the connection
+ manager's name, which is part of the filename, so these keys are
+ redundant. They were required in very old versions of Telepathy.</p>
+ </tp:rationale>
+
+ <p>For each protocol name <em>proto</em> that would be returned by
+ ListProtocols, the .manager file contains a group
+ headed <code>[Protocol <em>proto</em>]</code>. For each parameter
+ <em>p</em> that would be returned by GetParameters(<em>proto</em>), the
+ .manager file contains a key <code>param-<em>p</em></code> with a value
+ consisting of a D-Bus signature (a single complete type), optionally
+ followed by a space and a space-separated list of flags. The supported
+ flags are:</p>
+
+ <ul>
+ <li><code>required</code>, corresponding to
+ Conn_Mgr_Param_Flag_Required</li>
+ <li><code>register</code>, corresponding
+ to Conn_Mgr_Param_Flag_Register</li>
+ <li><code>secret</code>, corresponding
+ to Conn_Mgr_Param_Flag_Secret</li>
+ <li><code>dbus-property</code>, corresponding
+ to Conn_Mgr_Param_Flag_DBus_Property</li>
+ </ul>
+
+ <p>The group may also contain a key <code>default-<em>p</em></code>
+ whose value is a string form of the default value for the parameter.
+ If this key exists, it sets the default, and also sets the flag
+ Conn_Mgr_Param_Flag_Has_Default. The default value is formatted
+ according to the D-Bus signature as follows:</p>
+
+ <dl>
+ <dt>s (string)</dt>
+ <dd>The UTF-8 string, with the standard backslash escape
+ sequences supported by the Desktop Entry Specification
+ (the "localestring" type from the Desktop Entry Specification)</dd>
+ <dt>o (object path)</dt>
+ <dd>The object path as an ASCII string</dd>
+ <dt>b (boolean)</dt>
+ <dd>"true" (case-insensitively) or "1" means True, "false"
+ (case-insensitively) or "0" means False; when writing a file,
+ "true" and "false" SHOULD be used</dd>
+ <dt>y, q, u, t (8-, 16-, 32-, 64-bit unsigned integer)</dt>
+ <dd>ASCII decimal integer</dd>
+ <dt>n, i, x (16-, 32-, 64-bit signed integer)</dt>
+ <dd>ASCII decimal integer, optionally prefixed with "-"</dd>
+ <dt>d (double-precision floating point)</dt>
+ <dd>ASCII decimal number</dd>
+ <dt>as (array of string)</dt>
+ <dd>A sequence of UTF-8 strings each followed by a semicolon, with
+ any semicolons they contain escaped with a backslash
+ (the "localestrings" type from the Desktop Entry Specification)</dd>
+ </dl>
+
+ <p>Currently, no other D-Bus signatures are allowed to have default values,
+ but clients parsing the .manager file MUST ignore defaults
+ that they cannot parse, and treat them as if the
+ <code>default-<em>p</em></code> key was not present at all.</p>
+
+ <p>It is not required that a connection manager be able to support multiple
+ protocols, or even multiple connections. When a connection is made, a
+ service name where the connection object can be found is returned. A
+ manager which can only make one connection may then remove itself from its
+ well-known bus name, causing a new connection manager to be activated when
+ somebody attempts to make a new connection.</p>
+ </tp:docstring>
+
+ <tp:changed version="0.17.2">Prior to version 0.17.2, support for
+ CMs with no .manager file was not explicitly required.</tp:changed>
+ <tp:changed version="0.17.16">Prior to version 0.17.16 the serialization
+ of string arrays (signature 'as') was not defined</tp:changed>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Connection_Manager_Interface_Account_Storage.xml b/qt4/spec/Connection_Manager_Interface_Account_Storage.xml
new file mode 100644
index 000000000..2f4f4bf78
--- /dev/null
+++ b/qt4/spec/Connection_Manager_Interface_Account_Storage.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Manager_Interface_Account_Storage"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2011 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.ConnectionManager.Interface.AccountStorage.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.10">(draft 1)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.ConnectionManager"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for connection managers that store account details
+ internally. At the moment this consists only of storing an account's
+ credentials, but other functionality may be added in the future.</p>
+
+ <p><tp:dbus-ref namespace="ofdT">Account</tp:dbus-ref> objects
+ representing accounts on a connection manager that implements this
+ interface should implement the
+ <tp:dbus-ref namespace="ofdT.Account.Interface">ExternalPasswordStorage.DRAFT</tp:dbus-ref>
+ interface.</p>
+ </tp:docstring>
+
+ <tp:flags name="Account_Flags" value-prefix="Account_Flag" type="u">
+ <tp:docstring>
+ A set of flags representing the status of the Account stored in the
+ Connection Manager.
+ </tp:docstring>
+
+ <tp:flag suffix="Credentials_Stored" value="1">
+ <tp:docstring>
+ The associated account has its authentication credentials (password)
+ stored in the connection manager
+ </tp:docstring>
+ </tp:flag>
+ </tp:flags>
+
+ <tp:mapping name="Account_Flags_Map" array-name="Account_Flags_Map_List">
+ <tp:docstring>A mapping from Account_Ids to account flags.
+ </tp:docstring>
+ <tp:member type="s" name="Account_Id"/>
+ <tp:member type="u" tp:type="Account_Flags" name="Flags"/>
+ </tp:mapping>
+
+ <property name="Accounts"
+ tp:name-for-bindings="Accounts"
+ type="a{su}" tp:type="Account_Flags_Map" access="read">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The set of Accounts stored in this Connection Manager, and flags
+ indicating their status.</p>
+
+ <p>Change notification for this property is provided by the standard
+ D-Bus <code>PropertiesChanged</code> signal.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="ForgetCredentials" tp:name-for-bindings="Forget_Credentials">
+ <tp:docstring>
+ Clears any saved credentials associated with the specified Account_Id.
+ Any other saved data related to the account will be unaffected.
+ </tp:docstring>
+
+ <arg direction="in" name="Account_Id"
+ type="s">
+ <tp:docstring>
+ An account id as returned from
+ <tp:dbus-ref namespace="ofdT">Protocol.IdentifyAccount</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The account id is invalid.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="RemoveAccount" tp:name-for-bindings="Remove_Account">
+ <tp:docstring>
+ Completely removes all data associated with an account from the
+ connection manager's internal storage.
+ </tp:docstring>
+
+ <arg direction="in" name="Account_Id"
+ type="s">
+ <tp:docstring>
+ An account id as returned from
+ <tp:dbus-ref namespace="ofdT">Protocol.IdentifyAccount</tp:dbus-ref>.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The account id is invalid.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
diff --git a/qt4/spec/Debug.xml b/qt4/spec/Debug.xml
new file mode 100644
index 000000000..70a82e903
--- /dev/null
+++ b/qt4/spec/Debug.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" ?>
+<node name="/Debug"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright>Copyright (C) 2009 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Debug">
+ <tp:added version="0.17.27">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for providing debug messages.</p>
+
+ <p>This interface is primarily provided by one object per
+ service, at the path <tt>/org/freedesktop/Telepathy/debug</tt>.</p>
+ </tp:docstring>
+
+ <property name="Enabled" type="b" access="readwrite"
+ tp:name-for-bindings="Enabled">
+ <tp:docstring>
+ TRUE if the <tp:member-ref>NewDebugMessage</tp:member-ref> signal
+ should be emitted when a new debug message is generated.
+ </tp:docstring>
+ </property>
+
+ <method name="GetMessages" tp:name-for-bindings="Get_Messages">
+ <tp:docstring>
+ Retrieve buffered debug messages. An implementation could have a
+ limit on how many message it keeps and so the array returned from
+ this method should not be assumed to be all of the messages in
+ the lifetime of the service.
+ </tp:docstring>
+
+ <arg direction="out" name="Messages" type="a(dsus)"
+ tp:type="Debug_Message[]">
+ <tp:docstring>
+ A list of debug messages.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <signal name="NewDebugMessage" tp:name-for-bindings="New_Debug_Message">
+ <tp:docstring>
+ Emitted when a debug messages is generated if the
+ <tp:member-ref>Enabled</tp:member-ref> property is set to TRUE.
+ </tp:docstring>
+
+ <arg name="time" type="d">
+ <tp:docstring>
+ Timestamp of the debug message.
+ </tp:docstring>
+ </arg>
+ <arg name="domain" type="s">
+ <tp:docstring>
+ Domain of the debug message, as described in the Debug_Message struct.
+ </tp:docstring>
+ </arg>
+ <arg name="level" type="u" tp:type="Debug_Level">
+ <tp:docstring>
+ Level of the debug message.
+ </tp:docstring>
+ </arg>
+ <arg name="message" type="s">
+ <tp:docstring>
+ The text of the debug message.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <tp:enum name="Debug_Level" type="u">
+ <tp:enumvalue suffix="Error" value="0">
+ <tp:docstring>
+ Log level for errors. Error messages are always fatal, resulting
+ in the service terminating after something completely
+ unexpected occurred.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Critical" value="1">
+ <tp:docstring>
+ Log level for critical messages. Critical messages are messages
+ that the service might predict and it is up to the service itself
+ to decide whether to terminate following a critical message.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Warning" value="2">
+ <tp:docstring>
+ Log level for warnings.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Message" value="3">
+ <tp:docstring>
+ Log level for messages.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Info" value="4">
+ <tp:docstring>
+ Log level for information messages.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Debug" value="5">
+ <tp:docstring>
+ Log level for debug messages.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+
+ <tp:struct name="Debug_Message" array-name="Debug_Message_List">
+ <tp:docstring>
+ A struct representing a debug message, as returned by
+ <tp:member-ref>GetMessages</tp:member-ref>.
+ </tp:docstring>
+
+ <tp:member type="d" name="Timestamp">
+ <tp:docstring>
+ Timestamp of the debug message. This is a double to allow
+ more accuracy in the time the message was logged.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="s" name="Domain">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Domain of the debug message. This is used to identify
+ the source of debug messages. For example, debug messages
+ from a connection manager could have this Domain struct
+ member be the name of the connection manager, and logs
+ from any helper library could have the name of the helper
+ library.</p>
+
+ <p>The domain could also contain a category as to where
+ the log message originated separated by a forward-slash.
+ For example, if a debug message was output in a connection
+ manager called "dummy", in the file-transfer code, this
+ Domain struct member might be <tt>dummy/file-transfer</tt>.</p>
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="u" tp:type="Debug_Level" name="Level">
+ <tp:docstring>
+ Level of the debug message. This states the severity of the
+ debug message.
+ </tp:docstring>
+ </tp:member>
+
+ <tp:member type="s" name="Message">
+ <tp:docstring>
+ The text of the debug message.
+ </tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Media_Session_Handler.xml b/qt4/spec/Media_Session_Handler.xml
new file mode 100644
index 000000000..70aa75073
--- /dev/null
+++ b/qt4/spec/Media_Session_Handler.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" ?>
+<node name="/Media_Session_Handler" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005, 2006 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Media.SessionHandler">
+ <method name="Error" tp:name-for-bindings="Error">
+ <arg direction="in" name="Error_Code" type="u"
+ tp:type="Media_Stream_Error"/>
+ <arg direction="in" name="Message" type="s"/>
+ <tp:deprecated version="0.13.4">
+ Use <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media">StreamHandler.Error</tp:dbus-ref>
+ on each StreamHandler object instead.
+ </tp:deprecated>
+ <tp:docstring>
+ Informs the connection manager that an error occured in this session.
+ If used, the connection manager must terminate the session and all of
+ the streams within it, and may also emit a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Channel.Type.StreamedMedia">StreamError</tp:dbus-ref>
+ signal on the channel for each stream within the session.
+ </tp:docstring>
+ </method>
+ <signal name="NewStreamHandler" tp:name-for-bindings="New_Stream_Handler">
+ <arg name="Stream_Handler" type="o">
+ <tp:docstring>
+ The path of a new object implementing the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Media">StreamHandler</tp:dbus-ref>
+ interface.
+ </tp:docstring>
+ </arg>
+ <arg name="ID" type="u">
+ <tp:docstring>
+ The unique ID of the new stream
+ </tp:docstring>
+ </arg>
+ <arg name="Media_Type" type="u" tp:type="Media_Stream_Type">
+ <tp:docstring>
+ Type of media that this stream should handle
+ </tp:docstring>
+ </arg>
+ <arg name="Direction" type="u" tp:type="Media_Stream_Direction">
+ <tp:docstring>
+ Direction of this stream
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Emitted when a new stream handler has been created for this
+ session.
+ </tp:docstring>
+ </signal>
+ <method name="Ready" tp:name-for-bindings="Ready">
+ <tp:docstring>
+ Inform the connection manager that a client is ready to handle
+ this session handler (i.e. that it has connected to the
+ <tp:member-ref>NewStreamHandler</tp:member-ref> signal and done any
+ other necessary setup).
+ </tp:docstring>
+ </method>
+ <tp:docstring>
+ An media session handler is an object that handles a number of synchronised
+ media streams.
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Media_Stream_Handler.xml b/qt4/spec/Media_Stream_Handler.xml
new file mode 100644
index 000000000..123ea8be7
--- /dev/null
+++ b/qt4/spec/Media_Stream_Handler.xml
@@ -0,0 +1,725 @@
+<?xml version="1.0" ?>
+<node name="/Media_Stream_Handler" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005-2008 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005-2008 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Media.StreamHandler">
+
+ <tp:struct name="Media_Stream_Handler_Candidate"
+ array-name="Media_Stream_Handler_Candidate_List">
+ <tp:member type="s" name="Name"/>
+ <tp:member type="a(usuussduss)" name="Transports"
+ tp:type="Media_Stream_Handler_Transport[]"/>
+ </tp:struct>
+
+ <tp:struct name="Media_Stream_Handler_Transport"
+ array-name="Media_Stream_Handler_Transport_List">
+ <tp:member type="u" name="Component_Number"/>
+ <tp:member type="s" name="IP_Address"/>
+ <tp:member type="u" name="Port"/>
+ <tp:member type="u" tp:type="Media_Stream_Base_Proto" name="Protocol"/>
+ <tp:member type="s" name="Subtype"/>
+ <tp:member type="s" name="Profile"/>
+ <tp:member type="d" name="Preference_Value"/>
+ <tp:member type="u" tp:type="Media_Stream_Transport_Type"
+ name="Transport_Type"/>
+ <tp:member type="s" name="Username"/>
+ <tp:member type="s" name="Password"/>
+ </tp:struct>
+
+ <tp:struct name="Media_Stream_Handler_Codec"
+ array-name="Media_Stream_Handler_Codec_List">
+ <tp:docstring>
+ Information about a codec supported by a client or a peer's client.
+ </tp:docstring>
+
+ <tp:member type="u" name="Codec_ID">
+ <tp:docstring>
+ The codec's payload identifier, as per RFC 3551 (static or dynamic)
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="s" name="Name">
+ <tp:docstring>The codec's name</tp:docstring>
+ </tp:member>
+ <tp:member type="u" tp:type="Media_Stream_Type" name="Media_Type">
+ <tp:docstring>Type of stream this codec supports</tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Clock_Rate">
+ <tp:docstring>Sampling frequency in Hertz</tp:docstring>
+ </tp:member>
+ <tp:member type="u" name="Number_Of_Channels">
+ <tp:docstring>Number of supported channels</tp:docstring>
+ </tp:member>
+ <tp:member type="a{ss}" name="Parameters" tp:type="String_String_Map">
+ <tp:docstring>Codec-specific optional parameters</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <property name="STUNServers" tp:name-for-bindings="STUN_Servers"
+ type="a(sq)" tp:type="Socket_Address_IP[]" access="read">
+ <tp:added version="0.17.22"/>
+ <tp:docstring>
+ The IP addresses of possible STUN servers to use for NAT traversal, as
+ dotted-quad IPv4 address literals or RFC2373 IPv6 address literals.
+ This property cannot change once the stream has been created, so there
+ is no change notification. The IP addresses MUST NOT be given as DNS
+ hostnames.
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ to make life easier for streaming implementations.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="CreatedLocally" tp:name-for-bindings="Created_Locally"
+ type="b" access="read">
+ <tp:added version="0.17.22"/>
+ <tp:docstring>
+ True if we were the creator of this stream, false otherwise.
+ <tp:rationale>
+ This information is needed for some nat traversal mechanisms, such
+ as ICE-UDP, where the creator gets the role of the controlling agent.
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="NATTraversal" tp:name-for-bindings="NAT_Traversal"
+ type="s" access="read">
+ <tp:added version="0.17.22"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The transport (NAT traversal technique) to be used for this
+ stream. Well-known values include:</p>
+
+ <dl>
+ <dt>none</dt>
+ <dd>Raw UDP, with or without STUN, should be used. If the
+ <tp:member-ref>STUNServers</tp:member-ref> property is non-empty,
+ STUN SHOULD be used.</dd>
+
+ <dt>stun</dt>
+ <dd>A deprecated synonym for 'none'.</dd>
+
+ <dt>gtalk-p2p</dt>
+ <dd>Google Talk peer-to-peer connectivity establishment should be
+ used, as implemented in libjingle 0.3.</dd>
+
+ <dt>ice-udp</dt>
+ <dd>Interactive Connectivity Establishment should be used,
+ as defined by the IETF MMUSIC working group.</dd>
+
+ <dt>wlm-8.5</dt>
+ <dd>The transport used by Windows Live Messenger 8.5 or later,
+ which resembles ICE draft 6, should be used.</dd>
+
+ <dt>wlm-2009</dt>
+ <dd>The transport used by Windows Live Messenger 2009 or later,
+ which resembles ICE draft 19, should be used.</dd>
+ </dl>
+
+ <p>This property cannot change once the stream has been created, so
+ there is no change notification.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RelayInfo" type="aa{sv}" access="read"
+ tp:type="String_Variant_Map[]" tp:name-for-bindings="Relay_Info">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of mappings describing TURN or Google relay servers
+ available for the client to use in its candidate gathering, as
+ determined from the protocol. Map keys are:</p>
+
+ <dl>
+ <dt><code>ip</code> - s</dt>
+ <dd>The IP address of the relay server as a dotted-quad IPv4
+ address literal or an RFC2373 IPv6 address literal. This MUST NOT
+ be a DNS hostname.
+
+ <tp:rationale>
+ High-quality connection managers already need an asynchronous
+ DNS resolver, so they might as well resolve this name to an IP
+ and make life easier for streaming implementations.
+ </tp:rationale>
+ </dd>
+
+ <dt><code>type</code> - s</dt>
+ <dd>
+ <p>Either <code>udp</code> for UDP (UDP MUST be assumed if this
+ key is omitted), <code>tcp</code> for TCP, or
+ <code>tls</code>.</p>
+
+ <p>The precise meaning of this key depends on the
+ <tp:member-ref>NATTraversal</tp:member-ref> property: if
+ NATTraversal is <code>ice-udp</code>, <code>tls</code> means
+ TLS over TCP as referenced by ICE draft 19, and if
+ NATTraversal is <code>gtalk-p2p</code>, <code>tls</code> means
+ a fake SSL session over TCP as implemented by libjingle.</p>
+ </dd>
+
+ <dt><code>port</code> - q</dt>
+ <dd>The UDP or TCP port of the relay server as an ASCII unsigned
+ integer</dd>
+
+ <dt><code>username</code> - s</dt>
+ <dd>The username to use</dd>
+
+ <dt><code>password</code> - s</dt>
+ <dd>The password to use</dd>
+
+ <dt><code>component</code> - u</dt>
+ <dd>The component number to use this relay server for, as an
+ ASCII unsigned integer; if not included, this relay server
+ may be used for any or all components.
+
+ <tp:rationale>
+ In ICE draft 6, as used by Google Talk, credentials are only
+ valid once, so each component needs relaying separately.
+ </tp:rationale>
+ </dd>
+ </dl>
+
+ <tp:rationale>
+ <p>An equivalent of the gtalk-p2p-relay-token property on
+ MediaSignalling channels is not included here. The connection
+ manager should be responsible for making the necessary HTTP
+ requests to turn the token into a username and password.</p>
+ </tp:rationale>
+
+ <p>The type of relay server that this represents depends on
+ the value of the <tp:member-ref>NATTraversal</tp:member-ref>
+ property. If NATTraversal is ice-udp, this is a TURN server;
+ if NATTraversal is gtalk-p2p, this is a Google relay server;
+ otherwise, the meaning of RelayInfo is undefined.</p>
+
+ <p>If relaying is not possible for this stream, the list is empty.</p>
+
+ <p>This property cannot change once the stream has been created, so
+ there is no change notification.</p>
+ </tp:docstring>
+ </property>
+
+ <signal name="AddRemoteCandidate"
+ tp:name-for-bindings="Add_Remote_Candidate">
+ <arg name="Candidate_ID" type="s">
+ <tp:docstring>
+ String identifier for this candidate
+ </tp:docstring>
+ </arg>
+ <arg name="Transports" type="a(usuussduss)"
+ tp:type="Media_Stream_Handler_Transport[]">
+ <tp:docstring>
+ Array of transports for this candidate with fields,
+ as defined in NewNativeCandidate
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the connection manager wishes to inform the
+ client of a new remote candidate.
+ </tp:docstring>
+ </signal>
+ <signal name="Close" tp:name-for-bindings="Close">
+ <tp:docstring>
+ Signal emitted when the connection manager wishes the stream to be
+ closed.
+ </tp:docstring>
+ </signal>
+ <method name="CodecChoice" tp:name-for-bindings="Codec_Choice">
+ <arg direction="in" name="Codec_ID" type="u"/>
+ <tp:docstring>
+ Inform the connection manager of codec used to receive data.
+ </tp:docstring>
+ </method>
+ <method name="Error" tp:name-for-bindings="Error">
+ <arg direction="in" name="Error_Code" type="u" tp:type="Media_Stream_Error">
+ <tp:docstring>
+ ID of error, from the MediaStreamError enumeration
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Message" type="s">
+ <tp:docstring>
+ String describing the error
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Inform the connection manager that an error occured in this stream. The
+ connection manager should emit the StreamError signal for the stream on
+ the relevant channel, and remove the stream from the session.
+ </tp:docstring>
+ </method>
+ <tp:enum name="Media_Stream_Error" type="u">
+ <tp:enumvalue suffix="Unknown" value="0">
+ <tp:docstring>
+ An unknown error occured.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="EOS" value="1">
+ <tp:docstring>
+ The end of the stream was reached.
+ </tp:docstring>
+ <tp:deprecated version="0.17.27">
+ This error has no use anywhere. In Farsight 1 times, it was used to
+ indicate a GStreamer EOS (when the end of a file is reached). But
+ since this is for live calls, it makes no sense.
+ </tp:deprecated>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Codec_Negotiation_Failed" value="2">
+ <tp:added version="0.17.27"/>
+ <tp:docstring>
+ There are no common codecs between the local side
+ and the other particpants in the call. The possible codecs are not
+ signalled here: the streaming implementation is assumed to report
+ them in an implementation-dependent way, e.g. Farsight should use
+ GstMissingElement.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Connection_Failed" value="3">
+ <tp:added version="0.17.27"/>
+ <tp:docstring>
+ A network connection for the Media could not be established or was
+ lost.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Network_Error" value="4">
+ <tp:added version="0.17.27"/>
+ <tp:docstring>
+ There was an error in the networking stack
+ (other than the connection failure).
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="No_Codecs" value="5">
+ <tp:added version="0.17.27"/>
+ <tp:docstring>
+ There are no installed codecs for this media type.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Invalid_CM_Behavior" value="6">
+ <tp:added version="0.17.27"/>
+ <tp:docstring>
+ The CM is doing something wrong.
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Media_Error" value="7">
+ <tp:added version="0.17.27"/>
+ <tp:docstring>
+ There was an error in the media processing stack.
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ <method name="NativeCandidatesPrepared"
+ tp:name-for-bindings="Native_Candidates_Prepared">
+ <tp:docstring>
+ Informs the connection manager that all possible native candisates
+ have been discovered for the moment.
+ </tp:docstring>
+ </method>
+ <method name="NewActiveCandidatePair"
+ tp:name-for-bindings="New_Active_Candidate_Pair">
+ <arg direction="in" name="Native_Candidate_ID" type="s"/>
+ <arg direction="in" name="Remote_Candidate_ID" type="s"/>
+ <tp:docstring>
+ Informs the connection manager that a valid candidate pair
+ has been discovered and streaming is in progress.
+ </tp:docstring>
+ </method>
+ <method name="NewActiveTransportPair"
+ tp:name-for-bindings="New_Active_Transport_Pair">
+ <arg direction="in" name="Native_Candidate_ID" type="s"/>
+ <arg direction="in" name="Native_Transport" type="(usuussduss)"
+ tp:type="Media_Stream_Handler_Transport"/>
+ <arg direction="in" name="Remote_Candidate_ID" type="s"/>
+ <arg direction="in" name="Remote_Transport" type="(usuussduss)"
+ tp:type="Media_Stream_Handler_Transport"/>
+ <tp:docstring>
+ <p>Informs the connection manager that a valid transport pair
+ has been discovered and streaming is in progress. Component
+ id MUST be the same for both transports and the pair is
+ only valid for that component.</p>
+
+ <tp:rationale>
+ <p>The connection manager might need to send the details of
+ the active transport pair (e.g. c and o parameters of SDP
+ body need to contain address of selected native RTP transport
+ as stipulated by RFC 5245). However, the candidate ID might
+ not be enough to determine these info if the transport was
+ found after <tp:member-ref>NativeCandidatesPrepared</tp:member-ref>
+ has been called (e.g. peer reflexive ICE candidate). </p>
+ </tp:rationale>
+
+ <p>This method must be called before
+ <tp:member-ref>NewActiveCandidatePair</tp:member-ref>.</p>
+
+ <tp:rationale>
+ <p>This way, connection managers supporting this method can
+ safely ignore subsequent
+ <tp:member-ref>NewActiveCandidatePair</tp:member-ref> call.</p>
+ </tp:rationale>
+
+ <p>Connection managers SHOULD NOT implement this method unless
+ they need to inform the peer about selected transports. As a
+ result, streaming implementations MUST NOT treat errors raised
+ by this method as fatal.</p>
+
+ <tp:rationale>
+ <p>Usually, connection managers only need to do one answer/offer
+ round-trip. However, some protocols give the possibility to
+ to send an updated offer (e.g. ICE defines such mechanism to
+ avoid some race conditions and to properly set the state of
+ gateway devices).</p>
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+ <tp:enum name="Media_Stream_Base_Proto" type="u">
+ <tp:enumvalue suffix="UDP" value="0">
+ <tp:docstring>UDP (User Datagram Protocol)</tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="TCP" value="1">
+ <tp:docstring>TCP (Transmission Control Protocol)</tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ <method name="NewNativeCandidate"
+ tp:name-for-bindings="New_Native_Candidate">
+ <arg direction="in" name="Candidate_ID" type="s">
+ <tp:docstring>
+ String identifier for this candidate
+ </tp:docstring>
+ </arg>
+ <arg direction="in" name="Transports" type="a(usuussduss)"
+ tp:type="Media_Stream_Handler_Transport[]">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Array of transports for this candidate, with fields:
+ <ul>
+ <li>component number</li>
+ <li>IP address (as a string)</li>
+ <li>port</li>
+ <li>base network protocol (one of the values of MediaStreamBaseProto)</li>
+ <li>proto subtype (e.g. RTP)</li>
+ <li>proto profile (e.g. AVP)</li>
+ <li>our preference value of this transport (double in range 0.0-1.0
+ inclusive); 1 signals the most preferred transport</li>
+ <li>transport type, one of the values of MediaStreamTransportType</li>
+ <li>username if authentication is required</li>
+ <li>password if authentication is required</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Inform this MediaStreamHandler that a new native transport candidate
+ has been ascertained.
+ </tp:docstring>
+ </method>
+ <tp:enum name="Media_Stream_Transport_Type" type="u">
+ <tp:enumvalue suffix="Local" value="0">
+ <tp:docstring>
+ A local address
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Derived" value="1">
+ <tp:docstring>
+ An external address derived by a method such as STUN
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Relay" value="2">
+ <tp:docstring>
+ An external stream relay
+ </tp:docstring>
+ </tp:enumvalue>
+ </tp:enum>
+ <method name="Ready" tp:name-for-bindings="Ready">
+ <arg direction="in" name="Codecs" type="a(usuuua{ss})"
+ tp:type="Media_Stream_Handler_Codec[]">
+ <tp:docstring>
+ Locally-supported codecs.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Inform the connection manager that a client is ready to handle
+ this StreamHandler. Also provide it with info about all supported
+ codecs.
+ </tp:docstring>
+ </method>
+ <method name="SetLocalCodecs" tp:name-for-bindings="Set_Local_Codecs">
+ <arg name="Codecs" type="a(usuuua{ss})" direction="in"
+ tp:type="Media_Stream_Handler_Codec[]">
+ <tp:docstring>
+ Locally-supported codecs
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Used to provide codecs after Ready(), so the media client can go
+ ready for an incoming call and exchange candidates/codecs before
+ knowing what local codecs are available.</p>
+
+ <p>This is useful for gatewaying calls between two connection managers.
+ Given an incoming call, you need to call
+ <tp:member-ref>Ready</tp:member-ref> to get the remote codecs before
+ you can use them as the "local" codecs to place the outgoing call,
+ and hence receive the outgoing call's remote codecs to use as the
+ incoming call's "local" codecs.</p>
+
+ <p>In this situation, you would pass an empty list of codecs to the
+ incoming call's Ready method, then later call SetLocalCodecs on the
+ incoming call in order to respond to the offer.</p>
+ </tp:docstring>
+ </method>
+ <signal name="RemoveRemoteCandidate"
+ tp:name-for-bindings="Remove_Remote_Candidate">
+ <arg name="Candidate_ID" type="s">
+ <tp:docstring>
+ String identifier for remote candidate to drop
+ </tp:docstring>
+ </arg>
+ <tp:deprecated version="0.17.18">
+ There is no case where you want to release candidates (except
+ for an ICE reset, and there you'd want to replace then all,
+ using <tp:member-ref>SetRemoteCandidateList</tp:member-ref>).
+ </tp:deprecated>
+ <tp:docstring>
+ Signal emitted when the connection manager wishes to inform the
+ client that the remote end has removed a previously usable
+ candidate.
+
+ <tp:rationale>
+ It seemed like a good idea at the time, but wasn't.
+ </tp:rationale>
+ </tp:docstring>
+ </signal>
+ <signal name="SetActiveCandidatePair"
+ tp:name-for-bindings="Set_Active_Candidate_Pair">
+ <arg name="Native_Candidate_ID" type="s"/>
+ <arg name="Remote_Candidate_ID" type="s"/>
+ <tp:docstring>
+ Emitted by the connection manager to inform the client that a
+ valid candidate pair has been discovered by the remote end
+ and streaming is in progress.
+ </tp:docstring>
+ </signal>
+ <signal name="SetRemoteCandidateList"
+ tp:name-for-bindings="Set_Remote_Candidate_List">
+ <arg name="Remote_Candidates" type="a(sa(usuussduss))"
+ tp:type="Media_Stream_Handler_Candidate[]">
+ <tp:docstring>
+ A list of candidate id and a list of transports
+ as defined in NewNativeCandidate
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the connection manager wishes to inform the
+ client of all the available remote candidates at once.
+ </tp:docstring>
+ </signal>
+ <signal name="SetRemoteCodecs" tp:name-for-bindings="Set_Remote_Codecs">
+ <arg name="Codecs" type="a(usuuua{ss})"
+ tp:type="Media_Stream_Handler_Codec[]">
+ <tp:docstring>
+ Codecs supported by the remote peer.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Signal emitted when the connection manager wishes to inform the
+ client of the codecs supported by the remote end.
+ If these codecs are compatible with the remote codecs, then the client
+ must call <tp:member-ref>SupportedCodecs</tp:member-ref>,
+ otherwise call <tp:member-ref>Error</tp:member-ref>.
+ </tp:docstring>
+ </signal>
+ <signal name="SetStreamPlaying" tp:name-for-bindings="Set_Stream_Playing">
+ <arg name="Playing" type="b"/>
+ <tp:docstring>
+ If emitted with argument TRUE, this means that the connection manager
+ wishes to set the stream playing; this means that the streaming
+ implementation should expect to receive data. If emitted with argument
+ FALSE this signal is basically meaningless and should be ignored.
+
+ <tp:rationale>
+ We're very sorry.
+ </tp:rationale>
+ </tp:docstring>
+ </signal>
+ <signal name="SetStreamSending" tp:name-for-bindings="Set_Stream_Sending">
+ <arg name="Sending" type="b"/>
+ <tp:docstring>
+ Signal emitted when the connection manager wishes to set whether or not
+ the stream sends to the remote end.
+ </tp:docstring>
+ </signal>
+ <signal name="StartTelephonyEvent"
+ tp:name-for-bindings="Start_Telephony_Event">
+ <arg name="Event" type="y" tp:type="DTMF_Event">
+ <tp:docstring>
+ A telephony event code.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that a telephony event (as defined by RFC 4733) is transmitted
+ over this stream until StopTelephonyEvent is called.
+ </tp:docstring>
+ </signal>
+ <signal name="StartNamedTelephonyEvent"
+ tp:name-for-bindings="Start_Named_Telephony_Event">
+ <tp:added version="0.21.2"/>
+ <arg name="Event" type="y" tp:type="DTMF_Event">
+ <tp:docstring>
+ A telephony event code as defined by RFC 4733.
+ </tp:docstring>
+ </arg>
+ <arg name="Codec_ID" type="u">
+ <tp:docstring>
+ The payload type to use when sending events. The value 0xFFFFFFFF
+ means to send with the already configured event type instead of using
+ the specified one.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that a telephony event (as defined by RFC 4733) is transmitted
+ over this stream until StopTelephonyEvent is called. This differs from
+ StartTelephonyEvent in that you force the event to be transmitted
+ as a RFC 4733 named event, not as sound. You can also force a specific
+ Codec ID.
+ </tp:docstring>
+ </signal>
+ <signal name="StartSoundTelephonyEvent"
+ tp:name-for-bindings="Start_Sound_Telephony_Event">
+ <tp:added version="0.21.2"/>
+ <arg name="Event" type="y" tp:type="DTMF_Event">
+ <tp:docstring>
+ A telephony event code as defined by RFC 4733.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Request that a telephony event (as defined by RFC 4733) is transmitted
+ over this stream until StopTelephonyEvent is called. This differs from
+ StartTelephonyEvent in that you force the event to be transmitted
+ as sound instead of as a named event.
+ </tp:docstring>
+ </signal>
+ <signal name="StopTelephonyEvent"
+ tp:name-for-bindings="Stop_Telephony_Event">
+ <tp:docstring>
+ Request that any ongoing telephony events (as defined by RFC 4733)
+ being transmitted over this stream are stopped.
+ </tp:docstring>
+ </signal>
+ <method name="StreamState" tp:name-for-bindings="Stream_State">
+ <arg direction="in" name="State" type="u" tp:type="Media_Stream_State"/>
+ <tp:docstring>
+ Informs the connection manager of the stream's current state, as
+ as specified in Channel.Type.StreamedMedia::ListStreams.
+ </tp:docstring>
+ </method>
+
+ <method name="SupportedCodecs" tp:name-for-bindings="Supported_Codecs">
+ <arg direction="in" name="Codecs" type="a(usuuua{ss})"
+ tp:type="Media_Stream_Handler_Codec[]">
+ <tp:docstring>
+ Locally supported codecs.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Inform the connection manager of the supported codecs for this session.
+ This is called after the connection manager has emitted SetRemoteCodecs
+ to notify what codecs are supported by the peer, and will thus be an
+ intersection of all locally supported codecs (passed to Ready)
+ and those supported by the peer.
+ </tp:docstring>
+ </method>
+
+ <method name="CodecsUpdated" tp:name-for-bindings="Codecs_Updated">
+ <arg direction="in" name="Codecs" type="a(usuuua{ss})"
+ tp:type="Media_Stream_Handler_Codec[]">
+ <tp:docstring>
+ Locally supported codecs, which SHOULD be the same as were previously
+ in effect, but possibly with different parameters.
+ </tp:docstring>
+ </arg>
+ <tp:docstring>
+ Inform the connection manager that the parameters of the supported
+ codecs for this session have changed. The connection manager should
+ send the new parameters to the remote contact.
+
+ <tp:rationale>
+ This is required for H.264 and Theora, for example.
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <signal name="SetStreamHeld" tp:name-for-bindings="Set_Stream_Held">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Emitted when the connection manager wishes to place the stream on
+ hold (so the streaming client should free hardware or software
+ resources) or take the stream off hold (so the streaming client
+ should reacquire the necessary resources).</p>
+
+ <p>When placing a channel's streams on hold, the connection manager
+ SHOULD notify the remote contact that this will be done (if
+ appropriate in the protocol) before it emits this signal.</p>
+
+ <tp:rationale>
+ <p>It is assumed that relinquishing a resource will not fail.
+ If it does, the call is probably doomed anyway.</p>
+ </tp:rationale>
+
+ <p>When unholding a channel's streams, the connection manager
+ SHOULD emit this signal and wait for success to be indicated
+ via HoldState before it notifies the remote contact that the
+ channel has been taken off hold.</p>
+
+ <tp:rationale>
+ <p>This means that if a resource is unavailable, the remote
+ contact will never even be told that we tried to acquire it.</p>
+ </tp:rationale>
+ </tp:docstring>
+ <tp:added version="0.17.3"/>
+
+ <arg name="Held" type="b">
+ <tp:docstring>
+ If true, the stream is to be placed on hold.
+ </tp:docstring>
+ </arg>
+ </signal>
+
+ <method name="HoldState" tp:name-for-bindings="Hold_State">
+ <tp:docstring>
+ Notify the connection manager that the stream's hold state has
+ been changed successfully in response to SetStreamHeld.
+ </tp:docstring>
+ <tp:added version="0.17.3"/>
+ <arg direction="in" name="Held" type="b">
+ <tp:docstring>
+ If true, the stream is now on hold.
+ </tp:docstring>
+ </arg>
+ </method>
+
+ <method name="UnholdFailure" tp:name-for-bindings="Unhold_Failure">
+ <tp:docstring>
+ Notify the connection manager that an attempt to reacquire the
+ necessary hardware or software resources to unhold the stream,
+ in response to SetStreamHeld, has failed.
+ </tp:docstring>
+ <tp:added version="0.17.3"/>
+ </method>
+
+ <tp:docstring>
+ Handles signalling the information pertaining to a specific media stream.
+ A client should provide information to this handler as and when it is
+ available.
+ </tp:docstring>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Properties_Interface.xml b/qt4/spec/Properties_Interface.xml
new file mode 100644
index 000000000..bc9c8b260
--- /dev/null
+++ b/qt4/spec/Properties_Interface.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" ?>
+<node name="/Properties_Interface" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <tp:copyright> Copyright (C) 2005-2007 Collabora Limited </tp:copyright>
+ <tp:copyright> Copyright (C) 2005, 2006 Nokia Corporation </tp:copyright>
+ <tp:copyright> Copyright (C) 2006 INdT </tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+ <interface name="org.freedesktop.Telepathy.Properties">
+
+ <tp:struct name="Property_Spec" array-name="Property_Spec_List">
+ <tp:docstring>A struct (property ID, property name, D-Bus signature,
+ flags) representing a property, as returned by ListProperties on the
+ Properties interface.</tp:docstring>
+ <tp:member type="u" name="Property_ID"/>
+ <tp:member type="s" name="Name"/>
+ <tp:member type="s" tp:type="DBus_Signature" name="Signature"/>
+ <tp:member type="u" tp:type="Property_Flags" name="Flags"/>
+ </tp:struct>
+
+ <tp:struct name="Property_Flags_Change"
+ array-name="Property_Flags_Change_List">
+ <tp:docstring>A struct (property ID, flags) representing a change to
+ a property's flags, as seen in the PropertyFlagsChanged signal on
+ the Properties interface.</tp:docstring>
+ <tp:member type="u" name="Property_ID"/>
+ <tp:member type="u" name="New_Flags"/>
+ </tp:struct>
+
+ <tp:simple-type name="Property_ID" type="u" array-name="Property_ID_List">
+ <tp:docstring>
+ An unsigned integer used to represent a Telepathy property.
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:struct name="Property_Value" array-name="Property_Value_List">
+ <tp:docstring>A struct (property ID, value) representing a
+ property's value, as seen in the PropertiesChanged signal on
+ the Properties interface, returned by the GetProperties method
+ and passed to the SetProperties method.</tp:docstring>
+ <tp:member type="u" tp:type="Property_ID" name="Identifier"/>
+ <tp:member type="v" name="Value"/>
+ </tp:struct>
+
+ <method name="GetProperties" tp:name-for-bindings="Get_Properties">
+ <tp:docstring>
+ Returns an array of (identifier, value) pairs containing the current
+ values of the given properties.
+ </tp:docstring>
+ <arg direction="in" name="Properties" type="au" tp:type="Property_ID[]">
+ <tp:docstring>An array of property identifiers</tp:docstring>
+ </arg>
+ <arg direction="out" type="a(uv)" tp:type="Property_Value[]"
+ name="Values">
+ <!-- XXX: if we're ever breaking API compatibility, make this a{uv} -->
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of structs containing:</p>
+ <ul>
+ <li>integer identifiers</li>
+ <li>variant boxed values</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ Some property identifier requested is invalid
+ </tp:docstring>
+ </tp:error>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied">
+ <tp:docstring>
+ Some property requested does not have the PROPERTY_FLAG_READ flag
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+ <method name="ListProperties" tp:name-for-bindings="List_Properties">
+ <tp:docstring>
+ Returns a dictionary of the properties available on this channel.
+ </tp:docstring>
+ <arg direction="out" type="a(ussu)" tp:type="Property_Spec[]"
+ name="Available_Properties">
+ <!-- XXX: if we're ever breaking API compatibility, make this
+ a{u(ssu)} ? -->
+ <tp:docstring>
+ An array of structs containing:
+ <ul>
+ <li>an integer identifier</li>
+ <li>a string property name</li>
+ <li>a string representing the D-Bus signature of this property</li>
+ <li>a bitwise OR of the flags applicable to this property</li>
+ </ul>
+ </tp:docstring>
+ </arg>
+ </method>
+ <signal name="PropertiesChanged" tp:name-for-bindings="Properties_Changed">
+ <tp:docstring>
+ Emitted when the value of readable properties has changed.
+ </tp:docstring>
+ <arg name="Properties" type="a(uv)" tp:type="Property_Value[]">
+ <!-- XXX: if we're ever breaking API compatibility, make this a{uv} -->
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of structs containing:</p>
+ <ul>
+ <li>integer identifiers</li>
+ <li>variant boxed values</li>
+ </ul>
+ <p>The array should contain only properties whose values have
+ actually changed.</p>
+ </tp:docstring>
+ </arg>
+ </signal>
+ <signal name="PropertyFlagsChanged"
+ tp:name-for-bindings="Property_Flags_Changed">
+ <tp:docstring>
+ Emitted when the flags of some room properties have changed.
+ </tp:docstring>
+ <arg name="Properties" type="a(uu)" tp:type="Property_Flags_Change[]">
+ <!-- XXX: if we're ever breaking API compatibility, make this a{uu} -->
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An array of structs containing:</p>
+ <ul>
+ <li>integer identifiers</li>
+ <li>a bitwise OR of the current flags</li>
+ </ul>
+ <p>The array should contain only properties whose flags have actually
+ changed.</p>
+ </tp:docstring>
+ </arg>
+ </signal>
+ <method name="SetProperties" tp:name-for-bindings="Set_Properties">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Takes an array of (identifier, value) pairs containing desired
+ values to set the given properties. In the case of any errors, no
+ properties will be changed. When the changes have been acknowledged
+ by the server, the PropertiesChanged signal will be emitted.</p>
+
+ <p>All properties given must have the PROPERTY_FLAG_WRITE flag, or
+ PermissionDenied will be returned. If any variants are of the wrong
+ type, NotAvailable will be returned. If any given property identifiers
+ are invalid, InvalidArgument will be returned.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="Properties" type="a(uv)"
+ tp:type="Property_Value[]">
+ <!-- XXX: if we're ever breaking API compatibility, make this a{uv} -->
+ <tp:docstring>
+ An array mapping integer property identifiers to boxed values
+ </tp:docstring>
+ </arg>
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.PermissionDenied"/>
+ <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+ </tp:possible-errors>
+ </method>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Interface for channels and other objects, to allow querying and setting
+ properties. ListProperties returns which properties are valid for
+ the given channel, including their type, and an integer handle used to
+ refer to them in GetProperties, SetProperties, and the PropertiesChanged
+ signal. The values are represented by D-Bus variant types, and are
+ accompanied by flags indicating whether or not the property is readable or
+ writable.</p>
+
+ <p>Each property also has a flags value to indicate what methods are
+ available. This is a bitwise OR of PropertyFlags values.</p>
+ </tp:docstring>
+ <tp:flags name="Property_Flags" value-prefix="Property_Flag" type="u">
+ <tp:flag suffix="Read" value="1">
+ <tp:docstring>The property can be read</tp:docstring>
+ </tp:flag>
+ <tp:flag suffix="Write" value="2">
+ <tp:docstring>The property can be written</tp:docstring>
+ </tp:flag>
+ </tp:flags>
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Protocol.xml b/qt4/spec/Protocol.xml
new file mode 100644
index 000000000..5e2c9b197
--- /dev/null
+++ b/qt4/spec/Protocol.xml
@@ -0,0 +1,485 @@
+<?xml version="1.0" ?>
+<node name="/Protocol"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Protocol">
+ <tp:added version="0.19.10">(as stable API)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An object representing a protocol for which this <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ConnectionManager</tp:dbus-ref>
+ can create <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>s.</p>
+
+ <p>Each Protocol object has the same well-known bus name as its parent
+ ConnectionManager. Its object path is formed by taking the
+ ConnectionManager's object path and appending '/', followed by the
+ <tp:type>Protocol</tp:type> name with any hyphen/minus '-' converted
+ to underscores '_'.</p>
+
+ <tp:rationale>
+ <p>This is the same as the representation of protocol names
+ in Account object paths, and in Connection object paths and bus
+ names. For instance, telepathy-gabble and telepathy-salut would
+ implement objects at
+ <code>/org/freedesktop/Telepathy/ConnectionManager/gabble/jabber</code>
+ and
+ <code>/org/freedesktop/Telepathy/ConnectionManager/salut/local_xmpp</code>,
+ respectively.</p>
+ </tp:rationale>
+
+ <p>If the ConnectionManager has a <tt>.manager</tt> file, each
+ Protocol's immutable properties must be represented in that file;
+ the representation is described as part of the documentation for
+ each property. For instance, a very simple ConnectionManager with one
+ Protocol might be represented like this:</p>
+
+<pre>
+[ConnectionManager]
+Interfaces=
+
+[Protocol example]
+Interfaces=
+ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.Requests;
+param-account=s required
+param-password=s required secret
+RequestableChannelClasses=text;
+VCardField=x-example
+EnglishName=Example
+Icon=im-example
+AuthenticationTypes=org.freedesktop.Telepathy.Channel.Type.ServerTLSConnection;org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication;
+
+[text]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
+</pre>
+ </tp:docstring>
+
+ <property name="Interfaces" tp:name-for-bindings="Interfaces"
+ access="read" type="as" tp:type="DBus_Interface[]"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of interfaces supported by this Protocol object.</p>
+
+ <p>This property should not be confused with
+ <tp:member-ref>ConnectionInterfaces</tp:member-ref>,
+ which refers to the interfaces of <em>connections</em> to this
+ protocol.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ (as described as part of the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >ConnectionManager</tp:dbus-ref> interface) MUST cache this
+ property in the protocol's section of the <code>.manager</code>
+ file, using the key <code>Interfaces</code>. The corresponding value
+ is a list of D-Bus interface names, each followed by a semicolon.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Parameters" tp:name-for-bindings="Parameters"
+ access="read" type="a(susv)" tp:type="Param_Spec[]"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The parameters which must or may be provided to the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.ConnectionManager"
+ >RequestConnection</tp:dbus-ref> method when connecting to the
+ given protocol.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ (as described as part of the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >ConnectionManager</tp:dbus-ref> interface) MUST cache this
+ property in the protocol's section of the <code>.manager</code>
+ file via keys of the form <code>param-<em>p</em></code> and
+ <code>default-<em>p</em></code>, as documented in the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >ConnectionManager</tp:dbus-ref> interface.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="ConnectionInterfaces"
+ tp:name-for-bindings="Connection_Interfaces"
+ access="read" type="as" tp:type="DBus_Interface[]"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of interface names which might be in the
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection"
+ >Interfaces</tp:dbus-ref> property of a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection</tp:dbus-ref> to this protocol. Whether a Connection
+ will have all, some or none of these interfaces depends on server
+ capabilities.</p>
+
+ <p>This property should not be confused with
+ <tp:member-ref>Interfaces</tp:member-ref>.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file, using the key
+ <code>ConnectionInterfaces</code>. The corresponding value
+ is a list of D-Bus interface names, each followed by a semicolon.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="RequestableChannelClasses"
+ tp:name-for-bindings="Requestable_Channel_Classes"
+ access="read" type="a(a{sv}as)" tp:type="Requestable_Channel_Class[]"
+ tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of channel classes which might be requestable from a
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection</tp:dbus-ref> to this protocol (i.e. they will,
+ or might, appear in the Connection's <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface.Requests"
+ >RequestableChannelClasses</tp:dbus-ref> property).</p>
+
+ <p>Whether a Connection will have all, some or none of these
+ requestable channel classes depends on server capabilities;
+ similarly, individual contacts are not guaranteed to support
+ all of these channel classes.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file, using the key
+ <code>RequestableChannelClasses</code>. The corresponding value
+ is a list of opaque strings, each followed by a semicolon; each
+ of those strings is the name of a group in the <code>.manager</code>
+ file which represents a channel class.</p>
+
+ <p>The names of the groups representing channel classes are not
+ significant, and MUST NOT be interpreted. When writing
+ <tt>.manager</tt> files, authors MAY choose mnemonic group names,
+ generate group names mechanically (e.g. with an incrementing
+ integer), or use some combination of these.</p>
+
+ <p>Each group representing a channel class has a key
+ <code>allowed</code> which is a list of D-Bus property names
+ representing allowed parameters. Any other keys that do not contain
+ a space MUST be ignored. Any key containing a space represents
+ a fixed property; the key has the form
+ "<code><em>propertyname</em> <em>type</em></code>", and the value
+ is encoded in the same way as for the <code>default-<em>p</em></code>
+ keys described in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >ConnectionManager</tp:dbus-ref> documentation.</p>
+
+ <p>Connection managers that have channel classes whose fixed
+ properties are not representable in this form SHOULD NOT have
+ <code>.manager</code> files.</p>
+
+ <p>For instance, this <code>.manager</code> file could represent
+ a connection manager that supports 1-1 Text messages and
+ StreamedMedia audio calls:</p>
+
+<pre>[Protocol jabber]
+param-account=s required
+param-password=s required
+RequestableChannelClasses=rcc0;rcc1;
+
+[rcc0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
+
+[rcc1]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;org.freedesktop.Telepathy.Channel.Type.StreamedMedia.InitialAudio;
+</pre>
+ </tp:docstring>
+ </property>
+
+ <property name="VCardField" tp:name-for-bindings="VCard_Field"
+ access="read" type="s" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the most common vCard field used for this protocol's
+ contact identifiers, normalized to lower case, or the empty string
+ if there is no such field.</p>
+
+ <p>For example, this would be <code>x-jabber</code> for
+ Jabber/XMPP (including Google Talk), or <code>tel</code> for
+ the <abbr title="Public Switched Telephone Network">PSTN</abbr>.</p>
+
+ <p>A more exhaustive list of addressable vCard fields can be found in
+ the Protocol's Addressing interface's
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Protocol.Interface.Addressing.DRAFT">AddressableVCardFields</tp:dbus-ref>.</p>
+
+ <p>It is not necessarily valid to interpret contacts' identifiers
+ as values of this vCard field. For instance, telepathy-sofiasip
+ supports contacts whose identifiers are of the form
+ sip:jenny@example.com or tel:8675309, which would not normally
+ both be represented by any single vCard field. Arbitrary
+ handles/identifiers as vCard fields are represented
+ through the Connection's
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface">Addressing.DRAFT</tp:dbus-ref>
+ contact attributes.</p>
+
+ <tp:rationale>
+ <p>This is taken from Mission Control profiles as used on Maemo 5.
+ One valid use of this field is to answer the question: given a
+ contact's vCard containing an X-JABBER field, how can you
+ communicate with the contact? By iterating through protocols
+ looking for an x-jabber VCardField, one can build up a list of
+ protocols that handle x-jabber, then offer the user a list of
+ accounts for those protocols and/or the option to create a new
+ account for one of those protocols.</p>
+ </tp:rationale>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file if it is non-empty, using the key
+ <code>VCardField</code>. The corresponding value
+ is a string, following the syntax of the "localestring" type from
+ the Desktop Entry Specification.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="EnglishName" tp:name-for-bindings="English_Name"
+ access="read" type="s" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of the protocol in a form suitable for display to users,
+ such as "AIM" or "Yahoo!", or the empty string if none is
+ available.</p>
+
+ <p>This is effectively in the C locale (international English);
+ user interfaces requiring a localized protocol name SHOULD look
+ one up in their own message catalog based on either the Telepathy
+ <tp:type>Protocol</tp:type> name or this property, but SHOULD use
+ this English version as a fallback if no translated version can be
+ found.</p>
+
+ <tp:rationale>
+ <p>Many protocols are named after a company or product which isn't
+ translated in non-English locales. This also provides a fallback
+ display name, for UIs with no prior knowledge of a particular
+ protocol.</p>
+ </tp:rationale>
+
+ <p>If this property's value is empty, clients MAY fall back to using
+ the Telepathy <tp:type>Protocol</tp:type> name, possibly with its
+ capitalization adjusted.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file if it is non-empty, using the key
+ <code>EnglishName</code>. The corresponding value
+ is a string, following the syntax of the "localestring" type from
+ the Desktop Entry Specification.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="Icon" tp:name-for-bindings="Icon"
+ access="read" type="s" tp:immutable="yes">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The name of an icon in the system's icon theme, such as "im-msn", or
+ the empty string.</p>
+
+ <tp:rationale>
+ <p>This can be used as a default if the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account">Icon</tp:dbus-ref>
+ property is not set on an Account, or used by the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">AccountManager</tp:dbus-ref>
+ to choose a default icon if none is set during account
+ creation.</p>
+ </tp:rationale>
+
+ <p>If this property's value is empty, clients MAY fall back to
+ generating a name based on the <tp:type>Protocol</tp:type> name.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file if it is non-empty, using the key
+ <code>Icon</code>. The corresponding value
+ is a string, following the syntax of the "localestring" type from
+ the Desktop Entry Specification.</p>
+ </tp:docstring>
+ </property>
+
+ <method name="IdentifyAccount"
+ tp:name-for-bindings="Identify_Account">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Return a string which uniquely identifies the account to which the
+ given parameters would connect.</p>
+
+ <tp:rationale>
+ <p>For many protocols, this would return the well-known 'account'
+ parameter. However, for IRC the returned string would be composed
+ from the 'account' (i.e. nickname) and 'server' parameters.
+ AccountManager implementations can use this to form the
+ account-specific part of an Account's object path.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Parameters"
+ type="a{sv}" tp:type="String_Variant_Map">
+ <tp:docstring>
+ A set of parameters as would be provided to <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.ConnectionManager"
+ >RequestConnection</tp:dbus-ref>
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Account_ID" type="s">
+ <tp:docstring>
+ <p>An opaque string suitable for use as the account-specific part of
+ an <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Account</tp:dbus-ref>'s object path. This is not necessarily
+ globally unique, but should represent a "best-effort"
+ identification of the account.</p>
+
+ <tp:rationale>
+ <p>For a pathological case, consider a user signing in as
+ 'me@example.com' with 'server' set to either jabber1.example.com
+ or jabber2.example.com. Both of these should result in
+ me@example.com being returned from this method, even if the user
+ can actually be signed in to those two servers
+ simultaneously.</p>
+ </tp:rationale>
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The IdentifyAccount method is not supported by this connection
+ manager. The caller SHOULD fall back to deriving identification
+ from the parameters.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="NormalizeContact"
+ tp:name-for-bindings="Normalize_Contact">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Attempt to normalize the given contact ID. Where possible, this
+ SHOULD return the same thing that would be returned by
+ InspectHandles(RequestHandles(CONTACT, [Contact_ID])) on a connected
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection</tp:dbus-ref>.</p>
+
+ <p>If full normalization requires network activity or is otherwise
+ impossible to do without a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>,
+ this method SHOULD perform a best-effort normalization.</p>
+
+ <tp:rationale>
+ <p>One common example of a best-effort offline normalization
+ differing from the ideal normalization is XMPP.</p>
+
+ <p>On XMPP, contacts' JIDs should normally have the resource removed
+ during normalization, but for contacts in a MUC (chatroom), the
+ resource is an integral part of the JID - so the contact JID
+ alice@example.com/Empathy should normalize to alice@example.com,
+ but the in-MUC JID wonderland@conference.example.com/Alice should
+ normalize to itself.</p>
+
+ <p>While online, the connection manager has enough context to know
+ which chatrooms the user is in, and can infer from that whether
+ to remove resources, but the best-effort normalization performed
+ while offline does not have this context, so the best that can be
+ done is to remove the resource from all JIDs.</p>
+ </tp:rationale>
+
+ <p>This method MAY simply raise NotImplemented on some protocols.</p>
+
+ <tp:rationale>
+ <p>In link-local XMPP, you can't talk to someone who isn't present
+ on your local network, so normalizing identifiers in advance is
+ meaningless.</p>
+ </tp:rationale>
+ </tp:docstring>
+
+ <arg direction="in" name="Contact_ID" type="s">
+ <tp:docstring>
+ The identifier of a contact in this protocol
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Normalized_Contact_ID" type="s">
+ <tp:docstring>
+ The identifier of a contact in this protocol, normalized as much
+ as possible
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The NormalizeContact method is not supported by this connection
+ manager. The caller MAY recover by using the contact ID as-is.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <property name="AuthenticationTypes"
+ tp:name-for-bindings="Authentication_Types" access="read" type="as"
+ tp:type="DBus_Interface[]" tp:immutable="yes">
+ <tp:added version="0.21.7"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A list of D-Bus interfaces which provide information as to
+ what kind of authentication channels can possibly appear
+ before the connection reaches the CONNECTED state.</p>
+
+ <p>These can either be channel types, or where the channel
+ type isn't enough information to be useful, interfaces
+ indicating a specific use of a channel type. For example,
+ <tp:dbus-ref namespace="ofdT.Channel.Type">ServerTLSConnection</tp:dbus-ref>
+ channels are obviously about TLS certificates so the channel
+ type would appear in this list. However, a
+ <tp:dbus-ref namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channel type alone does not explain enough about the authentication type
+ in use as it is merely a base for the channel interfaces that appear in
+ said channels. In this case, CMs should use the value of the
+ <tp:dbus-ref namespace="ofdT.Channel.Type">ServerAuthentication.AuthenticationMethod</tp:dbus-ref>
+ property in this list.</p>
+
+ <p>For example, if a protocol's
+ <tp:member-ref>AuthenticationTypes</tp:member-ref> contains
+ two values:</p>
+
+ <blockquote>
+ <pre>
+[ ...<tp:dbus-ref namespace="ofdT">Channel.Type.ServerTLSConnection</tp:dbus-ref>,
+ ...<tp:dbus-ref namespace="ofdT">Channel.Interface.SASLAuthentication</tp:dbus-ref> ]</pre></blockquote>
+
+ <p>This tells a client that before the connection status
+ reached CONNECTED, a <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerTLSConnection</tp:dbus-ref>
+ could appear carrying a TLS certificate. It also tells the
+ client that before the connection status reaches CONNECTED, a
+ <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication</tp:dbus-ref>
+ channel could also appear, where <tp:dbus-ref
+ namespace="ofdT.Channel.Type">ServerAuthentication.AuthenticationMethod</tp:dbus-ref>=<tp:dbus-ref
+ namespace="ofdT.Channel.Interface">SASLAuthentication</tp:dbus-ref>. A
+ hypothetical future Channel.Interface.Captcha interface would
+ also appear in this list if the CM might require the user
+ solve a captcha before connecting.</p>
+
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Protocol_Interface_Addressing.xml b/qt4/spec/Protocol_Interface_Addressing.xml
new file mode 100644
index 000000000..3722c3b81
--- /dev/null
+++ b/qt4/spec/Protocol_Interface_Addressing.xml
@@ -0,0 +1,300 @@
+<?xml version="1.0" ?>
+<node name="/Protocol_Interface_Addressing"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface
+ name="org.freedesktop.Telepathy.Protocol.Interface.Addressing.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.19.12">(as draft)</tp:added>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for protocols that support multiple forms of
+ addressing contacts, for example through vCard addresses and URIs.</p>
+
+ <p>If the ConnectionManager has a <tt>.manager</tt> file, and it
+ supports this interface, the interface's immutable properties
+ must be represented in the file; the representation is described as
+ part of the documentation for each property.</p>
+
+ <p>For instance, a SIP connection manager might have the
+ following lines in the <tt>.manager</tt> file.</p>
+
+<pre>
+[Protocol sip]
+AddressableVCardFields=tel;x-sip;
+AddressableURISchemes=tel;sip;
+</pre>
+ </tp:docstring>
+
+ <property name="AddressableVCardFields"
+ tp:name-for-bindings="Addressable_VCard_Fields"
+ access="read" type="as">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The vCard fields that can be used to request a contact with
+ normalized to lower case. If the <code>URL</code> vCard
+ field is addressable, a colon, followed by the supported URI
+ schemes will be concatenated.</p>
+
+ <p>For example: <code>["tel", "x-sip"]</code>.</p>
+
+ <p>The <code>url</code> vCard field MUST NOT appear here; see
+ <tp:member-ref>AddressableURISchemes</tp:member-ref> instead.</p>
+
+ <tp:rationale>
+ <p>In practice, protocols have a limited set of URI
+ schemes that make sense to resolve as a contact.</p>
+ </tp:rationale>
+
+ <p>This property should only be used when the connection is
+ offline. When it is connected the addressable fields should be
+ retrieved from the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Requests.RequestableChannelClasses</tp:dbus-ref>'s
+ TargetVCardField fixed-property instead.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file if it is non-empty, using the key
+ <code>AddressableVCardFields</code>. The corresponding value
+ is a list of strings, each followed with a semicolon and in the
+ syntax of the "localestring" type from the Desktop Entry
+ Specification.</p>
+
+ <p>Well-known vCard fields:</p>
+
+ <dl>
+ <dt><code>tel</code></dt>
+ <dd>The TEL vCard field. Used for phone numbers.</dd>
+ <dt><code>x-sip</code></dt>
+ <dd>The X-SIP vCard field. Used for SIP addresses.</dd>
+ <dt><code>x-aim</code></dt>
+ <dd>The X-AIM vCard field. Used for AIM user IDs.</dd>
+ <dt><code>x-icq</code></dt>
+ <dd>The X-ICQ vCard field. Used for ICQ UINs.</dd>
+ <dt><code>x-skype</code></dt>
+ <dd>The X-SKYPE vCard field. Used for Skype user names or
+ telephone numbers. There is also a X-SKYPE-USERNAME field,
+ but for Telepathy purposes, <code>x-skype</code> is preferred</dd>
+ <dt><code>x-groupwise</code></dt>
+ <dd>The X-GROUPWISE vCard field. Used for Groupwise contacts.</dd>
+ <dt><code>x-gadugadu</code></dt>
+ <dd>The X-GADUGADU vCard field. Used for Gadu-Gadu contacts.</dd>
+ <dt><code>x-jabber</code></dt>
+ <dd>The X-JABBER vCard field. Used for XMPP JIDs.</dd>
+ <dt><code>x-msn</code></dt>
+ <dd>The X-MSN vCard field. Used for MSN contacts.</dd>
+ <dt><code>x-yahoo</code></dt>
+ <dd>The X-YAHOO vCard field. Used for Yahoo! IDs.</dd>
+ </dl>
+
+ </tp:docstring>
+ </property>
+
+ <property name="AddressableURISchemes"
+ tp:name-for-bindings="Addressable_URI_Schemes"
+ access="read" type="as">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The URI schemes that are supported by this protocol.</p>
+
+ <p>For example: <code>["tel", "sip"]</code>.</p>
+
+ <p>This property should only be used when the connection is
+ offline. When it is connected the addressable URI schemes should be
+ retrieved from the
+ <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Connection.Interface">Requests.RequestableChannelClasses</tp:dbus-ref>'s
+ TargetURIScheme fixed-property instead.</p>
+
+ <p>Connection managers with a <code>.manager</code> file
+ MUST cache this property in the protocol's section of the
+ <code>.manager</code> file if it is non-empty, using the key
+ <code>AddressableURISchemes</code>. The corresponding value
+ is a list of strings, each followed with a semicolon and in the
+ syntax of the "localestring" type from the Desktop Entry
+ Specification.</p>
+
+ <p>Well-known URI schemes:</p>
+
+ <dl>
+ <dt><code>sip</code></dt>
+ <dd>SIP protocol.
+ For example: <code>sip:julien@example.com</code>.</dd>
+ <dt><code>sips</code></dt>
+ <dd>Secure (encrypted) SIP protocol.
+ For example: <code>sips:julien@example.com</code>.</dd>
+ <dt><code>tel</code></dt>
+ <dd>Used for telephone numbers.
+ For example: <code>tel:+12065551234</code>.</dd>
+ <dt><code>xmpp</code></dt>
+ <dd>XMPP protocol.
+ For example: <code>xmpp:julien@example.com</code>.</dd>
+ <dt><code>msnim</code></dt>
+ <dd>For the purposes of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Protocol.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ the verb part is ignored, and SHOULD be <code>add</code>; the
+ <code>contact</code> field in the query string is used to
+ identify the contact.
+ For example: <code>msnim:add?contact=julien</code>.</dd>
+ <dt><code>aim</code></dt>
+ <dd>For the purposes of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Protocol.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ the verb part is ignored, and SHOULD be <code>addbuddy</code>; the
+ <code>screenname</code> field in the query string is used to
+ identify the contact.
+ For example: <code>aim:addbuddy?screenname=julien</code>.</dd>
+ <dt><code>skype</code></dt>
+ <dd>Skype protocol.
+ For example: <code>skype:julien</code>.</dd>
+ <dt><code>ymsgr</code></dt>
+ <dd>For the purposes of
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Protocol.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ and
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Channel.Interface.Addressing.DRAFT</tp:dbus-ref>,
+ the verb part is ignored, and SHOULD be <code>addfriend</code>; the
+ query string is used to identify the contact.
+ For example: <code>ymsgr:addfriend?julien</code>.</dd>
+ <dt><code>gg</code></dt>
+ <dd>Gadu-Gadu protocol.
+ For example: <code>gg:julien</code>.</dd>
+ </dl>
+ </tp:docstring>
+ </property>
+
+ <method name="NormalizeVCardAddress"
+ tp:name-for-bindings="Normalize_VCard_Address">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Attempt to normalize the given vCard address. Where possible, this
+ SHOULD return an address that would appear in the
+ <code>org.freedesktop.Telepathy.Connection.Interface.Addressing.DRAFT/addresses</code>
+ attribute for a contact on a connected
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>.
+ </p>
+
+ <p>If full normalization requires network activity or is otherwise
+ impossible to do without a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>,
+ this method SHOULD perform a best-effort normalization.</p>
+
+ <p>An example would be a vCard TEL field with a formatted
+ number in the form of <code>+1 (206) 555 1234</code>, this would be
+ normalized to <code>+12065551234</code>.</p>
+
+ <p>This method MAY simply raise NotImplemented on some
+ protocols, if it has no use.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="VCard_Field" type="s">
+ <tp:docstring>
+ The vCard field of the address we are normalizing. The
+ field name SHOULD be in lower case, and MUST appear in
+ <tp:member-ref>AddressableVCardFields</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="in" name="VCard_Address" type="s">
+ <tp:docstring>
+ The address to normalize.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Normalized_VCard_Address" type="s">
+ <tp:docstring>
+ The vCard address, normalized as much as possible.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The vCard field is not supported (it is not in
+ <tp:member-ref>AddressableVCardFields</tp:member-ref>).
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The address is syntactically incorrect.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ <method name="NormalizeURI"
+ tp:name-for-bindings="Normalize_URI">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Attempt to normalize the given contact URI. Where possible, this
+ SHOULD return an address that would appear in the
+ <code>org.freedesktop.Telepathy.Connection.Interface.Addressing.DRAFT/uris</code>
+ attribute for a contact on a connected
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>.
+ </p>
+
+ <p>If full normalization requires network activity or is otherwise
+ impossible to do without a <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">Connection</tp:dbus-ref>,
+ this method SHOULD perform a best-effort normalization.</p>
+
+ <p>Example: <code>xmpp:eitan@EXAMPLE.COM</code> would be normalized to
+ <code>xmpp:eitan@example.com</code>.</p>
+
+ <p>This method MAY simply raise NotImplemented on some
+ protocols, if it has no use.</p>
+ </tp:docstring>
+
+ <arg direction="in" name="URI" type="s">
+ <tp:docstring>
+ The URI to normalize. The URI's scheme (i.e. the part before
+ the first colon) MUST appear in
+ <tp:member-ref>AddressableURISchemes</tp:member-ref>.
+ </tp:docstring>
+ </arg>
+
+ <arg direction="out" name="Normalized_URI" type="s">
+ <tp:docstring>
+ A URI, normalized as much as possible.
+ </tp:docstring>
+ </arg>
+
+ <tp:possible-errors>
+ <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented">
+ <tp:docstring>
+ The URI scheme is not supported (it is not in
+ <tp:member-ref>AddressableURISchemes</tp:member-ref>).
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument">
+ <tp:docstring>
+ The URI is syntactically incorrect.
+ </tp:docstring>
+ </tp:error>
+ </tp:possible-errors>
+ </method>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/spec/Protocol_Interface_Avatars.xml b/qt4/spec/Protocol_Interface_Avatars.xml
new file mode 100644
index 000000000..cd913510d
--- /dev/null
+++ b/qt4/spec/Protocol_Interface_Avatars.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" ?>
+<node name="/Protocol_Interface_Avatars"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Protocol.Interface.Avatars">
+ <tp:added version="0.21.5">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Protocol"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for protocols where it might be possible to set the
+ user's avatar, and the expected size limits and supported MIME types
+ are known before connecting.</p>
+
+ <tp:rationale>
+ <p>If the avatar requirements cannot be discovered while offline,
+ it's impossible to avoid setting the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Account</tp:dbus-ref>'s <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy.Account.Interface.Avatar"
+ >Avatar</tp:dbus-ref> property to an unsupported avatar.</p>
+ </tp:rationale>
+
+ <p>Each property on this interface SHOULD be cached in the
+ <code>.manager</code> file, using a key of the same name as the
+ property in the <code>[Protocol <em>proto</em>]</code>
+ group. All properties are encoded in ASCII decimal in the obvious
+ way, except for
+ <tp:member-ref>SupportedAvatarMIMETypes</tp:member-ref> which is
+ encoded as a sequence of strings each followed by a semicolon
+ (as for the "localestrings" type in the Desktop Entry
+ Specification).</p>
+
+ <p>For instance, an XMPP connection manager might have this
+ <code>.manager</code> file:</p>
+
+<pre>[Protocol jabber]
+Interfaces=org.freedesktop.Telepathy.Protocol.Interface.Avatars;
+param-account=s required
+param-password=s required
+SupportedAvatarMIMETypes=image/png;image/jpeg;image/gif;
+MinimumAvatarHeight=32
+RecommendedAvatarHeight=64
+MaximumAvatarHeight=96
+MinimumAvatarWidth=32
+RecommendedAvatarWidth=64
+MaximumAvatarWidth=96
+MaximumAvatarBytes=8192
+</pre>
+ </tp:docstring>
+
+ <property name="SupportedAvatarMIMETypes"
+ tp:name-for-bindings="Supported_Avatar_MIME_Types"
+ type="as" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.SupportedAvatarMIMETypes</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="MinimumAvatarHeight"
+ tp:name-for-bindings="Minimum_Avatar_Height"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.MinimumAvatarHeight</tp:dbus-ref>
+ property on connections to this protocol.
+</tp:docstring>
+ </property>
+
+ <property name="MinimumAvatarWidth"
+ tp:name-for-bindings="Minimum_Avatar_Width"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.MinimumAvatarWidth</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="RecommendedAvatarHeight"
+ tp:name-for-bindings="Recommended_Avatar_Height"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.RecommendedAvatarHeight</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="RecommendedAvatarWidth"
+ tp:name-for-bindings="Recommended_Avatar_Width"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.RecommendedAvatarWidth</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumAvatarHeight"
+ tp:name-for-bindings="Maximum_Avatar_Height"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.MaximumAvatarHeight</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumAvatarWidth"
+ tp:name-for-bindings="Maximum_Avatar_Width"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.MaximumAvatarWidth</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumAvatarBytes"
+ tp:name-for-bindings="Maximum_Avatar_Bytes"
+ type="u" access="read">
+ <tp:docstring>
+ The expected value of the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Avatars.MaximumAvatarBytes</tp:dbus-ref>
+ property on connections to this protocol.
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
diff --git a/qt4/spec/Protocol_Interface_Presence.xml b/qt4/spec/Protocol_Interface_Presence.xml
new file mode 100644
index 000000000..ddff33263
--- /dev/null
+++ b/qt4/spec/Protocol_Interface_Presence.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" ?>
+<node name="/Protocol_Interface_Presence"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Protocol.Interface.Presence">
+ <tp:added version="0.21.3">(as stable API)</tp:added>
+ <tp:requires interface="org.freedesktop.Telepathy.Protocol"/>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>An interface for protocols where it might be possible to set the
+ user's presence, and the supported presence types can be predicted
+ before connecting.</p>
+
+ <tp:rationale>
+ <p>This allows UIs to show or hide presence types that aren't
+ always supported, such as "invisible", while not online.</p>
+ </tp:rationale>
+
+ <p>The properties on this interface SHOULD be cached in the
+ <code>.manager</code> file, in the
+ <code>[Protocol <em>proto</em>]</code>
+ group. For each status <em>s</em> in
+ <tp:member-ref>Statuses</tp:member-ref>, that group should
+ contain a key of the form <code>status-<em>s</em></code> whose value
+ is the <tp:type>Connection_Presence_Type</tp:type> as an ASCII
+ decimal integer, followed by a space-separated sequence of tokens
+ from the following set:</p>
+
+ <dl>
+ <dt>settable</dt>
+ <dd>If present, the user can set this status on themselves using
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection.Interface.SimplePresence"
+ >SetPresence</tp:dbus-ref>; this corresponds to May_Set_On_Self
+ in the <tp:type>Simple_Status_Spec</tp:type> struct.</dd>
+
+ <dt>message</dt>
+ <dd>If present, the user can set a non-empty message for this status;
+ this corresponds to Can_Have_Message in the
+ <tp:type>Simple_Status_Spec</tp:type> struct.</dd>
+ </dl>
+
+ <p>Unrecognised tokens MUST be ignored.</p>
+
+ <p>For instance, an XMPP connection manager might have this
+ <code>.manager</code> file:</p>
+
+<pre>[Protocol jabber]
+Interfaces=org.freedesktop.Telepathy.Protocol.Interface.Presence;
+param-account=s required
+param-password=s required
+status-offline=1
+status-unknown=7
+status-error=8
+status-hidden=5 settable message
+status-xa=4 settable message
+status-away=3 settable message
+status-dnd=6 settable message
+status-available=2 settable message
+status-chat=2 settable message
+</pre>
+
+ <p>which corresponds to these property values (using a Python-like
+ syntax):</p>
+
+<pre>Statuses = {
+ 'offline': (OFFLINE, False, False),
+ 'unknown': (UNKNOWN, False, False),
+ 'error': (ERROR, False, False),
+ 'hidden': (HIDDEN, True, True),
+ 'xa': (EXTENDED_AWAY, True, True),
+ 'away': (AWAY, True, True),
+ 'dnd': (BUSY, True, True),
+ 'available': (AVAILABLE, True, True),
+ 'chat': (AVAILABLE, True, True),
+}
+</pre>
+ </tp:docstring>
+
+ <property name="Statuses"
+ tp:name-for-bindings="Statuses"
+ type="a{s(ubb)}" tp:type="Simple_Status_Spec_Map" access="read">
+ <tp:docstring>
+ <p>The statuses that might appear in the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.SimplePresence.Statuses</tp:dbus-ref>
+ property on a connection to this protocol that supports
+ SimplePresence. This property is immutable.</p>
+
+ <p>Depending on server capabilities, it is possible that not all
+ of these will actually appear on the Connection.</p>
+ </tp:docstring>
+ </property>
+
+ </interface>
+</node>
diff --git a/qt4/spec/all.xml b/qt4/spec/all.xml
new file mode 100644
index 000000000..31db78062
--- /dev/null
+++ b/qt4/spec/all.xml
@@ -0,0 +1,313 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Telepathy D-Bus Interface Specification</tp:title>
+<tp:version>0.22.0</tp:version>
+
+<tp:copyright>Copyright © 2005-2011 Collabora Limited</tp:copyright>
+<tp:copyright>Copyright © 2005-2011 Nokia Corporation</tp:copyright>
+<tp:copyright>Copyright © 2006 INdT</tp:copyright>
+
+<tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+</tp:license>
+
+<tp:section name="Connection Managers">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ A Connection Manager is a factory for connections.
+ </p>
+ </tp:docstring>
+ <xi:include href="Connection_Manager.xml"/>
+ <xi:include href="Connection_Manager_Interface_Account_Storage.xml"/>
+ <xi:include href="Protocol.xml"/>
+ <xi:include href="Protocol_Interface_Addressing.xml"/>
+ <xi:include href="Protocol_Interface_Avatars.xml"/>
+ <xi:include href="Protocol_Interface_Presence.xml"/>
+
+ <tp:section name="Connection Object">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Connections represent active protocol sessions. There are a number of core
+ interfaces which all connections should implement, and a number of optional
+ interfaces which provide various functionality related to contacts and to
+ the connection itself.
+ </p>
+ </tp:docstring>
+ <xi:include href="Connection.xml"/>
+ <xi:include href="Connection_Future.xml"/>
+ <xi:include href="Connection_Interface_Contacts.xml"/>
+ <xi:include href="Connection_Interface_Requests.xml"/>
+
+ <tp:section name="Contact list interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ On protocols that support contact lists, these interface expose the user's
+ contact lists, along with presence subscription information, contact
+ list groups (if supported), and the ability to block and unblock contacts
+ (if supported).
+ </p>
+ </tp:docstring>
+
+ <xi:include href="Connection_Interface_Contact_List.xml"/>
+ <xi:include href="Connection_Interface_Contact_Groups.xml"/>
+ <xi:include href="Connection_Interface_Contact_Blocking.xml"/>
+ </tp:section>
+
+ <tp:section name="Contact metadata interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ These optional Connection interfaces expose metadata about contacts on
+ this connection—from their current presence through to the type of client
+ they're connected with—and allow the local user to publish such metadata
+ back to their contacts.
+ </p>
+ </tp:docstring>
+
+ <xi:include href="Connection_Interface_Aliasing.xml"/>
+ <xi:include href="Connection_Interface_Avatars.xml"/>
+ <xi:include href="Connection_Interface_Capabilities.xml"/>
+ <xi:include href="Connection_Interface_Client_Types.xml"/>
+ <xi:include href="Connection_Interface_Contact_Capabilities.xml"/>
+ <xi:include href="Connection_Interface_Contact_Info.xml"/>
+ <xi:include href="Connection_Interface_Location.xml"/>
+ <xi:include href="Connection_Interface_Presence.xml"/>
+ <xi:include href="Connection_Interface_Renaming.xml"/>
+ <xi:include href="Connection_Interface_Resources.xml"/>
+ <xi:include href="Connection_Interface_Simple_Presence.xml"/>
+ </tp:section>
+
+ <tp:section name="Connection feature interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ These optional Connection interfaces expose protocol-specific features,
+ and allow configuring the running connection.
+ </p>
+ </tp:docstring>
+
+ <xi:include href="Connection_Interface_Addressing.xml"/>
+ <xi:include href="Connection_Interface_Anonymity.xml"/>
+ <xi:include href="Connection_Interface_Balance.xml"/>
+ <xi:include href="Connection_Interface_Cellular.xml"/>
+ <xi:include href="Connection_Interface_Communication_Policy.xml"/>
+ <xi:include href="Connection_Interface_Forwarding.xml"/>
+ <xi:include href="Connection_Interface_Keepalive.xml"/>
+ <xi:include href="Connection_Interface_Mail_Notification.xml"/>
+ <xi:include href="Connection_Interface_Power_Saving.xml"/>
+ <xi:include href="Connection_Interface_Service_Point.xml"/>
+ </tp:section>
+ </tp:section>
+
+ <xi:include href="Channel_Bundle.xml"/>
+
+ <tp:section name="Channel Object">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ A Channel is used by Telepathy to exchange data between local
+ applications and remote servers. A given connection will have many
+ channels, each one represented by a D-Bus object.
+ </p>
+ <p>
+ Each Channel has a type, represented by a D-Bus interface, and may
+ implement one or more additional interfaces from the list of channel
+ interfaces below.
+ </p>
+ </tp:docstring>
+ <xi:include href="Channel.xml"/>
+ <xi:include href="Channel_Future.xml"/>
+
+ <tp:section name="Channel Types">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Each Channel implements one of the following types:
+ </p>
+ </tp:docstring>
+ <xi:include href="Channel_Type_Call.xml"/>
+ <xi:include href="Channel_Type_Contact_List.xml"/>
+ <xi:include href="Channel_Type_Contact_Search.xml"/>
+ <xi:include href="Channel_Type_DBus_Tube.xml"/>
+ <xi:include href="Channel_Type_File_Transfer.xml"/>
+ <xi:include href="Channel_Type_Room_List.xml"/>
+ <xi:include href="Channel_Type_Server_Authentication.xml"/>
+ <xi:include href="Channel_Type_Server_TLS_Connection.xml"/>
+ <xi:include href="Channel_Type_Stream_Tube.xml"/>
+ <xi:include href="Channel_Type_Streamed_Media.xml"/>
+ <xi:include href="Channel_Type_Text.xml"/>
+ <xi:include href="Channel_Type_Tubes.xml"/>
+ </tp:section>
+
+ <tp:section name="Channel Interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ A Channel may also implement one or more of the following interfaces,
+ depending on its type. Some interfaces are only applicable to particular
+ channel types, while others may (in principle) appear on any type of
+ channel.
+ </p>
+ </tp:docstring>
+
+ <xi:include href="Channel_Interface_Addressing.xml"/>
+ <xi:include href="Channel_Interface_Anonymity.xml"/>
+ <xi:include href="Channel_Interface_Destroyable.xml"/>
+ <xi:include href="Channel_Interface_Group.xml"/>
+ <xi:include href="Channel_Interface_Password.xml"/>
+ <xi:include href="Channel_Interface_Room.xml"/>
+ <xi:include href="Channel_Interface_SASL_Authentication.xml"/>
+ <xi:include href="Channel_Interface_Credentials_Storage.xml"/>
+ <xi:include href="Channel_Interface_Securable.xml"/>
+ <xi:include href="Channel_Interface_Service_Point.xml"/>
+ <xi:include href="Channel_Interface_Tube.xml"/>
+
+ <tp:section name="Text-specific interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>These interfaces may only appear on channels of type <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>Text</tp:dbus-ref>.</p>
+ </tp:docstring>
+
+ <xi:include href="Channel_Interface_Chat_State.xml"/>
+ <xi:include href="Channel_Interface_HTML.xml"/>
+ <xi:include href="Channel_Interface_Messages.xml"/>
+ <xi:include href="Channel_Interface_SMS.xml"/>
+ </tp:section>
+
+ <tp:section name="Streamed Media/Call-related interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>These interfaces are only applicable to channels of type <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>StreamedMedia</tp:dbus-ref>, with the
+ exception of the <tp:dbus-ref
+ namespace='ofdT.Channel.Interface'>Hold</tp:dbus-ref> and
+ <tp:dbus-ref namespace="ofdT.Channel.Interface">DTMF</tp:dbus-ref>
+ interfaces, which may also appear on <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>Call.DRAFT</tp:dbus-ref> channels.</p>
+ </tp:docstring>
+
+ <xi:include href="Channel_Interface_Call_State.xml"/>
+ <xi:include href="Channel_Interface_DTMF.xml"/>
+ <xi:include href="Channel_Interface_Hold.xml"/>
+ <xi:include href="Channel_Interface_Media_Signalling.xml"/>
+ </tp:section>
+
+ <tp:section name="Conference-related interfaces">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>These interfaces provide functionality for ad-hoc conference calls and
+ chat rooms. They are primarily intended for <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>Text</tp:dbus-ref>, <tp:dbus-ref
+ namespace='ofdT.Channel.Type'>StreamedMedia</tp:dbus-ref> and
+ <tp:dbus-ref namespace='ofdT.Channel.Type'>Call.DRAFT</tp:dbus-ref>
+ channels, but may also appear on other types of channel.</p>
+ </tp:docstring>
+
+ <xi:include href="Channel_Interface_Conference.xml"/>
+ <xi:include href="Channel_Interface_Splittable.xml"/>
+ <xi:include href="Channel_Interface_Mergeable_Conference.xml"/>
+ </tp:section>
+ </tp:section>
+ </tp:section>
+
+ <tp:section name="Authentication Objects">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ A set of objects to be used for authentication purposes, such
+ as TLS certificates or handshakes for negotiating end-to-end
+ security.
+ </p>
+ </tp:docstring>
+ <xi:include href="Authentication_TLS_Certificate.xml"/>
+ </tp:section>
+
+ <tp:section name="Media">
+ <xi:include href="Media_Session_Handler.xml"/>
+ <xi:include href="Media_Stream_Handler.xml"/>
+ </tp:section>
+
+ <tp:section name="Calls">
+ <xi:include href="Call_Content.xml"/>
+ <xi:include href="Call_Content_Interface_Media.xml"/>
+ <xi:include href="Call_Content_Interface_Mute.xml"/>
+ <xi:include href="Call_Content_Interface_Video_Control.xml"/>
+ <xi:include href="Call_Content_Codec_Offer.xml"/>
+ <xi:include href="Call_Stream.xml"/>
+ <xi:include href="Call_Stream_Interface_Media.xml"/>
+ <xi:include href="Call_Stream_Endpoint.xml"/>
+ </tp:section>
+
+ <tp:section name="Debugging">
+ <xi:include href="Debug.xml"/>
+ </tp:section>
+</tp:section>
+
+<tp:section name="The Account Manager">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The Account Manager is a desktop service that provides account configuration
+ and can manage the connection managers. In general, clients will use the
+ account manager to find out about instant messaging accounts and their
+ associated connections.
+ </p>
+ </tp:docstring>
+ <xi:include href="Account_Manager.xml"/>
+ <xi:include href="Account_Manager_Interface_Hidden.xml"/>
+ <xi:include href="Account.xml"/>
+ <xi:include href="Account_Interface_Addressing.xml"/>
+ <xi:include href="Account_Interface_Avatar.xml"/>
+ <xi:include href="Account_Interface_Hidden.xml"/>
+ <xi:include href="Account_Interface_Storage.xml"/>
+ <xi:include href="Account_Interface_External_Password_Storage.xml"/>
+</tp:section>
+
+<tp:section name="The Channel Dispatcher">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The Channel Dispatcher is a desktop service whose purpose is to dispatch
+ incoming Telepathy Channels to the appropriate client (e.g. incoming text
+ chat, file transfer, tubes, etc.).
+ </p>
+ </tp:docstring>
+ <xi:include href="Channel_Dispatcher.xml"/>
+ <xi:include href="Channel_Dispatcher_Interface_Operation_List.xml"/>
+ <xi:include href="Channel_Dispatcher_Interface_Messages.xml"/>
+ <xi:include href="Channel_Dispatch_Operation.xml"/>
+ <xi:include href="Channel_Request.xml"/>
+</tp:section>
+
+<tp:section name="Clients">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Clients should implement one or more of these interfaces to be able to
+ handle channels coming in from the Channel Dispatcher.
+ </p>
+ </tp:docstring>
+ <xi:include href="Client.xml"/>
+ <xi:include href="Client_Observer.xml"/>
+ <xi:include href="Client_Approver.xml"/>
+ <xi:include href="Client_Handler.xml"/>
+ <xi:include href="Client_Handler_Future.xml"/>
+ <xi:include href="Client_Interface_Requests.xml"/>
+
+ <xi:include href="Channel_Handler.xml"/>
+</tp:section>
+
+<xi:include href="Properties_Interface.xml"/>
+
+<xi:include href="errors.xml"/>
+<xi:include href="generic-types.xml"/>
+
+<!-- Never implemented, vague
+<xi:include href="Connection_Interface_Privacy.xml"/> -->
+<!-- Causes havoc, never implemented, unclear requirements
+<xi:include href="Channel_Interface_Transfer.xml"/> -->
+
+</tp:spec>
diff --git a/qt4/spec/errors.xml b/qt4/spec/errors.xml
new file mode 100644
index 000000000..d63662edb
--- /dev/null
+++ b/qt4/spec/errors.xml
@@ -0,0 +1,601 @@
+<?xml version="1.0" ?>
+<tp:errors xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" namespace="org.freedesktop.Telepathy.Error">
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The D-Bus errors used in Telepathy all start with
+ <code>org.freedesktop.Telepathy.Error.</code>. They are used in
+ D-Bus messages of type ERROR, and also as plain strings annotated with
+ the <tp:type>DBus_Error_Name</tp:type> type.</p>
+
+ <p>In principle, any method can raise any error (this is a general fact
+ of IPC). For instance, generic D-Bus errors starting with
+ <code>org.freedesktop.DBus.Error.</code> will occur in some
+ situations.</p>
+
+ <p>Telepathy methods can also raise implementation-specific errors to
+ indicate specialized failure conditions. For better interoperability,
+ if a suitable Telepathy error exists, it should be preferred.</p>
+
+ <p>The namespace <code>org.freedesktop.Telepathy.Qt4.Error.</code>
+ is reserved for use by the D-Bus client implementation in telepathy-qt4,
+ which uses it to represent certain error situations that did not involve
+ a D-Bus ERROR message. These errors are defined and documented as part of
+ telepathy-qt4's C++ API, and should not be used on D-Bus.</p>
+ </tp:docstring>
+
+ <tp:error name="Network Error">
+ <tp:docstring>
+ Raised when there is an error reading from or writing to the network.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Not Implemented">
+ <tp:docstring>
+ Raised when the requested method, channel, etc is not available on this connection.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Invalid Argument">
+ <tp:docstring>
+ Raised when one of the provided arguments is invalid.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Not Available">
+ <tp:docstring>
+ Raised when the requested functionality is temporarily unavailable.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Permission Denied">
+ <tp:docstring>
+ The user is not permitted to perform the requested operation.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Disconnected">
+ <tp:docstring>
+ The connection is not currently connected and cannot be used.
+ This error may also be raised when operations are performed on a
+ Connection for which
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection">StatusChanged</tp:dbus-ref>
+ has signalled status Disconnected for reason None.
+
+ <tp:rationale>
+ The second usage corresponds to None in the
+ <tp:type>Connection_Status_Reason</tp:type> enum; if a better reason
+ is available, the corresponding error should be used instead.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Invalid Handle">
+ <tp:docstring>
+ The handle specified is unknown on this channel or connection.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Channel.Banned">
+ <tp:docstring>
+ You are banned from the channel.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Channel.Full">
+ <tp:docstring>
+ The channel is full.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Channel.Invite Only">
+ <tp:docstring>
+ The requested channel is invite-only.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Not Yours">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The requested channel or other resource already exists, and another
+ user interface in this session is responsible for it.</p>
+
+ <p>User interfaces SHOULD handle this error unobtrusively, since it
+ indicates that some other user interface is already processing the
+ channel.</p>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cancelled">
+ <tp:docstring>
+ Raised by an ongoing request if it is cancelled by user request before
+ it has completed, or when operations are performed on an object which
+ the user has asked to close (for instance, a Connection where the user
+ has called Disconnect, or a Channel where the user has called Close).
+
+ <tp:rationale>
+ The second form can be used to correspond to the Requested member in
+ the <tp:type>Connection_Status_Reason</tp:type> enum, or to
+ to represent the situation where disconnecting a Connection,
+ closing a Channel, etc. has been requested by the user but this
+ request has not yet been acted on, for instance because the
+ service will only act on the request when it has finished processing
+ an event queue.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Authentication Failed">
+ <tp:docstring>
+ Raised when authentication with a service was unsuccessful.
+ <tp:rationale>
+ This corresponds to Authentication_Failed in the
+ <tp:type>Connection_Status_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Encryption Not Available">
+ <tp:docstring>
+ Raised if a user request insisted that encryption should be used,
+ but encryption was not actually available.
+
+ <tp:rationale>
+ This corresponds to part of Encryption_Error in the
+ <tp:type>Connection_Status_Reason</tp:type> enum. It's been separated
+ into a distinct error here because the two concepts that were part
+ of EncryptionError seem to be things that could reasonably appear
+ differently in the UI.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Encryption Error">
+ <tp:docstring>
+ Raised if encryption appears to be available, but could not actually be
+ used (for instance if SSL/TLS negotiation fails).
+ <tp:rationale>
+ This corresponds to part of Encryption_Error in the
+ <tp:type>Connection_Status_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Not Provided">
+ <tp:docstring>
+ Raised if the server did not provide a SSL/TLS certificate. This error
+ MUST NOT be used to represent the absence of a client certificate
+ provided by the Telepathy connection manager.
+ <tp:rationale>
+ This corresponds to Cert_Not_Provided in the
+ <tp:type>Connection_Status_Reason</tp:type> enum. That error
+ explicitly applied only to server SSL certificates, so this one
+ is similarly limited; having the CM present a client certificate
+ is a possible future feature, but it should have its own error
+ handling.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Untrusted">
+ <tp:docstring>
+ Raised if the server provided a SSL/TLS certificate signed by an
+ untrusted certifying authority. This error SHOULD NOT be used to
+ represent a self-signed certificate: see the Self Signed error for that.
+ <tp:rationale>
+ This corresponds to Cert_Untrusted in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Untrusted in the
+ <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum, with a clarification
+ to avoid ambiguity.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Expired">
+ <tp:docstring>
+ Raised if the server provided an expired SSL/TLS certificate.
+ <tp:rationale>
+ This corresponds to Cert_Expired in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Expired in
+ the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Not Activated">
+ <tp:docstring>
+ Raised if the server provided an SSL/TLS certificate that will become
+ valid at some point in the future.
+ <tp:rationale>
+ This corresponds to Cert_Not_Activated in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to
+ Not_Activated in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Fingerprint Mismatch">
+ <tp:docstring>
+ Raised if the server provided an SSL/TLS certificate that did not have
+ the expected fingerprint.
+ <tp:rationale>
+ This corresponds to Cert_Fingerprint_Mismatch in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to
+ Fingerprint_Mismatch in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Hostname Mismatch">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Raised if the server provided an SSL/TLS certificate that did not match
+ its hostname.</p>
+ <p>You MAY be able to get more details about the expected and certified
+ hostnames by looking up the 'expected-hostname' and 'certificate-hostname'
+ keys in the details map that came together with this error.</p>
+ <tp:rationale>
+ This corresponds to Cert_Hostname_Mismatch in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Hostname_Mismatch
+ in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Self Signed">
+ <tp:docstring>
+ Raised if the server provided an SSL/TLS certificate that is self-signed
+ and untrusted.
+ <tp:rationale>
+ This corresponds to Cert_Self_Signed in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Self_Signed
+ in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Revoked">
+ <tp:docstring>
+ Raised if the server provided an SSL/TLS certificate that has been
+ revoked.
+ <tp:rationale>
+ This corresponds to Cert_Revoked in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Revoked
+ in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Insecure">
+ <tp:added version="0.19.11"/>
+ <tp:docstring>
+ Raised if the server provided an SSL/TLS certificate that uses an
+ insecure cipher algorithm or is cryptographically weak.
+ <tp:rationale>
+ This corresponds to Cert_Insecure in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Insecure
+ in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Invalid">
+ <tp:added version="0.19.11"/>
+ <tp:docstring>
+ Raised if the server provided an SSL/TLS certificate that is
+ unacceptable in some way that does not have a more specific error.
+ <tp:rationale>
+ This corresponds to Cert_Other_Error in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Unknown
+ in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Cert.Limit Exceeded">
+ <tp:added version="0.19.11"/>
+ <tp:docstring>
+ Raised if the length in bytes of the server certificate, or the depth of the
+ server certificate chain exceeds the limits imposed by the crypto
+ library.
+ <tp:rationale>
+ This corresponds to Cert_Limit_Exceeded in the
+ <tp:type>Connection_Status_Reason</tp:type> enum and to Limit_Exceeded
+ in the <tp:type>TLS_Certificate_Reject_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Not Capable">
+ <tp:docstring>
+ Raised when requested functionality is unavailable due to contact
+ not having required capabilities.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Offline">
+ <tp:docstring>
+ Raised when requested functionality is unavailable because a contact is
+ offline.
+
+ <tp:rationale>
+ This corresponds to Offline in the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Channel.Kicked">
+ <tp:docstring>
+ Used to represent a user being ejected from a channel by another user,
+ for instance being kicked from a chatroom.
+
+ <tp:rationale>
+ This corresponds to Kicked in the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Busy">
+ <tp:docstring>
+ Used to represent a user being removed from a channel because of a
+ "busy" indication. This error SHOULD NOT be used to represent a server
+ or other infrastructure being too busy to process a request - for that,
+ see ServerBusy.
+
+ <tp:rationale>
+ This corresponds to Busy in the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="No Answer">
+ <tp:docstring>
+ Used to represent a user being removed from a channel because they did
+ not respond, e.g. to a StreamedMedia call.
+
+ <tp:rationale>
+ This corresponds to No_Answer in the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Does Not Exist">
+ <tp:docstring>
+ Raised when the requested user does not, in fact, exist.
+
+ <tp:rationale>
+ This corresponds to Invalid_Contact in the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum, but can also be
+ used to represent other things not existing (like chatrooms, perhaps).
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Terminated">
+ <tp:docstring>
+ Raised when a channel is terminated for an unspecified reason. In
+ particular, this error SHOULD be used whenever normal termination of
+ a 1-1 StreamedMedia call by the remote user is represented as a D-Bus
+ error name.
+
+ <tp:rationale>
+ This corresponds to None in the
+ <tp:type>Channel_Group_Change_Reason</tp:type> enum.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Connection Refused">
+ <tp:docstring>
+ Raised when a connection is refused.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Connection Failed">
+ <tp:docstring>
+ Raised when a connection can't be established.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Connection Lost">
+ <tp:docstring>
+ Raised when a connection is broken.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Already Connected">
+ <tp:docstring>
+ Raised when the user attempts to connect to an account but they are
+ already connected (perhaps from another client or computer), and the
+ protocol or account settings do not allow this.
+
+ <tp:rationale>
+ XMPP can have this behaviour if the user chooses the same resource
+ in both clients (it is server-dependent whether the result is
+ AlreadyConnected on the new connection, ConnectionReplaced on the
+ old connection, or two successful connections).
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Connection Replaced">
+ <tp:docstring>
+ Raised by an existing connection to an account if it is replaced by
+ a new connection (perhaps from another client or computer).
+
+ <tp:rationale>
+ In MSNP, when connecting twice with the same Passport, the new
+ connection "wins" and the old one is automatically disconnected.
+ XMPP can also have this behaviour if the user chooses the same
+ resource in two clients (it is server-dependent whether the result is
+ AlreadyConnected on the new connection, ConnectionReplaced on the
+ old connection, or two successful connections).
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Registration Exists">
+ <tp:docstring>
+ Raised during in-band registration if the server indicates that the
+ requested account already exists.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Service Busy">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ Raised if a server or some other piece of infrastructure cannot process
+ the request, e.g. due to resource limitations. Clients MAY try again
+ later.
+
+ <tp:rationale>
+ This is not the same error as Busy, which indicates that a
+ <em>user</em> is busy.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Resource Unavailable">
+ <tp:docstring>
+ Raised if a request cannot be satisfied because a process local to the
+ user has insufficient resources. Clients MAY try again
+ later.
+
+ <tp:rationale>
+ For instance, the <tp:dbus-ref
+ namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref>
+ might raise this error for some or all channel requests if it has
+ detected that there is not enough free memory.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Would Break Anonymity">
+ <tp:added version="0.19.7"/>
+ <tp:docstring>
+ Raised if a request cannot be satisfied without violating an earlier
+ request for anonymity, and the earlier request specified that raising
+ an error is preferable to disclosing the user's identity (for instance
+ via <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Connection.Interface.Anonymity.AnonymityMandatory</tp:dbus-ref> or
+ <tp:dbus-ref namespace="org.freedesktop.Telepathy"
+ >Channel.Interface.Anonymity.AnonymityMandatory</tp:dbus-ref>).
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Not Yet">
+ <tp:added version="0.19.12"/>
+ <tp:docstring>
+ Raised when the requested functionality is not yet available, but is
+ likely to become available after some time has passed.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Rejected">
+ <tp:added version="0.21.2"/>
+ <tp:docstring>
+ Raised when an incoming or outgoing <tp:dbus-ref
+ namespace="ofdT.Channel.Type">Call.DRAFT</tp:dbus-ref> is
+ rejected by the the receiver.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Picked Up Elsewhere">
+ <tp:added version="0.21.3"/>
+ <tp:docstring>
+ Raised when a call was terminated as a result of the local user
+ picking up the call on a different resource.
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Service Confused">
+ <tp:added version="0.21.5"/>
+ <tp:docstring>
+ Raised when a server or other piece of infrastructure indicates an
+ internal error, or when a message that makes no sense is received from
+ a server or other piece of infrastructure.
+
+ <tp:rationale>
+ For instance, this is appropriate for XMPP's
+ <code>internal-server-error</code>, and is also appropriate if
+ you receive sufficiently inconsistent information from a server that
+ you cannot continue.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Confused">
+ <tp:added version="0.21.5"/>
+ <tp:docstring>
+ Raised if a server rejects protocol messages from a connection manager
+ claiming that they do not make sense, two local processes fail to
+ understand each other, or an apparently impossible situation is
+ reached.
+
+ <tp:rationale>
+ For instance, this would be an appropriate mapping for XMPP's
+ errors bad-format, invalid-xml, etc., which can't happen unless
+ the local (or remote) XMPP implementation is faulty. This is
+ also analogous to
+ <tp:type>Media_Stream_Error</tp:type>_Invalid_CM_Behavior,
+ <code>TP_DBUS_ERROR_INCONSISTENT</code> in telepathy-glib, and
+ <code>TELEPATHY_QT4_ERROR_INCONSISTENT</code> in telepathy-qt4.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Software Upgrade Required">
+ <tp:added version="0.21.12"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Raised as a
+ <tp:dbus-ref namespace="ofdT.Connection">ConnectionError</tp:dbus-ref>
+ when a Connection cannot be established because either the Connection
+ Manager or its support library (e.g. wocky, papyon, sofiasip) requires
+ upgrading to support a newer protocol version.</p>
+
+ <p>This error corresponds to the
+ <tp:type>Connection_Status_Reason</tp:type> of Network_Error.</p>
+
+ <tp:rationale>
+ Some protocols transmit a protocol or library version number to the
+ server, which will disconnect them if the version isn't appropriate.
+ This way we can report the error to the user, and if appropriate, the
+ user's client can check for updates.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:error name="Emergency Calls Not Supported">
+ <tp:added version="0.21.12"/>
+ <tp:docstring>
+ Raised if a client attempts to dial a number that is recognized as an
+ emergency number (e.g. '911' in the USA), but the Connection Manager or
+ provider does not support dialling emergency numbers.
+
+ <tp:rationale>
+ Many VOIP providers have the ability to dial traditional (PSTN)
+ telephone numbers, but do not provide the ability to dial emergency
+ numbers (for instance, Google Voice). This error provides additional
+ information about why such a call was unsuccessful.
+ </tp:rationale>
+ </tp:docstring>
+ </tp:error>
+
+ <tp:copyright>Copyright © 2005-2010 Collabora Limited</tp:copyright>
+ <tp:copyright>Copyright © 2005-2009 Nokia Corporation</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+<p>This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.</p>
+
+<p>This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p>
+ </tp:license>
+</tp:errors>
diff --git a/qt4/spec/generic-types.xml b/qt4/spec/generic-types.xml
new file mode 100644
index 000000000..014f8ada4
--- /dev/null
+++ b/qt4/spec/generic-types.xml
@@ -0,0 +1,215 @@
+<tp:generic-types
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:simple-type name="Unix_Timestamp" type="u">
+ <tp:docstring>An unsigned 32-bit integer representing time as the number
+ of seconds elapsed since the Unix epoch
+ (1970-01-01T00:00:00Z)</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Unix_Timestamp64" type="x">
+ <tp:docstring>An signed 64-bit integer representing time as the number
+ of seconds elapsed since the Unix epoch
+ (1970-01-01T00:00:00Z); negative for times before the epoch</tp:docstring>
+
+ <tp:rationale>The Text interface is the only user of Unix_Timestamp so
+ far, and we'd like to be Y2038 compatible in future
+ interfaces.</tp:rationale>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Bus_Name" type="s"
+ array-name="DBus_Bus_Name_List">
+ <tp:docstring>A string representing a D-Bus bus name - either a well-known
+ name like "org.freedesktop.Telepathy.MissionControl" or a unique name
+ like ":1.123"</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Well_Known_Name" type="s"
+ array-name="DBus_Well_Known_Name_List">
+ <tp:docstring>A string representing a D-Bus well-known
+ name like "org.freedesktop.Telepathy.MissionControl".</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Unique_Name" type="s"
+ array-name="DBus_Unique_Name_List">
+ <tp:docstring>A string representing a D-Bus unique name, such as
+ ":1.123"</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Interface" type="s"
+ array-name="DBus_Interface_List">
+ <tp:docstring>An ASCII string representing a D-Bus interface - two or more
+ elements separated by dots, where each element is a non-empty
+ string of ASCII letters, digits and underscores, not starting with
+ a digit. The maximum total length is 255 characters. For example,
+ "org.freedesktop.DBus.Peer".</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Error_Name" type="s">
+ <tp:docstring>An ASCII string representing a D-Bus error. This is
+ syntactically the same as a <tp:type>DBus_Interface</tp:type>, but the
+ meaning is different.</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Signature" type="s">
+ <tp:docstring>A string representing a D-Bus signature
+ (the 'g' type isn't used because of poor interoperability, particularly
+ with dbus-glib)</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Member" type="s">
+ <tp:docstring>An ASCII string representing a D-Bus method, signal
+ or property name - a non-empty string of ASCII letters, digits and
+ underscores, not starting with a digit, with a maximum length of 255
+ characters. For example, "Ping".</tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="DBus_Qualified_Member" type="s"
+ array-name="DBus_Qualified_Member_List">
+ <tp:docstring>A string representing the full name of a D-Bus method,
+ signal or property, consisting of a DBus_Interface, followed by
+ a dot, followed by a DBus_Member. For example,
+ "org.freedesktop.DBus.Peer.Ping".</tp:docstring>
+ </tp:simple-type>
+
+ <tp:mapping name="Qualified_Property_Value_Map"
+ array-name="Qualified_Property_Value_Map_List">
+ <tp:docstring>A mapping from strings representing D-Bus
+ properties (by their namespaced names) to their values.</tp:docstring>
+ <tp:member type="s" name="Key" tp:type="DBus_Qualified_Member">
+ <tp:docstring>
+ A D-Bus interface name, followed by a dot and a D-Bus property name.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="v" name="Value">
+ <tp:docstring>
+ The value of the property.
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+ <tp:mapping name="String_Variant_Map" array-name="String_Variant_Map_List">
+ <tp:docstring>A mapping from strings to variants representing extra
+ key-value pairs.</tp:docstring>
+ <tp:member type="s" name="Key"/>
+ <tp:member type="v" name="Value"/>
+ </tp:mapping>
+
+ <tp:mapping name="String_String_Map" array-name="String_String_Map_List">
+ <tp:docstring>A mapping from strings to strings representing extra
+ key-value pairs.</tp:docstring>
+ <tp:member type="s" name="Key"/>
+ <tp:member type="s" name="Value"/>
+ </tp:mapping>
+
+ <tp:struct name="Socket_Address_IP" array-name="Socket_Address_IP_List">
+ <tp:docstring>An IP address and port.</tp:docstring>
+ <tp:member type="s" name="Address">
+ <tp:docstring>Either a dotted-quad IPv4 address literal as for
+ <tp:type>Socket_Address_IPv4</tp:type>, or an RFC2373 IPv6 address
+ as for <tp:type>Socket_Address_IPv6</tp:type>.
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="q" name="Port">
+ <tp:docstring>The TCP or UDP port number.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Socket_Address_IPv4">
+ <tp:docstring>An IPv4 address and port.</tp:docstring>
+ <tp:member type="s" name="Address">
+ <tp:docstring>A dotted-quad IPv4 address literal: four ASCII decimal
+ numbers, each between 0 and 255 inclusive, e.g.
+ "192.168.0.1".</tp:docstring>
+ </tp:member>
+ <tp:member type="q" name="Port">
+ <tp:docstring>The TCP or UDP port number.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Socket_Address_IPv6">
+ <tp:docstring>An IPv6 address and port.</tp:docstring>
+ <tp:member type="s" name="Address">
+ <tp:docstring>An IPv6 address literal as specified by RFC2373
+ section 2.2, e.g. "2001:DB8::8:800:200C:4171".</tp:docstring>
+ </tp:member>
+ <tp:member type="q" name="Port">
+ <tp:docstring>The TCP or UDP port number.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Socket_Netmask_IPv4">
+ <tp:docstring>An IPv4 network or subnet.</tp:docstring>
+ <tp:member type="s" name="Address">
+ <tp:docstring>A dotted-quad IPv4 address literal: four ASCII decimal
+ numbers, each between 0 and 255 inclusive, e.g.
+ "192.168.0.1".</tp:docstring>
+ </tp:member>
+ <tp:member type="y" name="Prefix_Length">
+ <tp:docstring>The number of leading bits of the address that must
+ match, for this netmask to be considered to match an
+ address.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:struct name="Socket_Netmask_IPv6">
+ <tp:docstring>An IPv6 network or subnet.</tp:docstring>
+ <tp:member type="s" name="Address">
+ <tp:docstring>An IPv6 address literal as specified by RFC2373
+ section 2.2, e.g. "2001:DB8::8:800:200C:4171".</tp:docstring>
+ </tp:member>
+ <tp:member type="y" name="Prefix_Length">
+ <tp:docstring>The number of leading bits of the address that must
+ match, for this netmask to be considered to match an
+ address.</tp:docstring>
+ </tp:member>
+ </tp:struct>
+
+ <tp:simple-type name="User_Action_Timestamp" type="x">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The time at which an user action occurred. This type has the 2
+ following special values:</p>
+
+ <p>0: the action doesn't involve any user action. Clients
+ SHOULD avoid stealing focus when presenting the channel.</p>
+
+ <p>MAX_INT64: clients SHOULD behave as though the user action happened
+ at the current time, e.g. a client MAY request that its window gains
+ focus.
+ </p>
+
+ <tp:rationale>
+ <p>This can be used by clients that can't know the X server time like
+ command line applications for example.</p>
+ </tp:rationale>
+
+ <p>For all the other values it corresponds to the time of the user
+ action. Clients SHOULD use this for focus-stealing prevention,
+ if applicable.
+ Note that the time is dependant on the local
+ environment and so is not necessarily a wall-clock time.
+ For example in an X environment it's expected to be the X timestamp
+ of events.
+ This corresponds to the _NET_WM_USER_TIME property in
+ <a href="http://standards.freedesktop.org/wm-spec/wm-spec-latest.html">EWMH</a>.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:mapping name="Object_Immutable_Properties_Map"
+ array-name="Object_Immutable_Properties_Map_List">
+ <tp:added version="0.19.12"/>
+ <tp:docstring>A mapping from object path to the immutable properties of
+ the object.</tp:docstring>
+ <tp:member type="o" name="Path">
+ <tp:docstring>
+ The object path of an object
+ </tp:docstring>
+ </tp:member>
+ <tp:member type="a{sv}" name="Immutable_Properties" tp:type="Qualified_Property_Value_Map">
+ <tp:docstring>
+ The immutable properties of the object
+ </tp:docstring>
+ </tp:member>
+ </tp:mapping>
+
+</tp:generic-types>
diff --git a/qt4/spec/template.xml b/qt4/spec/template.xml
new file mode 100644
index 000000000..726472070
--- /dev/null
+++ b/qt4/spec/template.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" ?>
+<node name="/Foo"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+
+ <tp:copyright>Copyright © 2010 Collabora Ltd.</tp:copyright>
+ <tp:license xmlns="http://www.w3.org/1999/xhtml">
+ <p>This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.</p>
+
+ <p>This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.</p>
+
+ <p>You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.</p>
+ </tp:license>
+
+ <interface name="org.freedesktop.Telepathy.Foo.DRAFT"
+ tp:causes-havoc="experimental">
+ <tp:added version="0.21.UNRELEASED">(draft 1)</tp:added>
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Foo.</p>
+ </tp:docstring>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/qt4/tests/CMakeLists.txt b/qt4/tests/CMakeLists.txt
new file mode 100644
index 000000000..b5b4124ff
--- /dev/null
+++ b/qt4/tests/CMakeLists.txt
@@ -0,0 +1,46 @@
+find_program(SH sh)
+
+set(test_environment "
+export abs_top_builddir=${CMAKE_BINARY_DIR}
+export abs_top_srcdir=${CMAKE_SOURCE_DIR}
+export XDG_DATA_HOME=${CMAKE_SOURCE_DIR}/tests
+export XDG_DATA_DIRS=${CMAKE_BINARY_DIR}/tests
+")
+
+# Add targets for callgrind and valgrind tests
+add_custom_target(check-valgrind)
+add_custom_target(check-callgrind)
+
+# Add targets for lcov reports
+add_custom_target(lcov-reset lcov --directory ${CMAKE_BINARY_DIR} --zerocounters
+ COMMAND find ${CMAKE_BINARY_DIR} -name '*.gcda' -exec rm -f '{}' ';' || true
+ COMMENT "Cleaning lcov files")
+
+add_custom_target(lcov-check make test || true
+ COMMAND lcov --directory ${CMAKE_BINARY_DIR} --capture --output-file ${CMAKE_BINARY_DIR}/lcov.info &&
+ mkdir ${CMAKE_BINARY_DIR}/lcov.html || true && genhtml --title ${PACKAGE_NAME}
+ --output-directory ${CMAKE_BINARY_DIR}/lcov.html ${CMAKE_BINARY_DIR}/lcov.info
+ COMMENT "Generating lcov report in file://${CMAKE_BINARY_DIR}/lcov.html/index.html"
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/tests)
+add_dependencies(lcov-check lcov-reset)
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/runGenericTest.sh "${test_environment} $@")
+
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/_gen)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_COVERAGE_FLAGS}")
+
+tpqt4_add_generic_unit_test(Capabilities capabilities)
+tpqt4_add_generic_unit_test(ChannelClassSpec channel-class-spec)
+tpqt4_add_generic_unit_test(Features features)
+tpqt4_add_generic_unit_test(KeyFile key-file)
+tpqt4_add_generic_unit_test(ManagerFile manager-file)
+tpqt4_add_generic_unit_test(Presence presence)
+tpqt4_add_generic_unit_test(Profile profile)
+tpqt4_add_generic_unit_test(Ptr ptr)
+tpqt4_add_generic_unit_test(RCCSpec rccspec)
+tpqt4_add_generic_unit_test(FileTransferChannelCreationProperties file-transfer-channel-creation-properties)
+
+add_subdirectory(dbus-1)
+add_subdirectory(dbus)
+add_subdirectory(lib)
diff --git a/qt4/tests/README b/qt4/tests/README
new file mode 100644
index 000000000..31e6a6fa4
--- /dev/null
+++ b/qt4/tests/README
@@ -0,0 +1,13 @@
+Tests should be divided up by the environment they require in order to operate
+correctly.
+
+Where to put new tests:
+
+* /tests/ if they're simple regression tests in pure C++ that don't access
+ D-Bus (these are likely to be rare)
+
+* /tests/dbus/ if they touch the session bus (a temporary session bus will be
+ used)
+
+/tests/lib/ contains support code, some of it taken from the telepathy-glib
+examples and regression tests.
diff --git a/qt4/tests/capabilities.cpp b/qt4/tests/capabilities.cpp
new file mode 100644
index 000000000..1279eb991
--- /dev/null
+++ b/qt4/tests/capabilities.cpp
@@ -0,0 +1,398 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/CapabilitiesBase>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ContactCapabilities>
+#include <TelepathyQt4/Types>
+
+#include <TelepathyQt4/test-backdoors.h>
+
+using namespace Tp;
+
+class TestCapabilities : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestCapabilities(QObject *parent = 0);
+
+private Q_SLOTS:
+ void testConnCapabilities();
+ void testContactCapabilities();
+};
+
+TestCapabilities::TestCapabilities(QObject *parent)
+ : QObject(parent)
+{
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+}
+
+void TestCapabilities::testConnCapabilities()
+{
+ ConnectionCapabilities connCaps;
+ // capabilities base
+ QVERIFY(!connCaps.isSpecificToContact());
+ QVERIFY(!connCaps.textChats());
+ QVERIFY(!connCaps.streamedMediaCalls());
+ QVERIFY(!connCaps.streamedMediaAudioCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(!connCaps.upgradingStreamedMediaCalls());
+ QVERIFY(!connCaps.fileTransfers());
+ // conn caps specific
+ QVERIFY(!connCaps.textChatrooms());
+ QVERIFY(!connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChats());
+ QVERIFY(!connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+ QVERIFY(!connCaps.contactSearches());
+ QVERIFY(!connCaps.contactSearchesWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchesWithLimit());
+ QVERIFY(!connCaps.contactSearch());
+ QVERIFY(!connCaps.contactSearchWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchWithLimit());
+ QVERIFY(!connCaps.streamTubes());
+
+ RequestableChannelClassSpecList rccSpecs;
+ rccSpecs.append(RequestableChannelClassSpec::textChat());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaCall());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaAudioCall());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaVideoCall());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio());
+ rccSpecs.append(RequestableChannelClassSpec::fileTransfer());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(!connCaps.isSpecificToContact());
+ QVERIFY(connCaps.textChats());
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(connCaps.streamedMediaVideoCalls());
+ QVERIFY(connCaps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(connCaps.fileTransfers());
+ // conn caps specific
+ QVERIFY(!connCaps.textChatrooms());
+ QVERIFY(!connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChats());
+ QVERIFY(!connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+ QVERIFY(!connCaps.contactSearches());
+ QVERIFY(!connCaps.contactSearchesWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchesWithLimit());
+ QVERIFY(!connCaps.contactSearch());
+ QVERIFY(!connCaps.contactSearchWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchWithLimit());
+ QVERIFY(!connCaps.streamTubes());
+ QCOMPARE(connCaps.allClassSpecs(), rccSpecs);
+
+ rccSpecs.append(RequestableChannelClassSpec::textChatroom());
+ rccSpecs.append(RequestableChannelClassSpec::conferenceStreamedMediaCall());
+ rccSpecs.append(RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees());
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChat());
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChatWithInvitees());
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChatroom());
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChatroomWithInvitees());
+ rccSpecs.append(RequestableChannelClassSpec::contactSearch());
+ rccSpecs.append(RequestableChannelClassSpec::contactSearchWithSpecificServer());
+ rccSpecs.append(RequestableChannelClassSpec::contactSearchWithLimit());
+ rccSpecs.append(RequestableChannelClassSpec::contactSearchWithSpecificServerAndLimit());
+ rccSpecs.append(RequestableChannelClassSpec::streamTube());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(!connCaps.isSpecificToContact());
+ QVERIFY(connCaps.textChats());
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(connCaps.streamedMediaVideoCalls());
+ QVERIFY(connCaps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(connCaps.fileTransfers());
+ // conn caps specific
+ QVERIFY(connCaps.textChatrooms());
+ QVERIFY(connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(connCaps.conferenceStreamedMediaCallsWithInvitees());
+ QVERIFY(connCaps.conferenceTextChats());
+ QVERIFY(connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(connCaps.conferenceTextChatrooms());
+ QVERIFY(connCaps.conferenceTextChatroomsWithInvitees());
+ QVERIFY(connCaps.contactSearches());
+ QVERIFY(connCaps.contactSearchesWithSpecificServer());
+ QVERIFY(connCaps.contactSearchesWithLimit());
+ QVERIFY(connCaps.contactSearch());
+ QVERIFY(connCaps.contactSearchWithSpecificServer());
+ QVERIFY(connCaps.contactSearchWithLimit());
+ QCOMPARE(connCaps.allClassSpecs(), rccSpecs);
+
+ // start over
+ rccSpecs.clear();
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaCall());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(!connCaps.streamedMediaAudioCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCallsWithAudio());
+ // conn caps specific
+ QVERIFY(!connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaAudioCall());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCallsWithAudio());
+ // conn caps specific
+ QVERIFY(!connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaVideoCall());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(connCaps.streamedMediaVideoCalls());
+ QVERIFY(!connCaps.streamedMediaVideoCallsWithAudio());
+ // conn caps specific
+ QVERIFY(!connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(connCaps.streamedMediaVideoCalls());
+ QVERIFY(connCaps.streamedMediaVideoCallsWithAudio());
+ // conn caps specific
+ QVERIFY(!connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::conferenceStreamedMediaCall());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(connCaps.streamedMediaVideoCalls());
+ QVERIFY(connCaps.streamedMediaVideoCallsWithAudio());
+ // conn caps specific
+ QVERIFY(connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(!connCaps.conferenceStreamedMediaCallsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.streamedMediaCalls());
+ QVERIFY(connCaps.streamedMediaAudioCalls());
+ QVERIFY(connCaps.streamedMediaVideoCalls());
+ QVERIFY(connCaps.streamedMediaVideoCallsWithAudio());
+ // conn caps specific
+ QVERIFY(connCaps.conferenceStreamedMediaCalls());
+ QVERIFY(connCaps.conferenceStreamedMediaCallsWithInvitees());
+
+ // capabilities base
+ QVERIFY(!connCaps.textChats());
+ QVERIFY(!connCaps.fileTransfers());
+ // conn caps specific
+ QVERIFY(!connCaps.textChatrooms());
+ QVERIFY(!connCaps.conferenceTextChats());
+ QVERIFY(!connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+ QVERIFY(!connCaps.contactSearches());
+ QVERIFY(!connCaps.contactSearchesWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchesWithLimit());
+ QVERIFY(!connCaps.contactSearch());
+ QVERIFY(!connCaps.contactSearchWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchWithLimit());
+ QVERIFY(!connCaps.streamTubes());
+
+ rccSpecs.append(RequestableChannelClassSpec::textChat());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.textChats());
+ // conn caps specific
+ QVERIFY(!connCaps.textChatrooms());
+ QVERIFY(!connCaps.conferenceTextChats());
+ QVERIFY(!connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::textChatroom());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.textChats());
+ // conn caps specific
+ QVERIFY(connCaps.textChatrooms());
+ QVERIFY(!connCaps.conferenceTextChats());
+ QVERIFY(!connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChat());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.textChats());
+ // conn caps specific
+ QVERIFY(connCaps.textChatrooms());
+ QVERIFY(connCaps.conferenceTextChats());
+ QVERIFY(!connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChatWithInvitees());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.textChats());
+ // conn caps specific
+ QVERIFY(connCaps.textChatrooms());
+ QVERIFY(connCaps.conferenceTextChats());
+ QVERIFY(connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(!connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChatroom());
+
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.textChats());
+ // conn caps specific
+ QVERIFY(connCaps.textChatrooms());
+ QVERIFY(connCaps.conferenceTextChats());
+ QVERIFY(connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(connCaps.conferenceTextChatrooms());
+ QVERIFY(!connCaps.conferenceTextChatroomsWithInvitees());
+
+ rccSpecs.append(RequestableChannelClassSpec::conferenceTextChatroomWithInvitees());
+ connCaps = TestBackdoors::createConnectionCapabilities(rccSpecs);
+ // capabilities base
+ QVERIFY(connCaps.textChats());
+ // conn caps specific
+ QVERIFY(connCaps.textChatrooms());
+ QVERIFY(connCaps.conferenceTextChats());
+ QVERIFY(connCaps.conferenceTextChatsWithInvitees());
+ QVERIFY(connCaps.conferenceTextChatrooms());
+ QVERIFY(connCaps.conferenceTextChatroomsWithInvitees());
+
+ // capabilities base
+ QVERIFY(!connCaps.fileTransfers());
+ // conn caps specific
+ QVERIFY(!connCaps.contactSearches());
+ QVERIFY(!connCaps.contactSearchesWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchesWithLimit());
+ QVERIFY(!connCaps.contactSearch());
+ QVERIFY(!connCaps.contactSearchWithSpecificServer());
+ QVERIFY(!connCaps.contactSearchWithLimit());
+ QVERIFY(!connCaps.streamTubes());
+}
+
+void TestCapabilities::testContactCapabilities()
+{
+ ContactCapabilities contactCaps;
+ // capabilities base
+ QVERIFY(!contactCaps.isSpecificToContact());
+ QVERIFY(!contactCaps.textChats());
+ QVERIFY(!contactCaps.streamedMediaCalls());
+ QVERIFY(!contactCaps.streamedMediaAudioCalls());
+ QVERIFY(!contactCaps.streamedMediaVideoCalls());
+ QVERIFY(!contactCaps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(!contactCaps.upgradingStreamedMediaCalls());
+ QVERIFY(!contactCaps.fileTransfers());
+ // contact caps specific
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("foobar")));
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("service-foo")));
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("service-bar")));
+ QVERIFY(contactCaps.streamTubeServices().isEmpty());
+
+ RequestableChannelClassSpecList rccSpecs;
+ rccSpecs.append(RequestableChannelClassSpec::textChat());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaCall());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaAudioCall());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaVideoCall());
+ rccSpecs.append(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio());
+ rccSpecs.append(RequestableChannelClassSpec::fileTransfer());
+
+ contactCaps = TestBackdoors::createContactCapabilities(rccSpecs, true);
+ // capabilities base
+ QVERIFY(contactCaps.isSpecificToContact());
+ QVERIFY(contactCaps.textChats());
+ QVERIFY(contactCaps.streamedMediaCalls());
+ QVERIFY(contactCaps.streamedMediaAudioCalls());
+ QVERIFY(contactCaps.streamedMediaVideoCalls());
+ QVERIFY(contactCaps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(contactCaps.fileTransfers());
+ // contact caps specific
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("foobar")));
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("service-foo")));
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("service-bar")));
+ QVERIFY(contactCaps.streamTubeServices().isEmpty());
+ QCOMPARE(contactCaps.allClassSpecs(), rccSpecs);
+
+ rccSpecs.append(RequestableChannelClassSpec::streamTube(QLatin1String("service-foo")));
+ rccSpecs.append(RequestableChannelClassSpec::streamTube(QLatin1String("service-bar")));
+
+ contactCaps = TestBackdoors::createContactCapabilities(rccSpecs, true);
+ // capabilities base
+ QVERIFY(contactCaps.isSpecificToContact());
+ QVERIFY(contactCaps.textChats());
+ QVERIFY(contactCaps.streamedMediaCalls());
+ QVERIFY(contactCaps.streamedMediaAudioCalls());
+ QVERIFY(contactCaps.streamedMediaVideoCalls());
+ QVERIFY(contactCaps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(contactCaps.fileTransfers());
+ // contact caps specific
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("foobar")));
+ QVERIFY(contactCaps.streamTubes(QLatin1String("service-foo")));
+ QVERIFY(contactCaps.streamTubes(QLatin1String("service-bar")));
+ QStringList stubeServices = contactCaps.streamTubeServices();
+ stubeServices.sort();
+ QStringList expectedSTubeServices;
+ expectedSTubeServices << QLatin1String("service-foo") << QLatin1String("service-bar");
+ expectedSTubeServices.sort();
+ QCOMPARE(stubeServices, expectedSTubeServices);
+ QCOMPARE(contactCaps.allClassSpecs(), rccSpecs);
+
+ rccSpecs.clear();
+ rccSpecs.append(RequestableChannelClassSpec::streamTube(QLatin1String("service-foo")));
+
+ contactCaps = TestBackdoors::createContactCapabilities(rccSpecs, true);
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("foobar")));
+ QVERIFY(contactCaps.streamTubes(QLatin1String("service-foo")));
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("service-bar")));
+ QCOMPARE(contactCaps.streamTubeServices(), QStringList() << QLatin1String("service-foo"));
+
+ rccSpecs.append(RequestableChannelClassSpec::streamTube(QLatin1String("service-bar")));
+
+ contactCaps = TestBackdoors::createContactCapabilities(rccSpecs, true);
+ QVERIFY(!contactCaps.streamTubes(QLatin1String("foobar")));
+ QVERIFY(contactCaps.streamTubes(QLatin1String("service-foo")));
+ QVERIFY(contactCaps.streamTubes(QLatin1String("service-bar")));
+ stubeServices = contactCaps.streamTubeServices();
+ stubeServices.sort();
+ expectedSTubeServices.clear();
+ expectedSTubeServices << QLatin1String("service-foo") << QLatin1String("service-bar");
+ expectedSTubeServices.sort();
+ QCOMPARE(stubeServices, expectedSTubeServices);
+}
+
+QTEST_MAIN(TestCapabilities)
+
+#include "_gen/capabilities.cpp.moc.hpp"
diff --git a/qt4/tests/channel-class-spec.cpp b/qt4/tests/channel-class-spec.cpp
new file mode 100644
index 000000000..9fb6cffc2
--- /dev/null
+++ b/qt4/tests/channel-class-spec.cpp
@@ -0,0 +1,153 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/Types>
+
+using namespace Tp;
+
+namespace {
+
+ChannelClassSpecList reverse(const ChannelClassSpecList &list)
+{
+ ChannelClassSpecList ret(list);
+ for (int k = 0; k < (list.size() / 2); k++) {
+ ret.swap(k, list.size() - (1 + k));
+ }
+ return ret;
+}
+
+};
+
+class TestChannelClassSpec : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestChannelClassSpec(QObject *parent = 0);
+
+private Q_SLOTS:
+ void testChannelClassSpecHash();
+ void testServiceLeaks();
+};
+
+TestChannelClassSpec::TestChannelClassSpec(QObject *parent)
+ : QObject(parent)
+{
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+}
+
+void TestChannelClassSpec::testChannelClassSpecHash()
+{
+ ChannelClassSpec st1 = ChannelClassSpec::textChat();
+ ChannelClassSpec st2 = ChannelClassSpec::textChat();
+ ChannelClassSpec ssm1 = ChannelClassSpec::streamedMediaCall();
+ ChannelClassSpec ssm2 = ChannelClassSpec::streamedMediaCall();
+
+ QCOMPARE(qHash(st1), qHash(st2));
+ QCOMPARE(qHash(ssm1), qHash(ssm2));
+ QVERIFY(qHash(st1) != qHash(ssm1));
+
+ // hash of list with duplicated elements should be the same as hash of the list of same items
+ // but with no duplicates
+ ChannelClassSpecList sl1;
+ sl1 << st1 << st2;
+ ChannelClassSpecList sl2;
+ sl2 << st1;
+ QCOMPARE(qHash(sl1), qHash(sl2));
+
+ // hash of list with same elements but different order should be the same
+ sl1.clear();
+ sl2.clear();
+ sl1 << st1 << ssm1;
+ sl2 << ssm1 << st1;
+ QCOMPARE(qHash(sl1), qHash(sl2));
+
+ // still the same but with duplicated elements
+ sl2 << ssm2 << st2;
+ QCOMPARE(qHash(sl1), qHash(sl2));
+ sl1 << st2;
+ QCOMPARE(qHash(sl1), qHash(sl2));
+
+ // now sl2 is different from sl1, hash should be different
+ sl2 << ChannelClassSpec::unnamedTextChat();
+ QVERIFY(qHash(sl1) != qHash(sl2));
+
+ // same again
+ sl1.prepend(ChannelClassSpec::unnamedTextChat());
+ QCOMPARE(qHash(sl1), qHash(sl2));
+
+ sl1.clear();
+ sl2.clear();
+
+ for (int i = 0; i < 100; ++i) {
+ sl1 << ChannelClassSpec::textChat() <<
+ ChannelClassSpec::streamedMediaCall() <<
+ ChannelClassSpec::unnamedTextChat();
+ }
+
+ ChannelClassSpec specs[3] = {
+ ChannelClassSpec::textChat(),
+ ChannelClassSpec::streamedMediaCall(),
+ ChannelClassSpec::unnamedTextChat()
+ };
+ for (int i = 0; i < 3; ++i) {
+ ChannelClassSpec spec = specs[i];
+ for (int j = 0; j < 100; ++j) {
+ sl2 << spec;
+ }
+ }
+
+ QCOMPARE(qHash(sl1), qHash(ChannelClassSpecList() <<
+ ChannelClassSpec::unnamedTextChat() <<
+ ChannelClassSpec::streamedMediaCall() <<
+ ChannelClassSpec::textChat()));
+
+ for (int i = 0; i < 1000; ++i) {
+ ChannelClassSpec spec = ChannelClassSpec::outgoingStreamTube(QString::number(i));
+ sl1 << spec;
+ sl2.prepend(spec);
+ }
+
+ QCOMPARE(qHash(sl1), qHash(sl2));
+
+ sl1 = reverse(sl1);
+ sl2 = reverse(sl2);
+ QCOMPARE(qHash(sl1), qHash(sl2));
+
+ sl2 << ChannelClassSpec::outgoingFileTransfer();
+ QVERIFY(qHash(sl1) != qHash(sl2));
+}
+
+void TestChannelClassSpec::testServiceLeaks()
+{
+ ChannelClassSpec bareTube = ChannelClassSpec::outgoingStreamTube();
+ QVERIFY(!bareTube.allProperties().contains(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")));
+
+ ChannelClassSpec ftpTube = ChannelClassSpec::outgoingStreamTube(QLatin1String("ftp"));
+ QVERIFY(ftpTube.allProperties().contains(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")));
+ QCOMPARE(ftpTube.allProperties().value(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")).toString(), QString::fromLatin1("ftp"));
+ QVERIFY(!bareTube.allProperties().contains(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")));
+
+ ChannelClassSpec httpTube = ChannelClassSpec::outgoingStreamTube(QLatin1String("http"));
+ QVERIFY(httpTube.allProperties().contains(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")));
+ QVERIFY(ftpTube.allProperties().contains(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")));
+ QCOMPARE(httpTube.allProperties().value(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")).toString(), QString::fromLatin1("http"));
+ QCOMPARE(ftpTube.allProperties().value(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")).toString(), QString::fromLatin1("ftp"));
+ QVERIFY(!bareTube.allProperties().contains(TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE +
+ QString::fromLatin1(".Service")));
+}
+
+QTEST_MAIN(TestChannelClassSpec)
+
+#include "_gen/channel-class-spec.cpp.moc.hpp"
diff --git a/qt4/tests/dbus-1/CMakeLists.txt b/qt4/tests/dbus-1/CMakeLists.txt
new file mode 100644
index 000000000..a8b067f8c
--- /dev/null
+++ b/qt4/tests/dbus-1/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(abs_top_builddir ${CMAKE_BINARY_DIR})
+configure_file(session.conf.in ${CMAKE_CURRENT_BINARY_DIR}/session.conf)
+
+add_subdirectory(services)
diff --git a/qt4/tests/dbus-1/services/CMakeLists.txt b/qt4/tests/dbus-1/services/CMakeLists.txt
new file mode 100644
index 000000000..00e0b2994
--- /dev/null
+++ b/qt4/tests/dbus-1/services/CMakeLists.txt
@@ -0,0 +1,4 @@
+set(TEST_PYTHON ${PYTHON_EXECUTABLE})
+set(abs_top_srcdir ${CMAKE_SOURCE_DIR})
+configure_file(account-manager.service.in ${CMAKE_CURRENT_BINARY_DIR}/account-manager.service)
+configure_file(spurious.service.in ${CMAKE_CURRENT_BINARY_DIR}/spurious.service)
diff --git a/qt4/tests/dbus-1/services/account-manager.service.in b/qt4/tests/dbus-1/services/account-manager.service.in
new file mode 100644
index 000000000..bccad6168
--- /dev/null
+++ b/qt4/tests/dbus-1/services/account-manager.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.AccountManager
+Exec=@TEST_PYTHON@ @abs_top_srcdir@/tests/lib/python/account-manager.py
diff --git a/qt4/tests/dbus-1/services/spurious.service.in b/qt4/tests/dbus-1/services/spurious.service.in
new file mode 100644
index 000000000..d0d70aa04
--- /dev/null
+++ b/qt4/tests/dbus-1/services/spurious.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.ConnectionManager.spurious
+Exec=/bin/false
diff --git a/qt4/tests/dbus-1/session.conf.in b/qt4/tests/dbus-1/session.conf.in
new file mode 100644
index 000000000..6babc1610
--- /dev/null
+++ b/qt4/tests/dbus-1/session.conf.in
@@ -0,0 +1,30 @@
+<!-- Copied from telepathy-gabble (which doubtless copied it from somewhere
+ else) and modified.
+ This configuration file controls the per-user-login-session message bus.
+ Add a session-local.conf and edit that rather than changing this
+ file directly. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <!-- Our well-known bus type, don't change this -->
+ <type>session</type>
+
+ <listen>unix:tmpdir=/tmp</listen>
+
+ <servicedir>@abs_top_builddir@/tests/dbus-1/services/</servicedir>
+
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*" eavesdrop="true"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ </policy>
+
+ <!-- This is included last so local configuration can override what's
+ in this standard file -->
+
+
+</busconfig>
diff --git a/qt4/tests/dbus/CMakeLists.txt b/qt4/tests/dbus/CMakeLists.txt
new file mode 100644
index 000000000..35cea92d4
--- /dev/null
+++ b/qt4/tests/dbus/CMakeLists.txt
@@ -0,0 +1,67 @@
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen")
+
+tpqt4_setup_dbus_test_environment()
+
+if(HAVE_TEST_PYTHON)
+ tpqt4_add_dbus_unit_test(DBusProperties dbus-properties "")
+endif(HAVE_TEST_PYTHON)
+
+if(ENABLE_TP_GLIB_TESTS)
+ include_directories(${TELEPATHY_GLIB_INCLUDE_DIR}
+ ${GLIB2_INCLUDE_DIR}
+ ${DBUS_INCLUDE_DIR})
+
+ add_definitions(-DQT_NO_KEYWORDS)
+
+ if(HAVE_TEST_PYTHON)
+ tpqt4_add_dbus_unit_test(AccountBasics account-basics tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(AccountSet account-set tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(AccountChannelDispatcher account-channel-dispatcher tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(Client client tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ClientFactories client-factories tp-glib-tests)
+ endif(HAVE_TEST_PYTHON)
+
+ tpqt4_add_dbus_unit_test(AccountConnectionFactory account-connection-factory tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ChannelBasics chan-basics tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ChannelConference chan-conference tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ChannelGroup chan-group tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ConnectionManagerBasics cm-basics tp-glib-tests)
+ tpqt4_add_dbus_unit_test(ConnectionBasics conn-basics tp-glib-tests)
+ tpqt4_add_dbus_unit_test(ConnectionCapabilities conn-capabilities tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ConnectionIntrospectCornercases conn-introspect-cornercases tp-glib-tests)
+ tpqt4_add_dbus_unit_test(ConnectionRequests conn-requests tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ConnectionRosterLegacy conn-roster-legacy tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ConnectionRoster conn-roster example-cm-contactlist2 tp-qt4-tests-glib-helpers
+ ${GLIB2_LIBRARIES} ${GOBJECT_LIBRARIES} ${DBUS_GLIB_LIBRARIES} ${TELEPATHY_GLIB_LIBRARIES})
+ tpqt4_add_dbus_unit_test(ConnectionRosterGroupsLegacy conn-roster-groups-legacy tp-glib-tests)
+ tpqt4_add_dbus_unit_test(ConnectionRosterGroups conn-roster-groups example-cm-contactlist2
+ ${GLIB2_LIBRARIES} ${GOBJECT_LIBRARIES} ${DBUS_GLIB_LIBRARIES} ${TELEPATHY_GLIB_LIBRARIES})
+ tpqt4_add_dbus_unit_test(ContactFactory contact-factory tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ContactMessenger contact-messenger tp-glib-tests)
+ tpqt4_add_dbus_unit_test(ContactSearchChannel contact-search-chan tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(Contacts contacts tp-glib-tests)
+ tpqt4_add_dbus_unit_test(ContactsAvatar contacts-avatar tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ContactsCapabilities contacts-capabilities tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ContactsInfo contacts-info tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(ContactsLocation contacts-location tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(DBusProxyFactory dbus-proxy-factory tp-glib-tests)
+ tpqt4_add_dbus_unit_test(Handles handles tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(SimpleObserver simple-observer tp-glib-tests)
+ tpqt4_add_dbus_unit_test(StatefulProxy stateful-proxy tp-glib-tests)
+ tpqt4_add_dbus_unit_test(StreamedMediaChannel streamed-media-chan tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(StreamTubeHandlers stream-tube-handlers tp-glib-tests tp-qt4-tests-glib-helpers)
+ tpqt4_add_dbus_unit_test(TextChannel text-chan tp-glib-tests tp-qt4-tests-glib-helpers)
+
+ if(ENABLE_TP_GLIB_GIO_TESTS)
+ tpqt4_add_dbus_unit_test(StreamTubeChannel stream-tube-chan tp-glib-tests tp-qt4-tests-glib-helpers)
+ endif(ENABLE_TP_GLIB_GIO_TESTS)
+endif(ENABLE_TP_GLIB_TESTS)
+
+tpqt4_add_dbus_unit_test(ProfileManager profile-manager)
+tpqt4_add_dbus_unit_test(Types types)
+
+# Make check target. In case of check, output on failure and put it into a log
+# This target has to stay here for catching all of the tests
+add_custom_target(check ctest --output-on-failure -O test.log
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/tests)
+add_dependencies(check check-local ${_telepathy_qt4_test_cases})
diff --git a/qt4/tests/dbus/account-basics.cpp b/qt4/tests/dbus/account-basics.cpp
new file mode 100644
index 000000000..f68f8728e
--- /dev/null
+++ b/qt4/tests/dbus/account-basics.cpp
@@ -0,0 +1,592 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo2/conn.h>
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/AccountSet>
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingStringList>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/Profile>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestAccountBasics : public Test
+{
+ Q_OBJECT
+
+public:
+ TestAccountBasics(QObject *parent = 0)
+ : Test(parent),
+ mConn(0),
+ mAccountsCount(0)
+ { }
+
+protected Q_SLOTS:
+ void onNewAccount(const Tp::AccountPtr &);
+ void onAccountServiceNameChanged(const QString &);
+ void onAccountDisplayNameChanged(const QString &);
+ void onAccountIconNameChanged(const QString &);
+ void onAccountNicknameChanged(const QString &);
+ void onAccountAvatarChanged(const Tp::Avatar &);
+ void onAccountParametersChanged(const QVariantMap &);
+ void onAccountCapabilitiesChanged(const Tp::ConnectionCapabilities &);
+ void onAccountConnectsAutomaticallyChanged(bool);
+ void onAccountAutomaticPresenceChanged(const Tp::Presence &);
+ void onAccountRequestedPresenceChanged(const Tp::Presence &);
+ void onAccountCurrentPresenceChanged(const Tp::Presence &);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testBasics();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QStringList pathsForAccounts(const QList<AccountPtr> &list);
+ QStringList pathsForAccounts(const AccountSetPtr &set);
+
+ Tp::AccountManagerPtr mAM;
+ TestConnHelper *mConn;
+ int mAccountsCount;
+
+ QHash<QString, QVariant> mProps;
+};
+
+#define TEST_VERIFY_PROPERTY_CHANGE(acc, Type, PropertyName, propertyName, expectedValue) \
+ TEST_VERIFY_PROPERTY_CHANGE_EXTENDED(acc, Type, PropertyName, propertyName, \
+ propertyName ## Changed, acc->set ## PropertyName(expectedValue), expectedValue)
+
+#define TEST_VERIFY_PROPERTY_CHANGE_EXTENDED(acc, Type, PropertyName, propertyName, signalName, po, expectedValue) \
+ { \
+ mProps.clear(); \
+ qDebug().nospace() << "connecting to " << #propertyName << "Changed()"; \
+ QVERIFY(connect(acc.data(), \
+ SIGNAL(signalName(Type)), \
+ SLOT(onAccount ## PropertyName ## Changed(Type)))); \
+ qDebug().nospace() << "setting " << #propertyName; \
+ QVERIFY(connect(po, \
+ SIGNAL(finished(Tp::PendingOperation*)), \
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*)))); \
+ QCOMPARE(mLoop->exec(), 0); \
+ \
+ if (!mProps.contains(QLatin1String(#PropertyName))) { \
+ qDebug().nospace() << "waiting for the " << #propertyName << "Changed signal"; \
+ QCOMPARE(mLoop->exec(), 0); \
+ } else { \
+ qDebug().nospace() << "not waiting for " << #propertyName << "Changed because we already got it"; \
+ } \
+ \
+ QCOMPARE(acc->propertyName(), expectedValue); \
+ QCOMPARE(acc->propertyName(), \
+ mProps[QLatin1String(#PropertyName)].value< Type >()); \
+ \
+ QVERIFY(disconnect(acc.data(), \
+ SIGNAL(signalName(Type)), \
+ this, \
+ SLOT(onAccount ## PropertyName ## Changed(Type)))); \
+ processDBusQueue(acc.data()); \
+ }
+
+#define TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(Type, PropertyName) \
+void TestAccountBasics::onAccount ## PropertyName ## Changed(Type value) \
+{ \
+ mProps[QLatin1String(#PropertyName)] = qVariantFromValue(value); \
+ mLoop->exit(0); \
+}
+
+void TestAccountBasics::onNewAccount(const Tp::AccountPtr &acc)
+{
+ Q_UNUSED(acc);
+
+ mAccountsCount++;
+ mLoop->exit(0);
+}
+
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const QString &, ServiceName)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const QString &, DisplayName)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const QString &, IconName)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const QString &, Nickname)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const Avatar &, Avatar)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const QVariantMap &, Parameters)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const ConnectionCapabilities &, Capabilities)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(bool, ConnectsAutomatically)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const Presence &, AutomaticPresence)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const Presence &, RequestedPresence)
+TEST_IMPLEMENT_PROPERTY_CHANGE_SLOT(const Presence &, CurrentPresence)
+
+QStringList TestAccountBasics::pathsForAccounts(const QList<AccountPtr> &list)
+{
+ QStringList ret;
+ Q_FOREACH (const AccountPtr &account, list) {
+ ret << account->objectPath();
+ }
+ return ret;
+}
+
+QStringList TestAccountBasics::pathsForAccounts(const AccountSetPtr &set)
+{
+ QStringList ret;
+ Q_FOREACH (const AccountPtr &account, set->accounts()) {
+ ret << account->objectPath();
+ }
+ return ret;
+}
+
+void TestAccountBasics::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("account-basics");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mAM = AccountManager::create(AccountFactory::create(QDBusConnection::sessionBus(),
+ Account::FeatureCore | Account::FeatureCapabilities));
+ QVERIFY(!mAM->isReady());
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "echo2",
+ NULL);
+ QVERIFY(mConn->connect());
+}
+
+void TestAccountBasics::init()
+{
+ mProps.clear();
+
+ initImpl();
+}
+
+void TestAccountBasics::testBasics()
+{
+ QVERIFY(connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mAM->isReady());
+ QCOMPARE(mAM->interfaces(), QStringList());
+ QCOMPARE(mAM->supportedAccountProperties(), QStringList() <<
+ QLatin1String("org.freedesktop.Telepathy.Account.Enabled"));
+
+ QVERIFY(connect(mAM.data(),
+ SIGNAL(newAccount(const Tp::AccountPtr &)),
+ SLOT(onNewAccount(const Tp::AccountPtr &))));
+
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ PendingAccount *pacc = mAM->createAccount(QLatin1String("foo"),
+ QLatin1String("bar"), QLatin1String("foobar"), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pacc->account());
+
+ while (mAccountsCount != 1) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ processDBusQueue(mConn->client().data());
+
+ QStringList paths;
+ QString accPath(QLatin1String("/org/freedesktop/Telepathy/Account/foo/bar/Account0"));
+ QCOMPARE(pathsForAccounts(mAM->allAccounts()), QStringList() << accPath);
+ QList<AccountPtr> accs = mAM->accountsForPaths(
+ QStringList() << accPath << QLatin1String("/invalid/path"));
+ QCOMPARE(accs.size(), 2);
+ QCOMPARE(accs[0]->objectPath(), accPath);
+ QVERIFY(!accs[1]);
+
+ QVERIFY(mAM->allAccounts()[0]->isReady(
+ Account::FeatureCore | Account::FeatureCapabilities));
+
+ AccountPtr acc = Account::create(mAM->dbusConnection(), mAM->busName(),
+ QLatin1String("/org/freedesktop/Telepathy/Account/foo/bar/Account0"),
+ mAM->connectionFactory(), mAM->channelFactory(), mAM->contactFactory());
+ QVERIFY(connect(acc->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady());
+
+ QCOMPARE(acc->connectionFactory(), mAM->connectionFactory());
+ QCOMPARE(acc->channelFactory(), mAM->channelFactory());
+ QCOMPARE(acc->contactFactory(), mAM->contactFactory());
+ QVERIFY(acc->isValidAccount());
+ QVERIFY(acc->isEnabled());
+ QCOMPARE(acc->cmName(), QLatin1String("foo"));
+ QCOMPARE(acc->protocolName(), QLatin1String("bar"));
+ // Service name is empty, fallback to protocol name
+ QCOMPARE(acc->serviceName(), QLatin1String("bar"));
+ // FeatureProfile not ready yet
+ QVERIFY(!acc->profile());
+ QCOMPARE(acc->displayName(), QString(QLatin1String("foobar (account 0)")));
+ QCOMPARE(acc->iconName(), QLatin1String("bob.png"));
+ QCOMPARE(acc->nickname(), QLatin1String("Bob"));
+ // FeatureProtocolInfo not ready yet
+ QVERIFY(!acc->avatarRequirements().isValid());
+ // FeatureAvatar not ready yet
+ QVERIFY(acc->avatar().avatarData.isEmpty());
+ QVERIFY(acc->avatar().MIMEType.isEmpty());
+ QCOMPARE(acc->parameters().size(), 1);
+ QVERIFY(acc->parameters().contains(QLatin1String("account")));
+ QCOMPARE(qdbus_cast<QString>(acc->parameters().value(QLatin1String("account"))),
+ QLatin1String("foobar"));
+ // FeatureProtocolInfo not ready yet
+ QVERIFY(!acc->protocolInfo().isValid());
+ // FeatureCapabilities not ready yet
+ ConnectionCapabilities caps = acc->capabilities();
+ QVERIFY(!caps.isSpecificToContact());
+ QVERIFY(!caps.textChats());
+ QVERIFY(!caps.streamedMediaCalls());
+ QVERIFY(!caps.streamedMediaAudioCalls());
+ QVERIFY(!caps.streamedMediaVideoCalls());
+ QVERIFY(!caps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(!caps.upgradingStreamedMediaCalls());
+ QVERIFY(!caps.fileTransfers());
+ QVERIFY(!caps.textChatrooms());
+ QVERIFY(!caps.conferenceStreamedMediaCalls());
+ QVERIFY(!caps.conferenceStreamedMediaCallsWithInvitees());
+ QVERIFY(!caps.conferenceTextChats());
+ QVERIFY(!caps.conferenceTextChatsWithInvitees());
+ QVERIFY(!caps.conferenceTextChatrooms());
+ QVERIFY(!caps.conferenceTextChatroomsWithInvitees());
+ QVERIFY(!caps.contactSearches());
+ QVERIFY(!caps.contactSearchesWithSpecificServer());
+ QVERIFY(!caps.contactSearchesWithLimit());
+ QVERIFY(!caps.streamTubes());
+ QVERIFY(caps.allClassSpecs().isEmpty());
+ QVERIFY(!acc->connectsAutomatically());
+ QVERIFY(!acc->hasBeenOnline());
+ QCOMPARE(acc->connectionStatus(), ConnectionStatusDisconnected);
+ QCOMPARE(acc->connectionStatusReason(), ConnectionStatusReasonNoneSpecified);
+ QVERIFY(acc->connectionError().isEmpty());
+ QVERIFY(!acc->connectionErrorDetails().isValid());
+ QVERIFY(acc->connectionErrorDetails().allDetails().isEmpty());
+ QVERIFY(!acc->connection());
+ QVERIFY(!acc->isChangingPresence());
+ // Neither FeatureProtocolInfo or FeatureProfile are ready yet and we have no connection
+ PresenceSpecList expectedPresences;
+ {
+ SimpleStatusSpec prSpec = { ConnectionPresenceTypeAvailable, true, false };
+ expectedPresences.append(PresenceSpec(QLatin1String("available"), prSpec));
+ }
+ {
+ SimpleStatusSpec prSpec = { ConnectionPresenceTypeOffline, true, false };
+ expectedPresences.append(PresenceSpec(QLatin1String("offline"), prSpec));
+ }
+ qSort(expectedPresences);
+
+ PresenceSpecList presences = acc->allowedPresenceStatuses(false);
+ qSort(presences);
+ QCOMPARE(presences.size(), 2);
+ QCOMPARE(presences, expectedPresences);
+
+ presences = acc->allowedPresenceStatuses(true);
+ qSort(presences);
+ QCOMPARE(presences.size(), 2);
+ QCOMPARE(presences, expectedPresences);
+
+ // No connection
+ QCOMPARE(acc->maxPresenceStatusMessageLength(), static_cast<uint>(0));
+ QCOMPARE(acc->automaticPresence(), Presence::available());
+ QCOMPARE(acc->currentPresence(), Presence::offline());
+ QCOMPARE(acc->requestedPresence(), Presence::offline());
+ QVERIFY(!acc->isOnline());
+ QCOMPARE(acc->uniqueIdentifier(), QLatin1String("foo/bar/Account0"));
+ QCOMPARE(acc->normalizedName(), QLatin1String("bob"));
+
+ TEST_VERIFY_PROPERTY_CHANGE(acc, QString, DisplayName, displayName, QLatin1String("foo@bar"));
+
+ TEST_VERIFY_PROPERTY_CHANGE(acc, QString, IconName, iconName, QLatin1String("im-foo"));
+
+ // Setting icon to an empty string should fallback to im-$protocol as FeatureProtocolInfo and
+ // FeatureProtocolInfo are not ready yet
+ TEST_VERIFY_PROPERTY_CHANGE_EXTENDED(acc, QString, IconName, iconName, iconNameChanged,
+ acc->setIconName(QString()), QLatin1String("im-bar"));
+
+ TEST_VERIFY_PROPERTY_CHANGE(acc, QString, Nickname, nickname, QLatin1String("Bob rocks!"));
+
+ qDebug() << "making Account::FeatureAvatar ready";
+ QVERIFY(connect(acc->becomeReady(Account::FeatureAvatar),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady(Account::FeatureAvatar));
+
+ Avatar expectedAvatar = { QByteArray("asdfg"), QLatin1String("image/jpeg") };
+ TEST_VERIFY_PROPERTY_CHANGE(acc, Tp::Avatar, Avatar, avatar, expectedAvatar);
+
+ QVariantMap expectedParameters = acc->parameters();
+ expectedParameters[QLatin1String("foo")] = QLatin1String("bar");
+ TEST_VERIFY_PROPERTY_CHANGE_EXTENDED(acc, QVariantMap, Parameters, parameters,
+ parametersChanged, acc->updateParameters(expectedParameters, QStringList()), expectedParameters);
+
+ TEST_VERIFY_PROPERTY_CHANGE_EXTENDED(acc, bool,
+ ConnectsAutomatically, connectsAutomatically, connectsAutomaticallyPropertyChanged,
+ acc->setConnectsAutomatically(true), true);
+
+ TEST_VERIFY_PROPERTY_CHANGE(acc, Tp::Presence, AutomaticPresence, automaticPresence,
+ Presence::busy());
+
+ // Changing requested presence will also change hasBeenOnline/isOnline/currentPresence
+ Presence expectedPresence = Presence::busy();
+ TEST_VERIFY_PROPERTY_CHANGE(acc, Tp::Presence, RequestedPresence, requestedPresence,
+ expectedPresence);
+ QVERIFY(acc->hasBeenOnline());
+ QVERIFY(acc->isOnline());
+ QCOMPARE(acc->currentPresence(), expectedPresence);
+
+ qDebug() << "creating another account";
+ pacc = mAM->createAccount(QLatin1String("spurious"),
+ QLatin1String("normal"), QLatin1String("foobar"), QVariantMap());
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ while (mAccountsCount != 2) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ processDBusQueue(mConn->client().data());
+
+ acc = Account::create(mAM->dbusConnection(), mAM->busName(),
+ QLatin1String("/org/freedesktop/Telepathy/Account/spurious/normal/Account0"),
+ mAM->connectionFactory(), mAM->channelFactory(), mAM->contactFactory());
+ QVERIFY(connect(acc->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady());
+
+ QCOMPARE(acc->iconName(), QLatin1String("bob.png"));
+ // Setting icon to an empty string should fallback to Profile/ProtocolInfo/im-$protocol
+ TEST_VERIFY_PROPERTY_CHANGE_EXTENDED(acc, QString, IconName, iconName, iconNameChanged,
+ acc->setIconName(QString()), QLatin1String("im-normal"));
+
+ QVERIFY(connect(acc->becomeReady(Account::FeatureProtocolInfo),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady(Account::FeatureProtocolInfo));
+
+ // This time it's fetched from the protocol object (although it probably internally just
+ // infers it from the protocol name too)
+ QCOMPARE(acc->iconName(), QLatin1String("im-normal"));
+
+ ProtocolInfo protocolInfo = acc->protocolInfo();
+ QVERIFY(protocolInfo.isValid());
+ QCOMPARE(protocolInfo.iconName(), QLatin1String("im-normal"));
+ QVERIFY(protocolInfo.hasParameter(QLatin1String("account")));
+ QVERIFY(protocolInfo.hasParameter(QLatin1String("password")));
+ QVERIFY(protocolInfo.hasParameter(QLatin1String("register")));
+ QVERIFY(!protocolInfo.hasParameter(QLatin1String("bogusparam")));
+ QCOMPARE(protocolInfo.parameters().size(), 3);
+
+ QVERIFY(connect(acc->becomeReady(Account::FeatureProfile),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady(Account::FeatureProfile));
+
+ ProfilePtr profile = acc->profile();
+ QVERIFY(!profile.isNull());
+ QVERIFY(profile->isFake());
+ QVERIFY(profile->isValid());
+ QCOMPARE(profile->serviceName(), QString(QLatin1String("%1-%2"))
+ .arg(acc->cmName()).arg(acc->serviceName()));
+ QCOMPARE(profile->type(), QLatin1String("IM"));
+ QCOMPARE(profile->provider(), QString());
+ QCOMPARE(profile->name(), acc->protocolName());
+ QCOMPARE(profile->cmName(), acc->cmName());
+ QCOMPARE(profile->protocolName(), acc->protocolName());
+ QVERIFY(!profile->parameters().isEmpty());
+ QVERIFY(profile->allowOtherPresences());
+ QVERIFY(profile->presences().isEmpty());
+ QVERIFY(profile->unsupportedChannelClassSpecs().isEmpty());
+
+ QCOMPARE(acc->serviceName(), acc->protocolName());
+ TEST_VERIFY_PROPERTY_CHANGE(acc, QString, ServiceName, serviceName,
+ QLatin1String("spurious-service"));
+
+ QVERIFY(connect(acc->becomeReady(Account::FeatureAvatar),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady(Account::FeatureAvatar));
+ QVERIFY(acc->avatar().avatarData.isEmpty());
+ QCOMPARE(acc->avatar().MIMEType, QString(QLatin1String("image/png")));
+
+ // make redundant becomeReady calls
+ QVERIFY(connect(acc->becomeReady(Account::FeatureAvatar | Account::FeatureProtocolInfo),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady(Account::FeatureAvatar | Account::FeatureProtocolInfo));
+
+ QVERIFY(acc->avatar().avatarData.isEmpty());
+ QCOMPARE(acc->avatar().MIMEType, QString(QLatin1String("image/png")));
+ protocolInfo = acc->protocolInfo();
+ QVERIFY(protocolInfo.isValid());
+ QCOMPARE(protocolInfo.iconName(), QLatin1String("im-normal"));
+ QVERIFY(protocolInfo.hasParameter(QLatin1String("account")));
+ QVERIFY(protocolInfo.hasParameter(QLatin1String("password")));
+ QVERIFY(protocolInfo.hasParameter(QLatin1String("register")));
+
+ QVERIFY(connect(acc->becomeReady(Account::FeatureCapabilities),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady(Account::FeatureCapabilities));
+
+ // using protocol info
+ caps = acc->capabilities();
+ QVERIFY(caps.textChats());
+
+ // set new service name will change caps, icon and serviceName
+ QVERIFY(connect(acc.data(),
+ SIGNAL(capabilitiesChanged(const Tp::ConnectionCapabilities &)),
+ SLOT(onAccountCapabilitiesChanged(const Tp::ConnectionCapabilities &))));
+ TEST_VERIFY_PROPERTY_CHANGE(acc, QString, ServiceName, serviceName,
+ QLatin1String("test-profile"));
+ while (!mProps.contains(QLatin1String("IconName")) &&
+ !mProps.contains(QLatin1String("Capabilities"))) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ // Now that both FeatureProtocolInfo and FeatureProfile are ready, let's check the allowed
+ // presences
+ expectedPresences.clear();
+ {
+ SimpleStatusSpec prSpec = { ConnectionPresenceTypeAvailable, true, true };
+ expectedPresences.append(PresenceSpec(QLatin1String("available"), prSpec));
+ }
+ {
+ SimpleStatusSpec prSpec = { ConnectionPresenceTypeAway, true, true };
+ expectedPresences.append(PresenceSpec(QLatin1String("away"), prSpec));
+ }
+ {
+ SimpleStatusSpec prSpec = { ConnectionPresenceTypeOffline, true, false };
+ expectedPresences.append(PresenceSpec(QLatin1String("offline"), prSpec));
+ }
+ qSort(expectedPresences);
+
+ presences = acc->allowedPresenceStatuses(false);
+ qSort(presences);
+ QCOMPARE(presences.size(), 3);
+ QCOMPARE(presences, expectedPresences);
+
+ {
+ SimpleStatusSpec prSpec = { ConnectionPresenceTypeExtendedAway, false, false };
+ expectedPresences.append(PresenceSpec(QLatin1String("xa"), prSpec));
+ }
+ qSort(expectedPresences);
+
+ presences = acc->allowedPresenceStatuses(true);
+ qSort(presences);
+ QCOMPARE(presences.size(), 4);
+ QCOMPARE(presences, expectedPresences);
+
+ QCOMPARE(acc->iconName(), QLatin1String("test-profile-icon"));
+
+ // using merged protocol info caps and profile caps
+ caps = acc->capabilities();
+ QVERIFY(!caps.textChats());
+
+ Client::DBus::PropertiesInterface *accPropertiesInterface =
+ acc->interface<Client::DBus::PropertiesInterface>();
+
+ // simulate that the account has a connection
+ QVERIFY(connect(new PendingVoid(
+ accPropertiesInterface->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("Connection"),
+ QDBusVariant(mConn->objectPath())),
+ acc),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ // wait for the connection to be built in Account
+ while (acc->connection().isNull()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QVERIFY(connect(acc->connection()->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(acc->isReady());
+
+ // once the status change the capabilities will be updated
+ mProps.clear();
+ QVERIFY(connect(acc->setRequestedPresence(Presence::available()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ while (!mProps.contains(QLatin1String("Capabilities"))) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ // using connection caps now
+ caps = acc->capabilities();
+ QVERIFY(caps.textChats());
+ QVERIFY(!caps.textChatrooms());
+ QVERIFY(!caps.streamedMediaCalls());
+ QVERIFY(!caps.streamedMediaAudioCalls());
+ QVERIFY(!caps.streamedMediaVideoCalls());
+ QVERIFY(!caps.streamedMediaVideoCallsWithAudio());
+ QVERIFY(!caps.upgradingStreamedMediaCalls());
+
+ // once the status change the capabilities will be updated
+ mProps.clear();
+ QVERIFY(connect(new PendingVoid(
+ accPropertiesInterface->Set(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT),
+ QLatin1String("Connection"),
+ QDBusVariant(QLatin1String("/"))),
+ acc),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QVERIFY(connect(acc->setRequestedPresence(Presence::offline()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ while (!mProps.contains(QLatin1String("Capabilities"))) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ // back to using merged protocol info caps and profile caps
+ caps = acc->capabilities();
+ QVERIFY(!caps.textChats());
+
+ processDBusQueue(mConn->client().data());
+}
+
+void TestAccountBasics::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestAccountBasics::cleanupTestCase()
+{
+ if (mConn) {
+ QVERIFY(mConn->disconnect());
+ delete mConn;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestAccountBasics)
+#include "_gen/account-basics.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/account-channel-dispatcher.cpp b/qt4/tests/dbus/account-channel-dispatcher.cpp
new file mode 100644
index 000000000..16a8d0999
--- /dev/null
+++ b/qt4/tests/dbus/account-channel-dispatcher.cpp
@@ -0,0 +1,1244 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo2/chan.h>
+#include <tests/lib/glib/echo2/conn.h>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/ChannelRequestHints>
+#include <TelepathyQt4/Client>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/HandledChannelNotifier>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingChannelRequest>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Types>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+class ChannelRequestAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelRequest")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelRequest\" >\n"
+" <property name=\"Account\" type=\"o\" access=\"read\" />\n"
+" <property name=\"UserActionTime\" type=\"x\" access=\"read\" />\n"
+" <property name=\"PreferredHandler\" type=\"s\" access=\"read\" />\n"
+" <property name=\"Requests\" type=\"aa{sv}\" access=\"read\" />\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"Hints\" type=\"a{sv}\" access=\"read\" />\n"
+" <method name=\"Proceed\" />\n"
+" <method name=\"Cancel\" />\n"
+" <signal name=\"Failed\" >\n"
+" <arg name=\"Error\" type=\"s\" />\n"
+" <arg name=\"Message\" type=\"s\" />\n"
+" </signal>\n"
+" <signal name=\"Succeeded\" />\n"
+" <signal name=\"SucceededWithChannel\" >\n"
+" <arg name=\"Connection\" type=\"o\" />\n"
+" <arg name=\"ConnectionProperties\" type=\"a{sv}\" />\n"
+" <arg name=\"Channel\" type=\"o\" />\n"
+" <arg name=\"ChannelProperties\" type=\"a{sv}\" />\n"
+" </signal>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Account READ Account)
+ Q_PROPERTY(qulonglong UserActionTime READ UserActionTime)
+ Q_PROPERTY(QString PreferredHandler READ PreferredHandler)
+ Q_PROPERTY(QualifiedPropertyValueMapList Requests READ Requests)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+ Q_PROPERTY(QVariantMap Hints READ Hints)
+
+public:
+ ChannelRequestAdaptor(QDBusObjectPath account,
+ qulonglong userActionTime,
+ QString preferredHandler,
+ QualifiedPropertyValueMapList requests,
+ QStringList interfaces,
+ bool shouldFail,
+ bool proceedNoop,
+ QVariantMap hints,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mAccount(account), mUserActionTime(userActionTime),
+ mPreferredHandler(preferredHandler), mRequests(requests),
+ mInterfaces(interfaces), mShouldFail(shouldFail),
+ mProceedNoop(proceedNoop), mHints(hints)
+ {
+ }
+
+ virtual ~ChannelRequestAdaptor()
+ {
+ }
+
+ void setChan(const QString &connPath, const QVariantMap &connProps,
+ const QString &chanPath, const QVariantMap &chanProps)
+ {
+ mConnPath = connPath;
+ mConnProps = connProps;
+ mChanPath = chanPath;
+ mChanProps = chanProps;
+ }
+
+public: // Properties
+ inline QDBusObjectPath Account() const
+ {
+ return mAccount;
+ }
+
+ inline qulonglong UserActionTime() const
+ {
+ return mUserActionTime;
+ }
+
+ inline QString PreferredHandler() const
+ {
+ return mPreferredHandler;
+ }
+
+ inline QualifiedPropertyValueMapList Requests() const
+ {
+ return mRequests;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+ inline QVariantMap Hints() const
+ {
+ return mHints;
+ }
+
+public Q_SLOTS: // Methods
+ void Proceed()
+ {
+ if (mProceedNoop) {
+ return;
+ }
+
+ if (mShouldFail) {
+ QTimer::singleShot(0, this, SLOT(fail()));
+ } else {
+ QTimer::singleShot(0, this, SLOT(succeed()));
+ }
+ }
+
+ void Cancel()
+ {
+ Q_EMIT Failed(QLatin1String(TELEPATHY_ERROR_CANCELLED), QLatin1String("Cancelled"));
+ }
+
+Q_SIGNALS: // Signals
+ void Failed(const QString &error, const QString &message);
+ void Succeeded();
+ void SucceededWithChannel(const QDBusObjectPath &connPath, const QVariantMap &connProps,
+ const QDBusObjectPath &chanPath, const QVariantMap &chanProps);
+
+private Q_SLOTS:
+ void fail()
+ {
+ Q_EMIT Failed(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("Not available"));
+ }
+
+ void succeed()
+ {
+ if (!mConnPath.isEmpty() && !mChanPath.isEmpty()) {
+ Q_EMIT SucceededWithChannel(QDBusObjectPath(mConnPath), mConnProps,
+ QDBusObjectPath(mChanPath), mChanProps);
+ }
+
+ Q_EMIT Succeeded();
+ }
+
+private:
+ QDBusObjectPath mAccount;
+ qulonglong mUserActionTime;
+ QString mPreferredHandler;
+ QualifiedPropertyValueMapList mRequests;
+ QStringList mInterfaces;
+ bool mShouldFail;
+ bool mProceedNoop;
+ QVariantMap mHints;
+ QString mConnPath, mChanPath;
+ QVariantMap mConnProps, mChanProps;
+};
+
+class ChannelDispatcherAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelDispatcher")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelDispatcher\" >\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"SupportsRequestHints\" type=\"b\" access=\"read\" />\n"
+" <method name=\"CreateChannel\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Requested_Properties\" type=\"a{sv}\" direction=\"in\" />\n"
+" <arg name=\"User_Action_Time\" type=\"x\" direction=\"in\" />\n"
+" <arg name=\"Preferred_Handler\" type=\"s\" direction=\"in\" />\n"
+" <arg name=\"Channel_Object_Path\" type=\"o\" direction=\"out\" />\n"
+" </method>\n"
+" <method name=\"EnsureChannel\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Requested_Properties\" type=\"a{sv}\" direction=\"in\" />\n"
+" <arg name=\"User_Action_Time\" type=\"x\" direction=\"in\" />\n"
+" <arg name=\"Preferred_Handler\" type=\"s\" direction=\"in\" />\n"
+" <arg name=\"Channel_Object_Path\" type=\"o\" direction=\"out\" />\n"
+" </method>\n"
+" <method name=\"CreateChannelWithHints\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Requested_Properties\" type=\"a{sv}\" direction=\"in\" />\n"
+" <arg name=\"User_Action_Time\" type=\"x\" direction=\"in\" />\n"
+" <arg name=\"Preferred_Handler\" type=\"s\" direction=\"in\" />\n"
+" <arg name=\"Hints\" type=\"a{sv}\" direction=\"in\" />\n"
+" <arg name=\"Channel_Object_Path\" type=\"o\" direction=\"out\" />\n"
+" </method>\n"
+" <method name=\"EnsureChannelWithHints\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"Requested_Properties\" type=\"a{sv}\" direction=\"in\" />\n"
+" <arg name=\"User_Action_Time\" type=\"x\" direction=\"in\" />\n"
+" <arg name=\"Preferred_Handler\" type=\"s\" direction=\"in\" />\n"
+" <arg name=\"Hints\" type=\"a{sv}\" direction=\"in\" />\n"
+" <arg name=\"Channel_Object_Path\" type=\"o\" direction=\"out\" />\n"
+" </method>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+ Q_PROPERTY(bool SupportsRequestHints READ SupportsRequestHints)
+
+public:
+ enum MethodCall {
+ CC = 0, // CreateChannel/WithHints
+ EC, // EnsureChannel/WithHints
+ };
+
+ ChannelDispatcherAdaptor(const QDBusConnection &bus, QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mBus(bus),
+ mRequests(0),
+ mCurRequest(0),
+ mInvokeHandler(false),
+ mChannelRequestShouldFail(false),
+ mChannelRequestProceedNoop(false)
+ {
+ }
+
+ virtual ~ChannelDispatcherAdaptor()
+ {
+ }
+
+ void setChan(const QString &connPath, const QVariantMap &connProps,
+ const QString &chanPath, const QVariantMap &chanProps)
+ {
+ mConnPath = connPath;
+ mConnProps = connProps;
+ mChanPath = chanPath;
+ mChanProps = chanProps;
+ }
+
+ void clearChan()
+ {
+ mConnPath = QString();
+ mConnProps.clear();
+ mChanPath = QString();
+ mChanProps.clear();
+ }
+
+public: // Properties
+ inline QStringList Interfaces() const
+ {
+ return QStringList();
+ }
+
+ inline bool SupportsRequestHints() const
+ {
+ return true;
+ }
+
+public Q_SLOTS: // Methods
+ QDBusObjectPath CreateChannel(const QDBusObjectPath& account,
+ const QVariantMap& requestedProperties, qlonglong userActionTime,
+ const QString& preferredHandler)
+ {
+ lastCall = CC;
+ return createChannel(account, requestedProperties,
+ userActionTime, preferredHandler);
+ }
+
+ QDBusObjectPath EnsureChannel(const QDBusObjectPath& account,
+ const QVariantMap& requestedProperties, qlonglong userActionTime,
+ const QString& preferredHandler)
+ {
+ lastCall = EC;
+ return createChannel(account, requestedProperties,
+ userActionTime, preferredHandler);
+ }
+
+ QDBusObjectPath CreateChannelWithHints(const QDBusObjectPath &account,
+ const QVariantMap &requestedProperties, qlonglong userActionTime,
+ const QString &preferredHandler, const QVariantMap &hints)
+ {
+ lastCall = CC;
+ return createChannel(account, requestedProperties,
+ userActionTime, preferredHandler, hints);
+ }
+
+ QDBusObjectPath EnsureChannelWithHints(const QDBusObjectPath &account,
+ const QVariantMap &requestedProperties, qlonglong userActionTime,
+ const QString &preferredHandler, const QVariantMap &hints)
+ {
+ lastCall = EC;
+ return createChannel(account, requestedProperties,
+ userActionTime, preferredHandler, hints);
+ }
+
+private:
+ friend class TestAccountChannelDispatcher;
+
+ QDBusObjectPath createChannel(const QDBusObjectPath &account,
+ const QVariantMap &requestedProperties, qlonglong userActionTime,
+ const QString &preferredHandler, const QVariantMap &hints = QVariantMap())
+ {
+ QObject *request = new QObject(this);
+
+ mCurRequest = new ChannelRequestAdaptor(
+ account,
+ userActionTime,
+ preferredHandler,
+ QualifiedPropertyValueMapList(),
+ QStringList(),
+ mChannelRequestShouldFail,
+ mChannelRequestProceedNoop,
+ hints,
+ request);
+ mCurRequest->setChan(mConnPath, mConnProps, mChanPath, mChanProps);
+ mCurRequestPath = QString(QLatin1String("/org/freedesktop/Telepathy/ChannelRequest/_%1"))
+ .arg(mRequests++);
+ mBus.registerService(QLatin1String("org.freedesktop.Telepathy.ChannelDispatcher"));
+ mBus.registerObject(mCurRequestPath, request);
+
+ mCurPreferredHandler = preferredHandler;
+
+ if (mInvokeHandler && !mConnPath.isEmpty() && !mChanPath.isEmpty()) {
+ invokeHandler(userActionTime);
+ }
+
+ return QDBusObjectPath(mCurRequestPath);
+ }
+
+ void invokeHandler(const qulonglong &userActionTime)
+ {
+ QString channelHandlerPath = QString(QLatin1String("/%1")).arg(mCurPreferredHandler);
+ channelHandlerPath.replace(QLatin1Char('.'), QLatin1Char('/'));
+ Client::ClientHandlerInterface *clientHandlerInterface =
+ new Client::ClientHandlerInterface(mBus, mCurPreferredHandler,
+ channelHandlerPath, this);
+
+ ChannelDetails channelDetails = { QDBusObjectPath(mChanPath), mChanProps };
+ clientHandlerInterface->HandleChannels(mCurRequest->Account(),
+ QDBusObjectPath(mConnPath),
+ ChannelDetailsList() << channelDetails,
+ ObjectPathList() << QDBusObjectPath(mCurRequestPath),
+ userActionTime, QVariantMap());
+ }
+
+ QDBusConnection mBus;
+ uint mRequests;
+ ChannelRequestAdaptor *mCurRequest;
+ QString mCurRequestPath;
+ QString mCurPreferredHandler;
+ bool mInvokeHandler;
+ bool mChannelRequestShouldFail;
+ bool mChannelRequestProceedNoop;
+ QString mConnPath, mChanPath;
+ QVariantMap mConnProps, mChanProps;
+ static MethodCall lastCall;
+};
+
+ChannelDispatcherAdaptor::MethodCall ChannelDispatcherAdaptor::lastCall =
+ (ChannelDispatcherAdaptor::MethodCall) -1;
+
+class TestAccountChannelDispatcher : public Test
+{
+ Q_OBJECT
+
+public:
+ TestAccountChannelDispatcher(QObject *parent = 0)
+ : Test(parent),
+ mChannelDispatcherAdaptor(0),
+ mChannelRequestFinished(false),
+ mChannelRequestFinishedWithError(false)
+ {
+ mHints = ChannelRequestHints();
+ mHints.setHint(QLatin1String("uk.co.willthompson"), QLatin1String("MomOrDad"), QString::fromLatin1("Mommy"));
+ }
+
+protected Q_SLOTS:
+ void onPendingChannelRequestFinished(Tp::PendingOperation *op);
+ void onPendingChannelFinished(Tp::PendingOperation *op);
+ void onChannelHandledAgain(const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &hints);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testEnsureTextChat();
+ void testEnsureTextChatFail();
+ void testEnsureTextChatCancel();
+ void testEnsureTextChatroom();
+ void testEnsureTextChatroomFail();
+ void testEnsureTextChatroomCancel();
+ void testEnsureMediaCall();
+ void testEnsureMediaCallFail();
+ void testEnsureMediaCallCancel();
+ void testCreateChannel();
+ void testCreateChannelFail();
+ void testCreateChannelCancel();
+ void testEnsureChannel();
+ void testEnsureChannelFail();
+ void testEnsureChannelCancel();
+ void testCreateFileTransferChannel();
+ void testCreateFileTransferChannelFail();
+ void testCreateFileTransferChannelCancel();
+ void testCreateFileTransferChannelInvalidParameters();
+
+ void testCreateAndHandleChannel();
+ void testCreateAndHandleChannelNotYours();
+ void testCreateAndHandleChannelFail();
+ void testCreateAndHandleChannelHandledAgain();
+ void testCreateAndHandleChannelHandledChannels();
+ void testCreateAndHandleFileTransferChannel();
+ void testCreateAndHandleFileTransferChannelFail();
+ void testCreateAndHandleFileTransferChannelInvalidParameters();
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ void testPCR(PendingChannelRequest *pcr);
+ void testPC(PendingChannel *pc, PendingChannel **pcOut = 0, ChannelPtr *channelOut = 0);
+
+ QList<ClientHandlerInterface *> ourHandlers();
+ QStringList ourHandledChannels();
+ void checkHandlerHandledChannels(ClientHandlerInterface *handler, const QStringList &toCompare);
+
+ AccountManagerPtr mAM;
+ AccountPtr mAccount;
+ ChannelDispatcherAdaptor *mChannelDispatcherAdaptor;
+ TestConnHelper *mConn;
+ ContactPtr mContact;
+ QDateTime mUserActionTime;
+ ChannelRequestPtr mChannelRequest;
+ bool mChannelRequestFinished;
+ bool mChannelRequestFinishedWithError;
+ QString mChannelRequestFinishedErrorName;
+ bool mChannelRequestAndHandleFinished;
+ bool mChannelRequestAndHandleFinishedWithError;
+ QString mChannelRequestAndHandleFinishedErrorName;
+ QDateTime mChannelHandledAgainActionTime;
+ ChannelRequestHints mHints;
+ QString mChanPath;
+ QVariantMap mConnProps, mChanProps;
+ QString mFilePath;
+};
+
+void TestAccountChannelDispatcher::onPendingChannelRequestFinished(
+ Tp::PendingOperation *op)
+{
+ mChannelRequestFinished = true;
+ mChannelRequestFinishedWithError = op->isError();
+ mChannelRequestFinishedErrorName = op->errorName();
+ mLoop->exit(0);
+}
+
+void TestAccountChannelDispatcher::onPendingChannelFinished(
+ Tp::PendingOperation *op)
+{
+ mChannelRequestAndHandleFinished = true;
+ mChannelRequestAndHandleFinishedWithError = op->isError();
+ mChannelRequestAndHandleFinishedErrorName = op->errorName();
+ mLoop->exit(0);
+}
+
+void TestAccountChannelDispatcher::onChannelHandledAgain(const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &hints)
+{
+ mChannelHandledAgainActionTime = userActionTime;
+ mLoop->exit(0);
+}
+
+void TestAccountChannelDispatcher::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("account-channel-dispatcher");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ // Create the CD first, because Accounts try to introspect it
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QString channelDispatcherBusName = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER);
+ QString channelDispatcherPath = QLatin1String("/org/freedesktop/Telepathy/ChannelDispatcher");
+ QObject *dispatcher = new QObject(this);
+ mChannelDispatcherAdaptor = new ChannelDispatcherAdaptor(bus, dispatcher);
+ QVERIFY(bus.registerService(channelDispatcherBusName));
+ QVERIFY(bus.registerObject(channelDispatcherPath, dispatcher));
+
+ mAM = AccountManager::create();
+ QVERIFY(connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAM->isReady(), true);
+
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ PendingAccount *pacc = mAM->createAccount(QLatin1String("foo"),
+ QLatin1String("bar"), QLatin1String("foobar"), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pacc->account());
+ mAccount = pacc->account();
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAccount->isReady(), true);
+
+ QCOMPARE(mAccount->supportsRequestHints(), true);
+ QCOMPARE(mAccount->requestsSucceedWithChannel(), true);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contacts",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+
+ mContact = mConn->contacts(QStringList() << QLatin1String("foo@bar"))[0];
+ QVERIFY(mContact);
+}
+
+void TestAccountChannelDispatcher::init()
+{
+ initImpl();
+
+ mChannelRequest.reset();
+ mChannelRequestFinished = false;
+ mChannelRequestFinishedWithError = false;
+ mChannelRequestFinishedErrorName = QString();
+ mChannelRequestAndHandleFinished = false;
+ mChannelRequestAndHandleFinishedWithError = false;
+ mChannelRequestAndHandleFinishedErrorName = QString();
+ mChannelHandledAgainActionTime = QDateTime();
+ QDateTime mUserActionTime = QDateTime::currentDateTime();
+
+ mChanPath.clear();
+ mChanProps.clear();
+ mFilePath = QDir::currentPath() + QLatin1String("/test-account-channel-dispatcher");
+}
+
+void TestAccountChannelDispatcher::testPCR(PendingChannelRequest *pcr)
+{
+ QVERIFY(connect(pcr,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onPendingChannelRequestFinished(Tp::PendingOperation *))));
+ mLoop->exec(0);
+
+ mChannelRequest = pcr->channelRequest();
+
+ if (!mChanPath.isEmpty()) {
+ QVERIFY(!mChannelRequest->channel().isNull());
+ QCOMPARE(mChannelRequest->channel()->connection()->objectPath(), mConn->objectPath());
+ QCOMPARE(mChannelRequest->channel()->objectPath(), mChanPath);
+ QCOMPARE(mChannelRequest->channel()->immutableProperties(), mChanProps);
+ } else {
+ QVERIFY(mChannelRequest.isNull() || mChannelRequest->channel().isNull());
+ }
+
+ if (!mChannelRequest.isNull()) {
+ QVERIFY(connect(mChannelRequest->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ mLoop->exec(0);
+ QCOMPARE(mChannelRequest->userActionTime(), mUserActionTime);
+ QCOMPARE(mChannelRequest->account().data(), mAccount.data());
+
+ QVERIFY(mChannelRequest->hints().isValid());
+ QCOMPARE(mChannelRequest->hints().allHints(), mHints.allHints());
+ }
+}
+
+void TestAccountChannelDispatcher::testPC(PendingChannel *pc, PendingChannel **pcOut, ChannelPtr *channelOut)
+{
+ if (pcOut) {
+ *pcOut = pc;
+ }
+
+ QVERIFY(connect(pc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onPendingChannelFinished(Tp::PendingOperation *))));
+ mLoop->exec(0);
+
+ ChannelPtr channel = pc->channel();
+ if (channelOut) {
+ *channelOut = channel;
+ }
+
+ if (mChannelDispatcherAdaptor->mInvokeHandler && !mChanPath.isEmpty()) {
+ QVERIFY(!channel.isNull());
+ QCOMPARE(channel->connection()->objectPath(), mConn->objectPath());
+ QCOMPARE(channel->objectPath(), mChanPath);
+ QCOMPARE(channel->immutableProperties(), mChanProps);
+ } else {
+ QVERIFY(channel.isNull());
+ }
+}
+
+QList<ClientHandlerInterface *> TestAccountChannelDispatcher::ourHandlers()
+{
+ QList<ClientHandlerInterface *> handlers;
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QStringList registeredNames = bus.interface()->registeredServiceNames();
+ Q_FOREACH (QString name, registeredNames) {
+ if (!name.startsWith(QLatin1String("org.freedesktop.Telepathy.Client."))) {
+ continue;
+ }
+
+ if (QDBusConnection::sessionBus().interface()->serviceOwner(name).value() !=
+ QDBusConnection::sessionBus().baseService()) {
+ continue;
+ }
+
+ QString path = QLatin1Char('/') + name;
+ path.replace(QLatin1Char('.'), QLatin1Char('/'));
+
+ ClientInterface client(name, path);
+ QStringList ifaces;
+ if (!waitForProperty(client.requestPropertyInterfaces(), &ifaces)) {
+ continue;
+ }
+
+ if (!ifaces.contains(TP_QT4_IFACE_CLIENT_HANDLER)) {
+ continue;
+ }
+
+ ClientHandlerInterface *handler = new ClientHandlerInterface(name, path, this);
+ handlers.append(handler);
+ }
+
+ return handlers;
+}
+
+QStringList TestAccountChannelDispatcher::ourHandledChannels()
+{
+ ObjectPathList handledChannels;
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QStringList registeredNames = bus.interface()->registeredServiceNames();
+ Q_FOREACH (QString name, registeredNames) {
+ if (!name.startsWith(QLatin1String("org.freedesktop.Telepathy.Client."))) {
+ continue;
+ }
+
+ if (QDBusConnection::sessionBus().interface()->serviceOwner(name).value() !=
+ QDBusConnection::sessionBus().baseService()) {
+ continue;
+ }
+
+ QString path = QLatin1Char('/') + name;
+ path.replace(QLatin1Char('.'), QLatin1Char('/'));
+
+ ClientInterface client(name, path);
+ QStringList ifaces;
+ if (!waitForProperty(client.requestPropertyInterfaces(), &ifaces)) {
+ continue;
+ }
+
+ if (!ifaces.contains(TP_QT4_IFACE_CLIENT_HANDLER)) {
+ continue;
+ }
+
+ ClientHandlerInterface *handler = new ClientHandlerInterface(bus, name, path, this);
+ handledChannels.clear();
+ if (waitForProperty(handler->requestPropertyHandledChannels(), &handledChannels)) {
+ break;
+ }
+ }
+
+ QStringList ret;
+ Q_FOREACH (const QDBusObjectPath &objectPath, handledChannels) {
+ ret << objectPath.path();
+ }
+
+ return ret;
+}
+
+void TestAccountChannelDispatcher::checkHandlerHandledChannels(ClientHandlerInterface *handler,
+ const QStringList &toCompare)
+{
+ ObjectPathList handledChannels;
+ QVERIFY(waitForProperty(handler->requestPropertyHandledChannels(), &handledChannels));
+ QStringList sortedHandledChannels;
+ Q_FOREACH (const QDBusObjectPath &objectPath, handledChannels) {
+ sortedHandledChannels << objectPath.path();
+ }
+ sortedHandledChannels.sort();
+ QCOMPARE(sortedHandledChannels, toCompare);
+}
+
+#define TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(method_name, shouldFail, proceedNoop, expectedError) \
+{ \
+ ChannelRequestHints savedHints = mHints; \
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_EXTENDED(method_name, QLatin1String("foo@bar"), \
+ ChannelRequestHints(), shouldFail, proceedNoop, expectedError) \
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_EXTENDED(method_name, QLatin1String("foo@bar"), \
+ savedHints, shouldFail, proceedNoop, expectedError) \
+ mHints = savedHints; \
+}
+
+#define TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(method_name, shouldFail, proceedNoop, expectedError) \
+{ \
+ ChannelRequestHints savedHints = mHints; \
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_EXTENDED(method_name, mContact, \
+ ChannelRequestHints(), shouldFail, proceedNoop, expectedError) \
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_EXTENDED(method_name, mContact, \
+ savedHints, shouldFail, proceedNoop, expectedError) \
+ mHints = savedHints; \
+}
+
+#define TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_EXTENDED(method_name, contact, hints, shouldFail, proceedNoop, expectedError) \
+{ \
+ mHints = hints; \
+ mChannelDispatcherAdaptor->mInvokeHandler = false; \
+ mChannelDispatcherAdaptor->mChannelRequestShouldFail = shouldFail; \
+ mChannelDispatcherAdaptor->mChannelRequestProceedNoop = proceedNoop; \
+ if (!mChanPath.isEmpty()) { \
+ mChannelDispatcherAdaptor->setChan(mConn->objectPath(), mConnProps, mChanPath, mChanProps); \
+ } else { \
+ mChannelDispatcherAdaptor->clearChan(); \
+ } \
+ PendingChannelRequest *pcr; \
+ if (mHints.isValid()) { \
+ pcr = mAccount->method_name(contact, mUserActionTime, QString(), mHints); \
+ } else { \
+ pcr = mAccount->method_name(contact, mUserActionTime, QString()); \
+ } \
+ if (shouldFail && proceedNoop) { \
+ pcr->cancel(); \
+ } \
+ testPCR(pcr); \
+ QCOMPARE(mChannelRequestFinishedWithError, shouldFail); \
+ if (shouldFail) {\
+ QCOMPARE(mChannelRequestFinishedErrorName, QString(QLatin1String(expectedError))); \
+ } \
+}
+
+#define TEST_CREATE_ENSURE_CHANNEL(method_name, shouldFail, proceedNoop, expectedError) \
+{ \
+ ChannelRequestHints savedHints = mHints; \
+ TEST_CREATE_ENSURE_CHANNEL_EXTENDED(method_name, \
+ ChannelRequestHints(), shouldFail, proceedNoop, expectedError) \
+ TEST_CREATE_ENSURE_CHANNEL_EXTENDED(method_name, \
+ savedHints, shouldFail, proceedNoop, expectedError) \
+ mHints = savedHints; \
+}
+
+#define TEST_CREATE_ENSURE_CHANNEL_EXTENDED(method_name, hints, shouldFail, proceedNoop, expectedError) \
+{ \
+ mHints = hints; \
+ QVariantMap request; \
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), \
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT)); \
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), \
+ (uint) Tp::HandleTypeContact); \
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), \
+ QLatin1String("foo@bar")); \
+ mChannelDispatcherAdaptor->mInvokeHandler = false; \
+ mChannelDispatcherAdaptor->mChannelRequestShouldFail = shouldFail; \
+ mChannelDispatcherAdaptor->mChannelRequestProceedNoop = proceedNoop; \
+ if (!mChanPath.isEmpty()) { \
+ mChannelDispatcherAdaptor->setChan(mConn->objectPath(), mConnProps, mChanPath, mChanProps); \
+ } else { \
+ mChannelDispatcherAdaptor->clearChan(); \
+ } \
+ PendingChannelRequest *pcr; \
+ if (mHints.isValid()) { \
+ pcr = mAccount->method_name(request, mUserActionTime, QString(), mHints); \
+ } else { \
+ pcr = mAccount->method_name(request, mUserActionTime, QString()); \
+ } \
+ if (shouldFail && proceedNoop) { \
+ pcr->cancel(); \
+ } \
+ testPCR(pcr); \
+ QCOMPARE(mChannelRequestFinishedWithError, shouldFail); \
+ if (shouldFail) {\
+ QCOMPARE(mChannelRequestFinishedErrorName, QString(QLatin1String(expectedError))); \
+ } \
+}
+
+void TestAccountChannelDispatcher::testEnsureTextChat()
+{
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureTextChat, false, false, "");
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureTextChat, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureTextChatFail()
+{
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureTextChat, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureTextChat, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureTextChatCancel()
+{
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureTextChat, true, true, TELEPATHY_ERROR_CANCELLED);
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureTextChat, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureTextChatroom()
+{
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureTextChatroom, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureTextChatroomFail()
+{
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureTextChatroom, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureTextChatroomCancel()
+{
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureTextChatroom, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureMediaCall()
+{
+ mChanPath = mConn->objectPath() + QLatin1String("/channel");
+ mChanProps = ChannelClassSpec::streamedMediaCall().allProperties();
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureStreamedMediaCall, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureStreamedMediaCall, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureStreamedMediaAudioCall, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureStreamedMediaAudioCall, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureMediaCallFail()
+{
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureStreamedMediaCall, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureStreamedMediaCall, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureStreamedMediaAudioCall, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureStreamedMediaAudioCall, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureMediaCallCancel()
+{
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureStreamedMediaCall, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureStreamedMediaCall, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC(ensureStreamedMediaAudioCall, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+
+ ChannelDispatcherAdaptor::lastCall = (ChannelDispatcherAdaptor::MethodCall) -1;
+ TEST_CREATE_ENSURE_CHANNEL_SPECIFIC_WITH_CONTACT(ensureStreamedMediaAudioCall, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+#define TEST_CREATE_FILE_TRANSFER_CHANNEL(shouldFail, proceedNoop, invalidProps, expectedError) \
+{ \
+ ChannelRequestHints savedHints = mHints; \
+ TEST_CREATE_FILE_TRANSFER_CHANNEL_EXTENDED(QLatin1String("foo@bar"), \
+ ChannelRequestHints(), shouldFail, proceedNoop, invalidProps, expectedError) \
+ TEST_CREATE_FILE_TRANSFER_CHANNEL_EXTENDED(QLatin1String("foo@bar"), \
+ savedHints, shouldFail, proceedNoop, invalidProps, expectedError) \
+ TEST_CREATE_FILE_TRANSFER_CHANNEL_EXTENDED(mContact, \
+ ChannelRequestHints(), shouldFail, proceedNoop, invalidProps, expectedError) \
+ TEST_CREATE_FILE_TRANSFER_CHANNEL_EXTENDED(mContact, \
+ savedHints, shouldFail, proceedNoop, invalidProps, expectedError) \
+ mHints = savedHints; \
+}
+
+#define TEST_CREATE_FILE_TRANSFER_CHANNEL_EXTENDED(contact, hints, shouldFail, proceedNoop, invalidProps, expectedError) \
+{ \
+ mHints = hints; \
+ mChannelDispatcherAdaptor->mInvokeHandler = false; \
+ mChannelDispatcherAdaptor->mChannelRequestShouldFail = shouldFail; \
+ mChannelDispatcherAdaptor->mChannelRequestProceedNoop = proceedNoop; \
+ if (!mChanPath.isEmpty()) { \
+ mChannelDispatcherAdaptor->setChan(mConn->objectPath(), mConnProps, mChanPath, mChanProps); \
+ } else { \
+ mChannelDispatcherAdaptor->clearChan(); \
+ } \
+ \
+ PendingChannelRequest *pcr; \
+ FileTransferChannelCreationProperties ftprops; \
+ if (!invalidProps) { \
+ QFileInfo fileInfo(mFilePath); \
+ ftprops = FileTransferChannelCreationProperties(fileInfo.fileName(), \
+ QLatin1String("application/octet-stream"), fileInfo.size()); \
+ } \
+ \
+ if (mHints.isValid()) { \
+ pcr = mAccount->createFileTransfer(contact, \
+ ftprops, mUserActionTime, QString(), mHints); \
+ } else { \
+ pcr = mAccount->createFileTransfer(contact, \
+ ftprops, mUserActionTime, QString()); \
+ } \
+ \
+ if (shouldFail && proceedNoop) { \
+ pcr->cancel(); \
+ } \
+ testPCR(pcr); \
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC); \
+ QCOMPARE(mChannelRequestFinishedWithError, shouldFail); \
+ if (shouldFail) {\
+ QCOMPARE(mChannelRequestFinishedErrorName, QString(QLatin1String(expectedError))); \
+ } \
+}
+
+void TestAccountChannelDispatcher::testCreateFileTransferChannel()
+{
+ mChanPath.clear();
+ mChanProps = ChannelClassSpec::outgoingFileTransfer().allProperties();
+
+ TEST_CREATE_FILE_TRANSFER_CHANNEL(false, false, false, "");
+}
+
+void TestAccountChannelDispatcher::testCreateFileTransferChannelFail()
+{
+ TEST_CREATE_FILE_TRANSFER_CHANNEL(true, false, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+}
+
+void TestAccountChannelDispatcher::testCreateFileTransferChannelCancel()
+{
+ TEST_CREATE_FILE_TRANSFER_CHANNEL(true, true, false, TELEPATHY_ERROR_CANCELLED);
+}
+
+void TestAccountChannelDispatcher::testCreateFileTransferChannelInvalidParameters()
+{
+ TEST_CREATE_FILE_TRANSFER_CHANNEL(true, false, true, TP_QT4_ERROR_INVALID_ARGUMENT);
+}
+
+void TestAccountChannelDispatcher::testCreateChannel()
+{
+ TEST_CREATE_ENSURE_CHANNEL(createChannel, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC);
+}
+
+void TestAccountChannelDispatcher::testCreateChannelFail()
+{
+ TEST_CREATE_ENSURE_CHANNEL(createChannel, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC);
+}
+
+void TestAccountChannelDispatcher::testCreateChannelCancel()
+{
+ TEST_CREATE_ENSURE_CHANNEL(createChannel, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC);
+}
+
+void TestAccountChannelDispatcher::testEnsureChannel()
+{
+ TEST_CREATE_ENSURE_CHANNEL(ensureChannel, false, false, "");
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureChannelFail()
+{
+ TEST_CREATE_ENSURE_CHANNEL(ensureChannel, true, false, TELEPATHY_ERROR_NOT_AVAILABLE);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testEnsureChannelCancel()
+{
+ TEST_CREATE_ENSURE_CHANNEL(ensureChannel, true, true, TELEPATHY_ERROR_CANCELLED);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+#define TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(method_name, channelRequestShouldFail, shouldFail, invokeHandler, expectedError, channelOut, pcOut) \
+ { \
+ QVariantMap request; \
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), \
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT)); \
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), \
+ (uint) Tp::HandleTypeContact); \
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), \
+ QLatin1String("foo@bar")); \
+ mChannelDispatcherAdaptor->mInvokeHandler = invokeHandler; \
+ mChannelDispatcherAdaptor->mChannelRequestShouldFail = channelRequestShouldFail; \
+ mChannelDispatcherAdaptor->mChannelRequestProceedNoop = false; \
+ if (!mChanPath.isEmpty()) { \
+ mChannelDispatcherAdaptor->setChan(mConn->objectPath(), mConnProps, mChanPath, mChanProps); \
+ } else { \
+ mChannelDispatcherAdaptor->clearChan(); \
+ } \
+ PendingChannel *pc = mAccount->method_name(request, mUserActionTime); \
+ testPC(pc, pcOut, channelOut); \
+ QCOMPARE(mChannelRequestAndHandleFinishedWithError, shouldFail); \
+ if (shouldFail) {\
+ QCOMPARE(mChannelRequestAndHandleFinishedErrorName, QString(QLatin1String(expectedError))); \
+ } \
+ }
+
+void TestAccountChannelDispatcher::testCreateAndHandleChannel()
+{
+ mChanPath = mConn->objectPath() + QLatin1String("/channel");
+ mChanProps = ChannelClassSpec::textChat().allProperties();
+
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(createAndHandleChannel, false, false, true, "", 0, 0);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC);
+}
+
+void TestAccountChannelDispatcher::testCreateAndHandleChannelNotYours()
+{
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(ensureAndHandleChannel, false, true, false, TP_QT4_ERROR_NOT_YOURS, 0, 0);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::EC);
+}
+
+void TestAccountChannelDispatcher::testCreateAndHandleChannelFail()
+{
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(createAndHandleChannel, true, true, false, TP_QT4_ERROR_NOT_AVAILABLE, 0, 0);
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC);
+}
+
+void TestAccountChannelDispatcher::testCreateAndHandleChannelHandledAgain()
+{
+ // create a Channel by magic, rather than doing D-Bus round-trips for it
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(contactRepo, "someone@localhost", 0, 0);
+
+ mChanPath = mConn->objectPath() + QLatin1String("/TextChannel");
+ QByteArray chanPath(mChanPath.toAscii());
+ ExampleEcho2Channel *textChanService = EXAMPLE_ECHO_2_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_2_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ tp_handle_unref(contactRepo, handle);
+
+ PendingChannel *pcOut = 0;
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(createAndHandleChannel, false, false, true, "", 0, &pcOut);
+
+ HandledChannelNotifier *notifier = pcOut->handledChannelNotifier();
+ connect(notifier,
+ SIGNAL(handledAgain(QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onChannelHandledAgain(QDateTime,Tp::ChannelRequestHints)));
+ QDateTime timestamp(QDate::currentDate());
+
+ mChannelDispatcherAdaptor->invokeHandler(timestamp.toTime_t());
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChannelHandledAgainActionTime, timestamp);
+
+ if (textChanService != 0) {
+ g_object_unref(textChanService);
+ textChanService = 0;
+ }
+}
+
+void TestAccountChannelDispatcher::testCreateAndHandleChannelHandledChannels()
+{
+ mChanPath = mConn->objectPath() + QLatin1String("/channel");
+ mChanProps = ChannelClassSpec::textChat().allProperties();
+
+ QVERIFY(ourHandledChannels().isEmpty());
+ QVERIFY(ourHandlers().isEmpty());
+
+ ChannelPtr channel;
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(createAndHandleChannel, false, false, true, "", &channel, 0);
+
+ // check that the channel appears in the HandledChannels property of the first handler
+ QVERIFY(!ourHandledChannels().isEmpty());
+ QCOMPARE(ourHandledChannels().size(), 1);
+ QVERIFY(ourHandledChannels().contains(mChanPath));
+
+ QVERIFY(!ourHandlers().isEmpty());
+ QCOMPARE(ourHandlers().size(), 1);
+
+ channel.reset();
+
+ // reseting the channel should unregister the handler
+ while (!ourHandlers().isEmpty()) {
+ mLoop->processEvents();
+ }
+
+ QVERIFY(ourHandledChannels().isEmpty());
+
+ ChannelPtr channel1;
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(createAndHandleChannel, false, false, true, "", &channel1, 0);
+
+ // check that the channel appears in the HandledChannels property of the first handler
+ QVERIFY(!ourHandledChannels().isEmpty());
+ QCOMPARE(ourHandledChannels().size(), 1);
+ QVERIFY(ourHandledChannels().contains(mChanPath));
+
+ QVERIFY(!ourHandlers().isEmpty());
+ QCOMPARE(ourHandlers().size(), 1);
+
+ mChanPath = mConn->objectPath() + QLatin1String("/channelother");
+ mChanProps = ChannelClassSpec::textChat().allProperties();
+
+ ChannelPtr channel2;
+ TEST_CREATE_ENSURE_AND_HANDLE_CHANNEL(createAndHandleChannel, false, false, true, "", &channel2, 0);
+
+ // check that the channel appears in the HandledChannels property of some handler
+ QVERIFY(!ourHandledChannels().isEmpty());
+ QCOMPARE(ourHandledChannels().size(), 2);
+ QVERIFY(ourHandledChannels().contains(mChanPath));
+
+ QVERIFY(!ourHandlers().isEmpty());
+ // only one handler will stay alive to properly report HandledChannels
+ QCOMPARE(ourHandlers().size(), 1);
+
+ QStringList sortedOurHandledChannels = ourHandledChannels();
+ sortedOurHandledChannels.sort();
+ Q_FOREACH (ClientHandlerInterface *handler, ourHandlers()) {
+ checkHandlerHandledChannels(handler, sortedOurHandledChannels);
+ }
+
+ channel1.reset();
+
+ while (ourHandledChannels().size() > 1) {
+ mLoop->processEvents();
+ }
+
+ QVERIFY(!ourHandledChannels().isEmpty());
+ QCOMPARE(ourHandledChannels().size(), 1);
+ QVERIFY(ourHandledChannels().contains(mChanPath));
+
+ channel2.reset();
+
+ while (!ourHandlers().isEmpty()) {
+ mLoop->processEvents();
+ }
+
+ QVERIFY(ourHandledChannels().isEmpty());
+}
+
+#define TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL(channelRequestShouldFail, shouldFail, \
+ invalidProps, invokeHandler, expectedError, channelOut, pcOut) \
+ TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL_EXTENDED(QLatin1String("foo@bar"), \
+ channelRequestShouldFail, shouldFail, invalidProps, invokeHandler, \
+ expectedError, channelOut, pcOut) \
+ TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL_EXTENDED(mContact, \
+ channelRequestShouldFail, shouldFail, invalidProps, invokeHandler, \
+ expectedError, channelOut, pcOut)
+
+#define TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL_EXTENDED(contact, channelRequestShouldFail, \
+ shouldFail, invalidProps, invokeHandler, expectedError, channelOut, pcOut) \
+ { \
+ mChannelDispatcherAdaptor->mInvokeHandler = invokeHandler; \
+ mChannelDispatcherAdaptor->mChannelRequestShouldFail = channelRequestShouldFail; \
+ mChannelDispatcherAdaptor->mChannelRequestProceedNoop = false; \
+ if (!mChanPath.isEmpty()) { \
+ mChannelDispatcherAdaptor->setChan(mConn->objectPath(), mConnProps, mChanPath, mChanProps); \
+ } else { \
+ mChannelDispatcherAdaptor->clearChan(); \
+ } \
+ PendingChannel *pc; \
+ if (!invalidProps) { \
+ QFileInfo fileInfo(mFilePath); \
+ FileTransferChannelCreationProperties ftprops(fileInfo.fileName(), \
+ QLatin1String("application/octet-stream"), fileInfo.size()); \
+ pc = mAccount->createAndHandleFileTransfer(contact, \
+ ftprops, mUserActionTime); \
+ } else { \
+ FileTransferChannelCreationProperties ftprops; \
+ pc = mAccount->createAndHandleFileTransfer(contact, \
+ ftprops, mUserActionTime); \
+ } \
+ testPC(pc, pcOut, channelOut); \
+ QCOMPARE(ChannelDispatcherAdaptor::lastCall, ChannelDispatcherAdaptor::CC); \
+ QCOMPARE(mChannelRequestAndHandleFinishedWithError, shouldFail); \
+ if (shouldFail) { \
+ QCOMPARE(mChannelRequestAndHandleFinishedErrorName, QString(QLatin1String(expectedError))); \
+ } \
+ }
+
+void TestAccountChannelDispatcher::testCreateAndHandleFileTransferChannel()
+{
+ mChanPath = mConn->objectPath() + QLatin1String("/channel");
+ mChanProps = ChannelClassSpec::incomingFileTransfer().allProperties();
+
+ TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL(false, false, false, true, "", 0, 0);
+}
+
+void TestAccountChannelDispatcher::testCreateAndHandleFileTransferChannelFail()
+{
+ TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL(true, true, false, true, TELEPATHY_ERROR_NOT_AVAILABLE, 0, 0);
+}
+
+void TestAccountChannelDispatcher::testCreateAndHandleFileTransferChannelInvalidParameters()
+{
+ TEST_CREATE_AND_HANDLE_FILE_TRANSFER_CHANNEL(true, true, true, true, TP_QT4_ERROR_INVALID_ARGUMENT, 0, 0);
+}
+
+void TestAccountChannelDispatcher::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestAccountChannelDispatcher::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestAccountChannelDispatcher)
+#include "_gen/account-channel-dispatcher.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/account-connection-factory.cpp b/qt4/tests/dbus/account-connection-factory.cpp
new file mode 100644
index 000000000..5a46c5a97
--- /dev/null
+++ b/qt4/tests/dbus/account-connection-factory.cpp
@@ -0,0 +1,449 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/PendingComposite>
+#include <TelepathyQt4/PendingReady>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+// A really minimal Account implementation, totally not spec compliant outside the parts stressed by
+// this test
+class AccountAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Account")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Account\" >\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
+" <signal name=\"AccountPropertyChanged\" >\n"
+" <arg name=\"Properties\" type=\"a{sv}\" />\n"
+" </signal>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Connection READ Connection)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+
+public:
+ AccountAdaptor(QObject *parent)
+ : QDBusAbstractAdaptor(parent), mConnection(QLatin1String("/"))
+ {
+ }
+
+ virtual ~AccountAdaptor()
+ {
+ }
+
+ void setConnection(QString conn)
+ {
+ if (conn.isEmpty()) {
+ conn = QLatin1String("/");
+ }
+
+ mConnection = QDBusObjectPath(conn);
+ QVariantMap props;
+ props.insert(QLatin1String("Connection"), QVariant::fromValue(mConnection));
+ Q_EMIT AccountPropertyChanged(props);
+ }
+
+public: // Properties
+ inline QDBusObjectPath Connection() const
+ {
+ return mConnection;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return QStringList();
+ }
+
+Q_SIGNALS: // Signals
+ void AccountPropertyChanged(const QVariantMap &properties);
+
+private:
+ QDBusObjectPath mConnection;
+};
+
+class TestAccountConnectionFactory : public Test
+{
+ Q_OBJECT
+
+public:
+ TestAccountConnectionFactory(QObject *parent = 0)
+ : Test(parent),
+ mConn1(0), mConn2(0),
+ mDispatcher(0), mAccountAdaptor(0),
+ mReceivedHaveConnection(0), mReceivedConn(0)
+ { }
+
+protected Q_SLOTS:
+ void onConnectionChanged(const Tp::ConnectionPtr &conn);
+ void expectPropertyChange(const QString &property);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testIntrospectSeveralAccounts();
+ void testCreateAndIntrospect();
+ void testDefaultFactoryInitialConn();
+ void testReadifyingFactoryInitialConn();
+ void testSwitch();
+ void testQueuedSwitch();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn1, *mConn2;
+ QObject *mDispatcher;
+ QString mAccountBusName, mAccountPath;
+ AccountAdaptor *mAccountAdaptor;
+ AccountPtr mAccount;
+ bool *mReceivedHaveConnection;
+ QString *mReceivedConn;
+ QStringList mReceivedConns;
+};
+
+void TestAccountConnectionFactory::onConnectionChanged(const Tp::ConnectionPtr &conn)
+{
+ qDebug() << "have connection:" << !conn.isNull();
+
+ if (mReceivedHaveConnection) {
+ delete mReceivedHaveConnection;
+ }
+
+ mReceivedHaveConnection = new bool(!conn.isNull());
+}
+
+void TestAccountConnectionFactory::expectPropertyChange(const QString &property)
+{
+ if (property != QLatin1String("connection")) {
+ // Not interesting
+ return;
+ }
+
+ ConnectionPtr conn = mAccount->connection();
+ qDebug() << "connection changed:" << (conn ? conn->objectPath() : QLatin1String("none"));
+
+ if (conn) {
+ QCOMPARE(conn->objectPath(), conn->objectPath());
+ }
+
+ if (mReceivedConn) {
+ delete mReceivedConn;
+ }
+
+ mReceivedConn = new QString(conn ? conn->objectPath() : QLatin1String(""));
+ mReceivedConns.push_back(conn ? conn->objectPath() : QLatin1String(""));
+}
+
+void TestAccountConnectionFactory::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("account-connection-factory");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn1 = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL);
+ QCOMPARE(mConn1->isReady(), false);
+
+ mConn2 = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me2@example.com",
+ "protocol", "simple",
+ NULL);
+ QCOMPARE(mConn2->isReady(), false);
+
+ mAccountBusName = QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_MANAGER);
+ mAccountPath = QLatin1String("/org/freedesktop/Telepathy/Account/simple/simple/account");
+}
+
+void TestAccountConnectionFactory::init()
+{
+ initImpl();
+
+ mDispatcher = new QObject(this);
+
+ mAccountAdaptor = new AccountAdaptor(mDispatcher);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QVERIFY(bus.registerService(mAccountBusName));
+ QVERIFY(bus.registerObject(mAccountPath, mDispatcher));
+}
+
+// If this test fails, probably the code which tries to introspect the CD just once and then
+// continue with Account introspection has a bug
+void TestAccountConnectionFactory::testIntrospectSeveralAccounts()
+{
+ QList<PendingOperation *> ops;
+ for (int i = 0; i < 10; i++) {
+ AccountPtr acc = Account::create(mAccountBusName, mAccountPath);
+
+ // This'll get the CD introspected in the middle (but won't finish any of the pending ops,
+ // as they'll only finish in a singleShot in the next iter)
+ //
+ // One iteration to get readinessHelper to start introspecting,
+ // the second to download the CD property
+ // the third to get PendingVariant to actually emit the finished signal for it
+ if (i == 5) {
+ mLoop->processEvents();
+ mLoop->processEvents();
+ mLoop->processEvents();
+ }
+
+ ops.push_back(acc->becomeReady());
+ }
+
+ QVERIFY(connect(new PendingComposite(ops, SharedPtr<RefCounted>()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+// If this test fails, probably the mini-Account implements too little for the Account proxy to work
+// OR the Account proxy is completely broken :)
+void TestAccountConnectionFactory::testCreateAndIntrospect()
+{
+ mAccount = Account::create(mAccountBusName, mAccountPath);
+
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestAccountConnectionFactory::testDefaultFactoryInitialConn()
+{
+ mAccountAdaptor->setConnection(mConn1->objectPath());
+
+ mAccount = Account::create(mAccountBusName, mAccountPath);
+
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
+ QVERIFY(!mAccount->connection().isNull());
+
+ QCOMPARE(mAccount->connectionFactory()->features(), Features());
+}
+
+void TestAccountConnectionFactory::testReadifyingFactoryInitialConn()
+{
+ mAccountAdaptor->setConnection(mConn1->objectPath());
+
+ mAccount = Account::create(mAccountBusName, mAccountPath,
+ ConnectionFactory::create(QDBusConnection::sessionBus(),
+ Connection::FeatureCore),
+ ChannelFactory::create(QDBusConnection::sessionBus()));
+
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
+
+ ConnectionPtr conn = mAccount->connection();
+ QVERIFY(!conn.isNull());
+
+ QVERIFY(conn->isReady(Connection::FeatureCore));
+
+ QCOMPARE(mAccount->connectionFactory()->features(), Features(Connection::FeatureCore));
+}
+
+void TestAccountConnectionFactory::testSwitch()
+{
+ mAccount = Account::create(mAccountBusName, mAccountPath,
+ ConnectionFactory::create(QDBusConnection::sessionBus(),
+ Connection::FeatureCore),
+ ChannelFactory::create(QDBusConnection::sessionBus()));
+
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mAccount->connection().isNull());
+
+ QVERIFY(connect(mAccount.data(),
+ SIGNAL(connectionChanged(const Tp::ConnectionPtr &)),
+ SLOT(onConnectionChanged(const Tp::ConnectionPtr &))));
+
+ QVERIFY(connect(mAccount.data(),
+ SIGNAL(propertyChanged(QString)),
+ SLOT(expectPropertyChange(QString))));
+
+ // Switch from none to conn 1
+ mAccountAdaptor->setConnection(mConn1->objectPath());
+ while (!mReceivedHaveConnection || !mReceivedConn) {
+ mLoop->processEvents();
+ }
+ QCOMPARE(*mReceivedHaveConnection, true);
+ QCOMPARE(*mReceivedConn, mConn1->objectPath());
+
+ delete mReceivedHaveConnection;
+ mReceivedHaveConnection = 0;
+
+ delete mReceivedConn;
+ mReceivedConn = 0;
+
+ QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
+ QVERIFY(!mAccount->connection().isNull());
+
+ ConnectionPtr conn = mAccount->connection();
+ QVERIFY(!conn.isNull());
+ QCOMPARE(conn->objectPath(), mConn1->objectPath());
+
+ QVERIFY(conn->isReady(Connection::FeatureCore));
+
+ // Switch from conn 1 to conn 2
+ mAccountAdaptor->setConnection(mConn2->objectPath());
+ while (!mReceivedConn) {
+ mLoop->processEvents();
+ }
+ QCOMPARE(*mReceivedConn, mConn2->objectPath());
+
+ delete mReceivedConn;
+ mReceivedConn = 0;
+
+ // connectionChanged() should have been emitted as it is a new connection
+ QVERIFY(mReceivedHaveConnection);
+ QVERIFY(!mAccount->connection().isNull());
+
+ QCOMPARE(mAccount->connection()->objectPath(), mConn2->objectPath());
+
+ conn = mAccount->connection();
+ QVERIFY(!conn.isNull());
+ QCOMPARE(conn->objectPath(), mConn2->objectPath());
+
+ QVERIFY(conn->isReady(Connection::FeatureCore));
+
+ // Switch from conn 2 to none
+ mAccountAdaptor->setConnection(QString());
+ while (!mReceivedHaveConnection || !mReceivedConn) {
+ mLoop->processEvents();
+ }
+ QCOMPARE(*mReceivedConn, QString());
+ QCOMPARE(*mReceivedHaveConnection, false);
+
+ QVERIFY(mAccount->connection().isNull());
+}
+
+void TestAccountConnectionFactory::testQueuedSwitch()
+{
+ mAccount = Account::create(mAccountBusName, mAccountPath,
+ ConnectionFactory::create(QDBusConnection::sessionBus(),
+ Connection::FeatureCore),
+ ChannelFactory::create(QDBusConnection::sessionBus()));
+
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mAccount->connection().isNull());
+
+ QVERIFY(connect(mAccount.data(),
+ SIGNAL(propertyChanged(QString)),
+ SLOT(expectPropertyChange(QString))));
+
+ // Switch a few times but don't give the proxy update machinery time to run
+ mAccountAdaptor->setConnection(mConn1->objectPath());
+ mAccountAdaptor->setConnection(QString());
+ mAccountAdaptor->setConnection(mConn2->objectPath());
+ mAccountAdaptor->setConnection(QString());
+ mAccountAdaptor->setConnection(QString());
+ mAccountAdaptor->setConnection(QString());
+ mAccountAdaptor->setConnection(mConn2->objectPath());
+ mAccountAdaptor->setConnection(QString());
+ mAccountAdaptor->setConnection(mConn2->objectPath());
+ mAccountAdaptor->setConnection(mConn2->objectPath());
+ mAccountAdaptor->setConnection(mConn1->objectPath());
+
+ // We should get a total of 8 changes because some of them aren't actually any different
+ while (mReceivedConns.size() < 8) {
+ mLoop->processEvents();
+ }
+ // To ensure it didn't go over, which might be possible if it handled two events in one iter
+ QCOMPARE(mReceivedConns.size(), 8);
+
+ // Ensure we got them in the correct order
+ QCOMPARE(mReceivedConns, QStringList() << mConn1->objectPath()
+ << QString()
+ << mConn2->objectPath()
+ << QString()
+ << mConn2->objectPath()
+ << QString()
+ << mConn2->objectPath()
+ << mConn1->objectPath());
+
+ // Check that the final state is correct
+ QCOMPARE(mAccount->connection()->objectPath(), mConn1->objectPath());
+ QVERIFY(!mAccount->connection().isNull());
+}
+
+void TestAccountConnectionFactory::cleanup()
+{
+ mAccount.reset();
+
+ if (mReceivedHaveConnection) {
+ delete mReceivedHaveConnection;
+ mReceivedHaveConnection = 0;
+ }
+
+ if (mReceivedConn) {
+ delete mReceivedConn;
+ mReceivedConn = 0;
+ }
+
+ if (mAccountAdaptor) {
+ delete mAccountAdaptor;
+ mAccountAdaptor = 0;
+ }
+
+ if (mDispatcher) {
+ delete mDispatcher;
+ mDispatcher = 0;
+ }
+
+ mReceivedConns.clear();
+
+ cleanupImpl();
+}
+
+void TestAccountConnectionFactory::cleanupTestCase()
+{
+ if (mConn1) {
+ QCOMPARE(mConn1->disconnect(), true);
+ delete mConn1;
+ }
+
+ if (mConn2) {
+ QCOMPARE(mConn2->disconnect(), true);
+ delete mConn2;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestAccountConnectionFactory)
+#include "_gen/account-connection-factory.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/account-set.cpp b/qt4/tests/dbus/account-set.cpp
new file mode 100644
index 000000000..924567f6d
--- /dev/null
+++ b/qt4/tests/dbus/account-set.cpp
@@ -0,0 +1,416 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo2/conn.h>
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountCapabilityFilter>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/AccountPropertyFilter>
+#include <TelepathyQt4/AccountSet>
+#include <TelepathyQt4/AndFilter>
+#include <TelepathyQt4/NotFilter>
+#include <TelepathyQt4/OrFilter>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingVoid>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestAccountSet : public Test
+{
+ Q_OBJECT
+
+public:
+ TestAccountSet(QObject *parent = 0)
+ : Test(parent),
+ mConn(0)
+ { }
+
+protected Q_SLOTS:
+ void onAccountAdded(const Tp::AccountPtr &);
+ void onAccountRemoved(const Tp::AccountPtr &);
+ void onCreateAccountFinished(Tp::PendingOperation *op);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testBasics();
+ void testFilters();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ void createAccount(const char *cmName, const char *protocolName,
+ const char *displayName, const QVariantMap &parameters);
+ void removeAccount(const AccountPtr &acc);
+ QStringList pathsForAccounts(const QList<Tp::AccountPtr> &list);
+ QStringList pathsForAccounts(const Tp::AccountSetPtr &set);
+
+ AccountManagerPtr mAM;
+ TestConnHelper *mConn;
+ AccountPtr mAccountCreated;
+ AccountPtr mAccountAdded;
+ AccountPtr mAccountRemoved;
+};
+
+void TestAccountSet::onAccountAdded(const Tp::AccountPtr &acc)
+{
+ Q_UNUSED(acc);
+
+ mAccountAdded = acc;
+ qDebug() << "ACCOUNT ADDED:" << acc->objectPath();
+}
+
+void TestAccountSet::onAccountRemoved(const Tp::AccountPtr &acc)
+{
+ Q_UNUSED(acc);
+
+ mAccountRemoved = acc;
+ qDebug() << "ACCOUNT REMOVED:" << acc->objectPath();
+}
+
+void TestAccountSet::onCreateAccountFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingAccount *pa = qobject_cast<PendingAccount*>(op);
+ mAccountCreated = pa->account();
+
+ // the account should appear in the valid accounts set first, so mAccountAdded must be non-null
+ QVERIFY(mAccountAdded);
+ QVERIFY(mAccountCreated);
+
+ qDebug() << "ACCOUNT CREATED:" << mAccountAdded->objectPath();
+ mLoop->exit(0);
+}
+
+void TestAccountSet::createAccount(const char *cmName, const char *protocolName,
+ const char *displayName, const QVariantMap &parameters)
+{
+ AccountSetPtr accounts = mAM->validAccounts();
+
+ // AccountSet listen to AM::newAccount to check for accounts matching its filter.
+ //
+ // PendingAccount calls AM.CreateAccount and waits for the call to finish.
+ // Once the call is finished, if everything is fine, it checks if the account was already added
+ // to the AM or waits till it gets added by connecting to AM::newAccount.
+ // Once the newly created account appears in the AM, it signals PendingAccount::finished.
+ //
+ // So the signal ordering depends on whether the PendingAccount was created before the
+ // AccountSet or not.
+ //
+ // In this case where we are creating the AccountSet before calling Am::createAccount,
+ // the account will first appear in the set via AccountSet::accountAdded and after that the
+ // PendingAccount operation will finish.
+
+ mAccountCreated.reset();
+ mAccountAdded.reset();
+
+ QVERIFY(connect(accounts.data(),
+ SIGNAL(accountAdded(Tp::AccountPtr)),
+ SLOT(onAccountAdded(Tp::AccountPtr))));
+
+ PendingAccount *pacc = mAM->createAccount(QLatin1String(cmName),
+ QLatin1String(protocolName), QLatin1String(displayName), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onCreateAccountFinished(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // check that the added account is the same one that was created
+ QCOMPARE(mAccountAdded, mAccountCreated);
+}
+
+void TestAccountSet::removeAccount(const AccountPtr &acc)
+{
+ QCOMPARE(acc->isValid(), true);
+ AccountSetPtr accounts = mAM->validAccounts();
+ QVERIFY(accounts->accounts().contains(acc));
+
+ int oldAccountsCount = accounts->accounts().size();
+ QVERIFY(connect(accounts.data(),
+ SIGNAL(accountRemoved(Tp::AccountPtr)),
+ SLOT(onAccountRemoved(Tp::AccountPtr))));
+
+ QVERIFY(connect(acc->remove(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // wait the account to disappear from the set
+ while (accounts->accounts().size() != oldAccountsCount - 1) {
+ processDBusQueue(mConn->client().data());
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(acc->isValid(), false);
+ QCOMPARE(acc->invalidationReason(), TP_QT4_ERROR_OBJECT_REMOVED);
+}
+
+QStringList TestAccountSet::pathsForAccounts(const QList<Tp::AccountPtr> &list)
+{
+ QStringList ret;
+ Q_FOREACH (const AccountPtr &account, list) {
+ ret << account->objectPath();
+ }
+ return ret;
+}
+
+QStringList TestAccountSet::pathsForAccounts(const Tp::AccountSetPtr &set)
+{
+ QStringList ret;
+ Q_FOREACH (const AccountPtr &account, set->accounts()) {
+ ret << account->objectPath();
+ }
+ return ret;
+}
+
+void TestAccountSet::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("account-set");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mAM = AccountManager::create(AccountFactory::create(QDBusConnection::sessionBus(),
+ Account::FeatureCore | Account::FeatureCapabilities));
+ QCOMPARE(mAM->isReady(), false);
+
+ QVERIFY(connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAM->isReady(), true);
+
+ QCOMPARE(mAM->allAccounts().isEmpty(), true);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "echo2",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestAccountSet::init()
+{
+ mAccountAdded.reset();
+ mAccountRemoved.reset();
+
+ initImpl();
+}
+
+void TestAccountSet::testBasics()
+{
+ AccountSetPtr validAccounts = mAM->validAccounts();
+
+ // create and remove the same account twice to check whether AccountSet::accountAdded/Removed is
+ // properly emitted and the account becomes invalid after being removed
+ for (int i = 0; i < 2; ++i) {
+ // create the account
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ createAccount("foo", "bar", "foobar", parameters);
+
+ // check that the account is properly created and added to the set of valid accounts
+ QCOMPARE(validAccounts->accounts().size(), 1);
+ QStringList paths = QStringList() <<
+ QLatin1String("/org/freedesktop/Telepathy/Account/foo/bar/Account0");
+ QCOMPARE(pathsForAccounts(validAccounts), paths);
+ QCOMPARE(pathsForAccounts(mAM->invalidAccounts()), QStringList());
+ QCOMPARE(pathsForAccounts(mAM->allAccounts()), paths);
+ QCOMPARE(mAM->allAccounts(), validAccounts->accounts());
+
+ // remove the account
+ AccountPtr acc = validAccounts->accounts().first();
+ QVERIFY(acc);
+ removeAccount(acc);
+
+ // check that the account is properly invalidated and removed from the set
+ QCOMPARE(validAccounts->accounts().size(), 0);
+ QCOMPARE(pathsForAccounts(validAccounts), QStringList());
+ QCOMPARE(pathsForAccounts(mAM->invalidAccounts()), QStringList());
+ QCOMPARE(pathsForAccounts(mAM->allAccounts()), QStringList());
+ QCOMPARE(mAM->allAccounts(), validAccounts->accounts());
+ }
+}
+
+void TestAccountSet::testFilters()
+{
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ createAccount("foo", "bar", "foobar", parameters);
+ QCOMPARE(mAM->allAccounts().size(), 1);
+ QCOMPARE(mAM->validAccounts()->accounts().size(), 1);
+ AccountPtr fooAcc = mAM->allAccounts()[0];
+
+ parameters.clear();
+ parameters[QLatin1String("account")] = QLatin1String("spuriousnormal");
+ createAccount("spurious", "normal", "spuriousnormal", parameters);
+ QCOMPARE(mAM->allAccounts().size(), 2);
+ QCOMPARE(mAM->validAccounts()->accounts().size(), 2);
+ AccountPtr spuriousAcc = mAM->allAccounts()[1];
+
+ Tp::AccountSetPtr filteredAccountSet;
+
+ {
+ QVariantMap filter;
+ filter.insert(QLatin1String("protocolName"), QLatin1String("bar"));
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, filter));
+ QCOMPARE(filteredAccountSet->accounts().size(), 1);
+ QVERIFY(filteredAccountSet->accounts().contains(fooAcc));
+
+ filter.clear();
+ filter.insert(QLatin1String("protocolName"), QLatin1String("normal"));
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, filter));
+ QCOMPARE(filteredAccountSet->accounts().size(), 1);
+ QVERIFY(filteredAccountSet->accounts().contains(spuriousAcc));
+ }
+
+ {
+ QList<AccountFilterConstPtr> filterChain;
+ AccountPropertyFilterPtr cmNameFilter0 = AccountPropertyFilter::create();
+ cmNameFilter0->addProperty(QLatin1String("cmName"), QLatin1String("foo"));
+ AccountPropertyFilterPtr cmNameFilter1 = AccountPropertyFilter::create();
+ cmNameFilter1->addProperty(QLatin1String("cmName"), QLatin1String("spurious"));
+ filterChain.append(cmNameFilter0);
+ filterChain.append(cmNameFilter1);
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM,
+ OrFilter<Account>::create(filterChain)));
+ QCOMPARE(filteredAccountSet->accounts().size(), 2);
+ QVERIFY(filteredAccountSet->accounts().contains(fooAcc));
+ QVERIFY(filteredAccountSet->accounts().contains(spuriousAcc));
+ }
+
+ {
+ QList<AccountFilterConstPtr> filterChain;
+ AccountPropertyFilterPtr cmNameFilter = AccountPropertyFilter::create();
+ cmNameFilter->addProperty(QLatin1String("cmName"), QLatin1String("foo"));
+ AccountCapabilityFilterPtr capsFilter = AccountCapabilityFilter::create();
+ capsFilter->addRequestableChannelClassSubset(RequestableChannelClassSpec::textChat());
+ filterChain.append(cmNameFilter);
+ filterChain.append(capsFilter);
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, AndFilter<Account>::create(filterChain)));
+ QCOMPARE(filteredAccountSet->accounts().size(), 0);
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, OrFilter<Account>::create(filterChain)));
+ QCOMPARE(filteredAccountSet->accounts().size(), 2);
+ QVERIFY(filteredAccountSet->accounts().contains(fooAcc));
+ QVERIFY(filteredAccountSet->accounts().contains(spuriousAcc));
+
+ filterChain.clear();
+ cmNameFilter = AccountPropertyFilter::create();
+ cmNameFilter->addProperty(QLatin1String("cmName"), QLatin1String("spurious"));
+ capsFilter = AccountCapabilityFilter::create();
+ capsFilter->setRequestableChannelClassesSubset(
+ RequestableChannelClassSpecList() << RequestableChannelClassSpec::textChat());
+ filterChain.append(cmNameFilter);
+ filterChain.append(capsFilter);
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, AndFilter<Account>::create(filterChain)));
+ QCOMPARE(filteredAccountSet->accounts().size(), 1);
+ QVERIFY(filteredAccountSet->accounts().contains(spuriousAcc));
+
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, NotFilter<Account>::create(AndFilter<Account>::create(filterChain))));
+ QCOMPARE(filteredAccountSet->accounts().size(), 1);
+ QVERIFY(filteredAccountSet->accounts().contains(fooAcc));
+ }
+
+ {
+ // should not match as allowedProperties has TargetFoo that is not allowed
+ RequestableChannelClassList rccs;
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ rcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) HandleTypeContact);
+ rcc.allowedProperties.append(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"));
+ rcc.allowedProperties.append(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetFoo"));
+ rccs.append(rcc);
+ filteredAccountSet = AccountSetPtr(new AccountSet(mAM, AccountCapabilityFilter::create(rccs)));
+ QCOMPARE(filteredAccountSet->accounts().isEmpty(), true);
+ }
+
+ {
+ // let's change a property and see
+ AccountSetPtr enabledAccounts = mAM->enabledAccounts();
+ QVERIFY(connect(enabledAccounts.data(),
+ SIGNAL(accountRemoved(Tp::AccountPtr)),
+ SLOT(onAccountRemoved(Tp::AccountPtr))));
+ AccountSetPtr disabledAccounts = mAM->disabledAccounts();
+ QVERIFY(connect(disabledAccounts.data(),
+ SIGNAL(accountAdded(Tp::AccountPtr)),
+ SLOT(onAccountAdded(Tp::AccountPtr))));
+
+ QCOMPARE(enabledAccounts->accounts().size(), 2);
+ QCOMPARE(disabledAccounts->accounts().size(), 0);
+
+ QVERIFY(connect(fooAcc->setEnabled(false),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ while (fooAcc->isEnabled() != false) {
+ mLoop->processEvents();
+ }
+
+ processDBusQueue(mConn->client().data());
+
+ QVERIFY(mAccountAdded);
+ QVERIFY(mAccountRemoved);
+ QCOMPARE(mAccountAdded, mAccountRemoved);
+
+ QCOMPARE(enabledAccounts->accounts().size(), 1);
+ QVERIFY(enabledAccounts->accounts().contains(spuriousAcc));
+ QCOMPARE(disabledAccounts->accounts().size(), 1);
+ QVERIFY(disabledAccounts->accounts().contains(fooAcc));
+ }
+
+ {
+ QCOMPARE(mAM->invalidAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->onlineAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->offlineAccounts()->accounts().size(), 2);
+ QCOMPARE(mAM->textChatAccounts()->accounts().size(), 1);
+ QVERIFY(mAM->textChatAccounts()->accounts().contains(spuriousAcc));
+ QCOMPARE(mAM->textChatroomAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->streamedMediaCallAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->streamedMediaAudioCallAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->streamedMediaVideoCallAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->streamedMediaVideoCallWithAudioAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->fileTransferAccounts()->accounts().size(), 0);
+ QCOMPARE(mAM->accountsByProtocol(QLatin1String("bar"))->accounts().size(), 1);
+ QVERIFY(mAM->accountsByProtocol(QLatin1String("bar"))->accounts().contains(fooAcc));
+ QCOMPARE(mAM->accountsByProtocol(QLatin1String("normal"))->accounts().size(), 1);
+ QVERIFY(mAM->accountsByProtocol(QLatin1String("normal"))->accounts().contains(spuriousAcc));
+ QCOMPARE(mAM->accountsByProtocol(QLatin1String("noname"))->accounts().size(), 0);
+ }
+}
+
+void TestAccountSet::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestAccountSet::cleanupTestCase()
+{
+ if (mConn) {
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestAccountSet)
+#include "_gen/account-set.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/chan-basics.cpp b/qt4/tests/dbus/chan-basics.cpp
new file mode 100644
index 000000000..7b27e2758
--- /dev/null
+++ b/qt4/tests/dbus/chan-basics.cpp
@@ -0,0 +1,286 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo2/conn.h>
+#include <tests/lib/glib/textchan-null.h>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/TextChannel>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestChanBasics : public Test
+{
+ Q_OBJECT
+
+public:
+ TestChanBasics(QObject *parent = 0)
+ : Test(parent), mConn(0), mHandle(0)
+ { }
+
+protected Q_SLOTS:
+ void expectInvalidated();
+ void expectPendingHandleFinished(Tp::PendingOperation *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRequestHandle();
+ void testCreateChannel();
+ void testEnsureChannel();
+ void testFallback();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ ChannelPtr mChan;
+ QString mChanObjectPath;
+ uint mHandle;
+};
+
+void TestChanBasics::expectInvalidated()
+{
+ mLoop->exit(0);
+}
+
+void TestChanBasics::expectPendingHandleFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingHandles *pending = qobject_cast<PendingHandles*>(op);
+ mHandle = pending->handles().at(0);
+ mLoop->exit(0);
+}
+
+void TestChanBasics::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("chan-basics");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contacts",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+
+ QCOMPARE(mConn->enableFeatures(Connection::FeatureSelfContact), true);
+}
+
+void TestChanBasics::init()
+{
+ initImpl();
+
+ mChan.reset();
+}
+
+void TestChanBasics::testRequestHandle()
+{
+ // Test identifiers
+ QStringList ids = QStringList() << QLatin1String("alice");
+
+ // Request handles for the identifiers and wait for the request to process
+ PendingHandles *pending = mConn->client()->lowlevel()->requestHandles(Tp::HandleTypeContact, ids);
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingHandleFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(disconnect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ this,
+ SLOT(expectPendingHandleFinished(Tp::PendingOperation*))));
+ QVERIFY(mHandle != 0);
+}
+
+void TestChanBasics::testCreateChannel()
+{
+ mChan = mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_TEXT, Tp::HandleTypeContact, mHandle);
+ QVERIFY(mChan);
+ mChanObjectPath = mChan->objectPath();
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+ QCOMPARE(mChan->isRequested(), true);
+ QCOMPARE(mChan->channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QCOMPARE(mChan->groupCanAddContacts(), false);
+ QCOMPARE(mChan->groupCanRemoveContacts(), false);
+ QCOMPARE(mChan->initiatorContact()->id(), QString(QLatin1String("me@example.com")));
+ QCOMPARE(mChan->groupSelfContact()->id(), QString(QLatin1String("me@example.com")));
+ QCOMPARE(mChan->groupSelfContact(), mConn->client()->selfContact());
+ QCOMPARE(mChan->targetId(), QString::fromLatin1("alice"));
+ QVERIFY(!mChan->targetContact().isNull());
+ QCOMPARE(mChan->targetContact()->id(), QString::fromLatin1("alice"));
+
+ QStringList ids;
+ Q_FOREACH (const ContactPtr &contact, mChan->groupContacts()) {
+ ids << contact->id();
+
+ QVERIFY(contact == mChan->groupSelfContact() || contact == mChan->targetContact());
+ }
+ ids.sort();
+ QStringList toCheck = QStringList() << QLatin1String("me@example.com")
+ << QLatin1String("alice");
+ toCheck.sort();
+ QCOMPARE(ids, toCheck);
+
+ ChannelPtr chan = Channel::create(mConn->client(), mChan->objectPath(),
+ mChan->immutableProperties());
+ QVERIFY(chan);
+ QVERIFY(chan->isValid());
+ QVERIFY(connect(chan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(chan->isReady(), true);
+ QCOMPARE(chan->channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+
+ // create an invalid connection to use as the channel connection
+ ConnectionPtr conn = Connection::create(QLatin1String(""), QLatin1String("/"),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QVERIFY(conn);
+ QVERIFY(!conn->isValid());
+
+ chan = Channel::create(conn, mChan->objectPath(),
+ mChan->immutableProperties());
+ QVERIFY(chan);
+ QVERIFY(!chan->isValid());
+ QVERIFY(connect(chan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, chan->invalidationReason());
+ QCOMPARE(mLastErrorMessage, chan->invalidationMessage());
+ QCOMPARE(chan->channelType(), QString());
+}
+
+void TestChanBasics::testEnsureChannel()
+{
+ ChannelPtr chan = mConn->ensureChannel(TP_QT4_IFACE_CHANNEL_TYPE_TEXT,
+ Tp::HandleTypeContact, mHandle);
+ QVERIFY(chan);
+ QCOMPARE(chan->objectPath(), mChanObjectPath);
+ mChan = chan;
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+ QCOMPARE(mChan->isRequested(), true);
+ QCOMPARE(mChan->groupCanAddContacts(), false);
+ QCOMPARE(mChan->groupCanRemoveContacts(), false);
+ QCOMPARE(mChan->initiatorContact()->id(), QString(QLatin1String("me@example.com")));
+ QCOMPARE(mChan->groupSelfContact()->id(), QString(QLatin1String("me@example.com")));
+ QCOMPARE(mChan->groupSelfContact(), mConn->client()->selfContact());
+
+ QStringList ids;
+ Q_FOREACH (const ContactPtr &contact, mChan->groupContacts()) {
+ ids << contact->id();
+ }
+ ids.sort();
+ QStringList toCheck = QStringList() << QLatin1String("me@example.com")
+ << QLatin1String("alice");
+ toCheck.sort();
+ QCOMPARE(ids, toCheck);
+
+ QVERIFY(connect(mChan->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isValid(), false);
+
+ // calling requestClose again should be no-op
+ QVERIFY(connect(mChan->requestClose(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isValid(), false);
+}
+
+void TestChanBasics::testFallback()
+{
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()),
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(contactRepo, "someone@localhost", 0, 0);
+
+ QString textChanPath = mConn->objectPath() + QLatin1String("/Channel");
+ QByteArray chanPath(textChanPath.toAscii());
+
+ TpTestsTextChannelNull *textChanService = TP_TESTS_TEXT_CHANNEL_NULL (g_object_new (
+ TP_TESTS_TYPE_TEXT_CHANNEL_NULL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ TextChannelPtr textChan = TextChannel::create(mConn->client(), textChanPath, QVariantMap());
+ QVERIFY(connect(textChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(textChan->isReady(), true);
+
+ QCOMPARE(textChanService->get_channel_type_called, static_cast<uint>(1));
+ QCOMPARE(textChanService->get_interfaces_called, static_cast<uint>(1));
+ QCOMPARE(textChanService->get_handle_called, static_cast<uint>(1));
+
+ QCOMPARE(textChan->channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QVERIFY(textChan->interfaces().isEmpty());
+ QCOMPARE(textChan->targetHandleType(), Tp::HandleTypeContact);
+ QCOMPARE(textChan->targetHandle(), handle);
+
+ // we have no Group support, groupAddContacts should fail
+ QVERIFY(connect(textChan->groupAddContacts(QList<ContactPtr>() << mConn->client()->selfContact()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, TP_QT4_ERROR_NOT_IMPLEMENTED);
+ QVERIFY(!mLastErrorMessage.isEmpty());
+}
+
+void TestChanBasics::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestChanBasics::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ if (mChan) {
+ QVERIFY(!mChan->isValid());
+ QVERIFY(mChan->invalidationReason() == TP_QT4_ERROR_CANCELLED ||
+ mChan->invalidationReason() == TP_QT4_ERROR_ORPHANED);
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestChanBasics)
+#include "_gen/chan-basics.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/chan-conference.cpp b/qt4/tests/dbus/chan-conference.cpp
new file mode 100644
index 000000000..4e1ac7559
--- /dev/null
+++ b/qt4/tests/dbus/chan-conference.cpp
@@ -0,0 +1,288 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo/chan.h>
+#include <tests/lib/glib/echo/conn.h>
+#include <tests/lib/glib/future/conference/chan.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/PendingReady>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestConferenceChan : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConferenceChan(QObject *parent = 0)
+ : Test(parent),
+ mConn(0), mContactRepo(0),
+ mTextChan1Service(0), mTextChan2Service(0), mConferenceChanService(0)
+ { }
+
+protected Q_SLOTS:
+ void onConferenceChannelMerged(const Tp::ChannelPtr &);
+ void onConferenceChannelRemoved(const Tp::ChannelPtr &channel,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testConference();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ TpHandleRepoIface *mContactRepo;
+
+ ChannelPtr mChan;
+ QString mTextChan1Path;
+ ExampleEchoChannel *mTextChan1Service;
+ QString mTextChan2Path;
+ ExampleEchoChannel *mTextChan2Service;
+ QString mTextChan3Path;
+ ExampleEchoChannel *mTextChan3Service;
+ QString mConferenceChanPath;
+ TpTestsConferenceChannel *mConferenceChanService;
+
+ ChannelPtr mChannelMerged;
+ ChannelPtr mChannelRemovedDetailed;
+ Channel::GroupMemberChangeDetails mChannelRemovedDetailedDetails;
+};
+
+void TestConferenceChan::onConferenceChannelMerged(const Tp::ChannelPtr &channel)
+{
+ mChannelMerged = channel;
+ mLoop->exit(0);
+}
+
+void TestConferenceChan::onConferenceChannelRemoved(const Tp::ChannelPtr &channel,
+ const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ mChannelRemovedDetailed = channel;
+ mChannelRemovedDetailedDetails = details;
+ mLoop->exit(0);
+}
+
+void TestConferenceChan::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("chan-conference");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+
+ // create a Channel by magic, rather than doing D-Bus round-trips for it
+ mContactRepo = tp_base_connection_get_handles(TP_BASE_CONNECTION(mConn->service()),
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle1 = tp_handle_ensure(mContactRepo, "someone1@localhost", 0, 0);
+ guint handle2 = tp_handle_ensure(mContactRepo, "someone2@localhost", 0, 0);
+ guint handle3 = tp_handle_ensure(mContactRepo, "someone3@localhost", 0, 0);
+
+ QByteArray chanPath;
+ GPtrArray *initialChannels = g_ptr_array_new();
+
+ mTextChan1Path = mConn->objectPath() + QLatin1String("/TextChannel/1");
+ chanPath = mTextChan1Path.toAscii();
+ mTextChan1Service = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle1,
+ NULL));
+ g_ptr_array_add(initialChannels, g_strdup(chanPath.data()));
+
+ mTextChan2Path = mConn->objectPath() + QLatin1String("/TextChannel/2");
+ chanPath = mTextChan2Path.toAscii();
+ mTextChan2Service = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle2,
+ NULL));
+ g_ptr_array_add(initialChannels, g_strdup(chanPath.data()));
+
+ // let's not add this one to initial channels
+ mTextChan3Path = mConn->objectPath() + QLatin1String("/TextChannel/3");
+ chanPath = mTextChan3Path.toAscii();
+ mTextChan3Service = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle3,
+ NULL));
+
+ mConferenceChanPath = mConn->objectPath() + QLatin1String("/ConferenceChannel");
+ chanPath = mConferenceChanPath.toAscii();
+ mConferenceChanService = TP_TESTS_CONFERENCE_CHANNEL(g_object_new(
+ TP_TESTS_TYPE_CONFERENCE_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "initial-channels", initialChannels,
+ NULL));
+
+ tp_handle_unref(mContactRepo, handle1);
+ tp_handle_unref(mContactRepo, handle2);
+ tp_handle_unref(mContactRepo, handle3);
+
+ g_ptr_array_foreach(initialChannels, (GFunc) g_free, NULL);
+ g_ptr_array_free(initialChannels, TRUE);
+}
+
+void TestConferenceChan::init()
+{
+ initImpl();
+}
+
+void TestConferenceChan::testConference()
+{
+ mChan = Channel::create(mConn->client(), mConferenceChanPath, QVariantMap());
+ QCOMPARE(mChan->isConference(), false);
+ QVERIFY(mChan->conferenceInitialInviteeContacts().isEmpty());
+ QVERIFY(mChan->conferenceChannels().isEmpty());
+ QVERIFY(mChan->conferenceInitialChannels().isEmpty());
+ QVERIFY(mChan->conferenceOriginalChannels().isEmpty());
+ QCOMPARE(mChan->supportsConferenceMerging(), false);
+ QCOMPARE(mChan->supportsConferenceSplitting(), false);
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+
+ QStringList expectedObjectPaths;
+ expectedObjectPaths << mTextChan1Path << mTextChan2Path;
+
+ QStringList objectPaths;
+ Q_FOREACH (const ChannelPtr &channel, mChan->conferenceInitialChannels()) {
+ objectPaths << channel->objectPath();
+ }
+ QCOMPARE(expectedObjectPaths, objectPaths);
+
+ objectPaths.clear();
+ Q_FOREACH (const ChannelPtr &channel, mChan->conferenceChannels()) {
+ objectPaths << channel->objectPath();
+ }
+ QCOMPARE(expectedObjectPaths, objectPaths);
+
+ /*
+ // TODO - Properly check for initial invitee contacts if/when a test CM supports it
+ QVERIFY(!mChan->isReady(Channel::FeatureConferenceInitialInviteeContacts));
+ QCOMPARE(mChan->conferenceInitialInviteeContacts(), Contacts());
+
+ QVERIFY(connect(mChan->becomeReady(Channel::FeatureConferenceInitialInviteeContacts),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(Channel::FeatureConferenceInitialInviteeContacts), true);
+
+ QCOMPARE(mChan->conferenceInitialInviteeContacts(), Contacts());
+ */
+
+ QCOMPARE(mChan->supportsConferenceMerging(), true);
+ QCOMPARE(mChan->supportsConferenceSplitting(), false);
+ QVERIFY(connect(mChan->conferenceSplitChannel(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, TP_QT4_ERROR_NOT_IMPLEMENTED);
+ QVERIFY(!mLastErrorMessage.isEmpty());
+
+ ChannelPtr otherChannel = Channel::create(mConn->client(), mTextChan3Path, QVariantMap());
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(conferenceChannelMerged(const Tp::ChannelPtr &)),
+ SLOT(onConferenceChannelMerged(const Tp::ChannelPtr &))));
+ QVERIFY(connect(mChan->conferenceMergeChannel(otherChannel),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+ while (!mChannelMerged) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mChannelMerged->objectPath(), otherChannel->objectPath());
+
+ expectedObjectPaths << mTextChan3Path;
+ objectPaths.clear();
+ Q_FOREACH (const ChannelPtr &channel, mChan->conferenceChannels()) {
+ objectPaths << channel->objectPath();
+ }
+ QCOMPARE(expectedObjectPaths, objectPaths);
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(conferenceChannelRemoved(const Tp::ChannelPtr &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onConferenceChannelRemoved(const Tp::ChannelPtr &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+ tp_tests_conference_channel_remove_channel (mConferenceChanService,
+ mChannelMerged->objectPath().toAscii().data());
+ while (!mChannelRemovedDetailed) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mChannelRemovedDetailed, mChannelMerged);
+ QCOMPARE(mChannelRemovedDetailedDetails.allDetails().isEmpty(), false);
+ QCOMPARE(qdbus_cast<int>(mChannelRemovedDetailedDetails.allDetails().value(
+ QLatin1String("domain-specific-detail-uint"))), 3);
+ QCOMPARE(mChannelRemovedDetailedDetails.hasActor(), true);
+ QCOMPARE(mChannelRemovedDetailedDetails.actor(), mChan->groupSelfContact());
+
+ expectedObjectPaths.clear();
+ expectedObjectPaths << mTextChan1Path << mTextChan2Path;
+ objectPaths.clear();
+ Q_FOREACH (const ChannelPtr &channel, mChan->conferenceChannels()) {
+ objectPaths << channel->objectPath();
+ }
+ QCOMPARE(expectedObjectPaths, objectPaths);
+
+ mChan.reset();
+ mChannelMerged.reset();
+}
+
+void TestConferenceChan::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestConferenceChan::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ if (mTextChan1Service != 0) {
+ g_object_unref(mTextChan1Service);
+ mTextChan1Service = 0;
+ }
+
+ if (mTextChan2Service != 0) {
+ g_object_unref(mTextChan2Service);
+ mTextChan2Service = 0;
+ }
+
+ if (mConferenceChanService != 0) {
+ g_object_unref(mConferenceChanService);
+ mConferenceChanService = 0;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConferenceChan)
+#include "_gen/chan-conference.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/chan-group.cpp b/qt4/tests/dbus/chan-group.cpp
new file mode 100644
index 000000000..dfdd53a82
--- /dev/null
+++ b/qt4/tests/dbus/chan-group.cpp
@@ -0,0 +1,511 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contactlist/conn.h>
+#include <tests/lib/glib/textchan-group.h>
+#include <tests/lib/glib/textchan-null.h>
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/TextChannel>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestChanGroup : public Test
+{
+ Q_OBJECT
+
+public:
+ TestChanGroup(QObject *parent = 0)
+ : Test(parent), mConn(0), mChanService(0),
+ mGotGroupFlagsChanged(false),
+ mGroupFlags((ChannelGroupFlags) 0),
+ mGroupFlagsAdded((ChannelGroupFlags) 0),
+ mGroupFlagsRemoved((ChannelGroupFlags) 0)
+ { }
+
+protected Q_SLOTS:
+ void onGroupMembersChanged(
+ const Tp::Contacts &groupMembersAdded,
+ const Tp::Contacts &groupLocalPendingMembersAdded,
+ const Tp::Contacts &groupRemotePendingMembersAdded,
+ const Tp::Contacts &groupMembersRemoved,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void onGroupFlagsChanged(Tp::ChannelGroupFlags flags,
+ Tp::ChannelGroupFlags added, Tp::ChannelGroupFlags removed);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testCreateChannel();
+ void testMCDGroup();
+ void testPropertylessGroup();
+ void testLeave();
+ void testLeaveWithFallback();
+ void testGroupFlagsChange();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ void debugContacts();
+
+ void commonTest(gboolean properties);
+
+ TestConnHelper *mConn;
+ TpTestsTextChannelGroup *mChanService;
+ ChannelPtr mChan;
+ QString mChanObjectPath;
+ QList<ContactPtr> mContacts;
+ Contacts mChangedCurrent;
+ Contacts mChangedLP;
+ Contacts mChangedRP;
+ Contacts mChangedRemoved;
+ Channel::GroupMemberChangeDetails mDetails;
+ UIntList mInitialMembers;
+ bool mGotGroupFlagsChanged;
+ ChannelGroupFlags mGroupFlags;
+ ChannelGroupFlags mGroupFlagsAdded;
+ ChannelGroupFlags mGroupFlagsRemoved;
+};
+
+void TestChanGroup::onGroupMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ qDebug() << "group members changed";
+ mChangedCurrent = groupMembersAdded;
+ mChangedLP = groupLocalPendingMembersAdded;
+ mChangedRP = groupRemotePendingMembersAdded;
+ mChangedRemoved = groupMembersRemoved;
+ mDetails = details;
+ debugContacts();
+ mLoop->exit(0);
+}
+
+void TestChanGroup::onGroupFlagsChanged(Tp::ChannelGroupFlags flags,
+ Tp::ChannelGroupFlags added, Tp::ChannelGroupFlags removed)
+{
+ qDebug() << "group flags changed";
+ mGotGroupFlagsChanged = true;
+ mGroupFlags = flags;
+ mGroupFlagsAdded = added;
+ mGroupFlagsRemoved = removed;
+}
+
+void TestChanGroup::debugContacts()
+{
+ qDebug() << "contacts on group:";
+ Q_FOREACH (const ContactPtr &contact, mChan->groupContacts()) {
+ qDebug() << " " << contact->id();
+ }
+
+ qDebug() << "local pending contacts on group:";
+ Q_FOREACH (const ContactPtr &contact, mChan->groupLocalPendingContacts()) {
+ qDebug() << " " << contact->id();
+ }
+
+ qDebug() << "remote pending contacts on group:";
+ Q_FOREACH (const ContactPtr &contact, mChan->groupRemotePendingContacts()) {
+ qDebug() << " " << contact->id();
+ }
+}
+
+void TestChanGroup::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("chan-group");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example-contact-list",
+ "simulation-delay", 1,
+ NULL);
+ QCOMPARE(mConn->connect(Connection::FeatureSelfContact), true);
+
+ QStringList ids;
+ ids << QLatin1String("sjoerd@example.com");
+
+ // Check that the contact is properly built
+ mContacts = mConn->contacts(ids);
+ QCOMPARE(mContacts.size(), 1);
+ QVERIFY(!mContacts.first().isNull());
+}
+
+void TestChanGroup::init()
+{
+ initImpl();
+
+ mChangedCurrent.clear();
+ mChangedLP.clear();
+ mChangedRP.clear();
+ mChangedRemoved.clear();
+ mDetails = Channel::GroupMemberChangeDetails();
+ mGotGroupFlagsChanged = false;
+ mGroupFlags = (ChannelGroupFlags) 0;
+ mGroupFlagsAdded = (ChannelGroupFlags) 0;
+ mGroupFlagsRemoved = (ChannelGroupFlags) 0;
+}
+
+void TestChanGroup::testCreateChannel()
+{
+ mChan = mConn->ensureChannel(TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+ Tp::HandleTypeGroup, QLatin1String("Cambridge"));
+ QVERIFY(mChan);
+ mChanObjectPath = mChan->objectPath();
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+
+ QCOMPARE(static_cast<uint>(mChan->targetHandleType()),
+ static_cast<uint>(Tp::HandleTypeGroup));
+ QVERIFY(mChan->targetContact().isNull());
+
+ QCOMPARE(mChan->isRequested(), false);
+ QVERIFY(mChan->groupContacts().contains(mContacts.first()));
+
+ Q_FOREACH (ContactPtr contact, mChan->groupContacts())
+ mInitialMembers.push_back(contact->handle()[0]);
+
+ QCOMPARE(mChan->groupFlags(), ChannelGroupFlagCanAdd |
+ ChannelGroupFlagCanRemove | ChannelGroupFlagProperties);
+
+ QCOMPARE(mChan->groupCanAddContacts(), true);
+ QCOMPARE(mChan->groupCanAddContactsWithMessage(), false);
+ QCOMPARE(mChan->groupCanAcceptContactsWithMessage(), false);
+ QCOMPARE(mChan->groupCanRescindContacts(), false);
+ QCOMPARE(mChan->groupCanRescindContactsWithMessage(), false);
+ QCOMPARE(mChan->groupCanRemoveContacts(), true);
+ QCOMPARE(mChan->groupCanRemoveContactsWithMessage(), false);
+ QCOMPARE(mChan->groupCanRejectContactsWithMessage(), false);
+ QCOMPARE(mChan->groupCanDepartWithMessage(), false);
+
+ QCOMPARE(mChan->groupIsSelfContactTracked(), true);
+
+ debugContacts();
+
+ QCOMPARE(mChan->groupContacts().count(), 4);
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(groupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+
+ QList<ContactPtr> toRemove;
+ toRemove.append(mContacts[0]);
+ connect(mChan->groupRemoveContacts(toRemove, QLatin1String("I want to remove some of them")),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *)));
+ QCOMPARE(mLoop->exec(), 0);
+
+ while (mChangedRemoved.isEmpty()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QVERIFY(mChangedRemoved.contains(mContacts[0]));
+
+ QCOMPARE(mChan->groupContacts().count(), 3);
+}
+
+void TestChanGroup::testMCDGroup()
+{
+ commonTest(true);
+}
+
+void TestChanGroup::testPropertylessGroup()
+{
+ commonTest(false);
+}
+
+void TestChanGroup::commonTest(gboolean properties)
+{
+ mChanObjectPath = QString(QLatin1String("%1/ChannelForTpQt4MCDTest%2"))
+ .arg(mConn->objectPath())
+ .arg(QLatin1String(properties ? "props" : ""));
+ QByteArray chanPathLatin1(mChanObjectPath.toLatin1());
+
+ mChanService = TP_TESTS_TEXT_CHANNEL_GROUP(g_object_new(
+ TP_TESTS_TYPE_TEXT_CHANNEL_GROUP,
+ "connection", mConn->service(),
+ "object-path", chanPathLatin1.data(),
+ "detailed", TRUE,
+ "properties", properties,
+ NULL));
+ QVERIFY(mChanService != 0);
+
+ TpIntSet *members = tp_intset_sized_new(mInitialMembers.length());
+ Q_FOREACH (uint handle, mInitialMembers)
+ tp_intset_add(members, handle);
+
+ QVERIFY(tp_group_mixin_change_members(G_OBJECT(mChanService), "be there or be []",
+ members, NULL, NULL, NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE));
+
+ tp_intset_destroy(members);
+
+ mChan = Channel::create(mConn->client(), mChanObjectPath, QVariantMap());
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+
+ QCOMPARE(mChan->isRequested(), true);
+ QVERIFY(mChan->groupContacts().contains(mContacts.first()));
+
+ Q_FOREACH (ContactPtr contact, mChan->groupContacts())
+ mInitialMembers.push_back(contact->handle()[0]);
+
+ QCOMPARE(mChan->groupCanAddContacts(), false);
+ QCOMPARE(mChan->groupCanRemoveContacts(), false);
+
+ debugContacts();
+
+ QCOMPARE(mChan->groupContacts().count(), 4);
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(groupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+
+ TpIntSet *remove = tp_intset_new_containing(mContacts[0]->handle()[0]);
+
+ QVERIFY(tp_group_mixin_change_members(G_OBJECT(mChanService), "be a []",
+ NULL, remove, NULL, NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE));
+
+ tp_intset_destroy(remove);
+
+ while (mChangedRemoved.isEmpty()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QVERIFY(mChangedRemoved.contains(mContacts[0]));
+
+ QCOMPARE(mChan->groupContacts().count(), 3);
+}
+
+void TestChanGroup::testLeave()
+{
+ mChan = mConn->ensureChannel(TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+ Tp::HandleTypeGroup, QLatin1String("Cambridge"));
+ QVERIFY(mChan);
+ mChanObjectPath = mChan->objectPath();
+
+ // channel is not ready yet, it should fail
+ QVERIFY(connect(mChan->groupAddContacts(QList<ContactPtr>() << mConn->client()->selfContact()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, TP_QT4_ERROR_NOT_AVAILABLE);
+ QVERIFY(!mLastErrorMessage.isEmpty());
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+
+ // passing no contact should also fail
+ QVERIFY(connect(mChan->groupAddContacts(QList<ContactPtr>()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, TP_QT4_ERROR_INVALID_ARGUMENT);
+ QVERIFY(!mLastErrorMessage.isEmpty());
+
+ // passing an invalid contact too
+ QVERIFY(connect(mChan->groupAddContacts(QList<ContactPtr>() << ContactPtr()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, TP_QT4_ERROR_INVALID_ARGUMENT);
+ QVERIFY(!mLastErrorMessage.isEmpty());
+
+ // now it should work
+ QVERIFY(connect(mChan->groupAddContacts(QList<ContactPtr>() << mConn->client()->selfContact()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ QString leaveMessage(QLatin1String("I'm sick of this lot"));
+ QVERIFY(connect(mChan->requestLeave(leaveMessage),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ // We left gracefully, which we have details for.
+ // Unfortunately, the test CM used here ignores the message and the reason specified, so can't
+ // verify those. When the leave code was originally written however, it was able to carry out
+ // the almost-impossible mission of delivering the message and reason to the CM admirably, as
+ // verified by dbus-monitor.
+ QVERIFY(mChan->groupSelfContactRemoveInfo().isValid());
+ QVERIFY(mChan->groupSelfContactRemoveInfo().hasActor());
+ QVERIFY(mChan->groupSelfContactRemoveInfo().actor() == mConn->client()->selfContact());
+
+ // Another leave should no-op succeed, because we aren't a member
+ QVERIFY(connect(mChan->requestLeave(leaveMessage),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestChanGroup::testLeaveWithFallback()
+{
+ mChanObjectPath = QString(QLatin1String("%1/ChannelForTpQt4LeaveTestFallback"))
+ .arg(mConn->objectPath());
+ QByteArray chanPathLatin1(mChanObjectPath.toLatin1());
+
+ // The text channel doesn't support removing, so will trigger the fallback
+ mChanService = TP_TESTS_TEXT_CHANNEL_GROUP(g_object_new(
+ TP_TESTS_TYPE_TEXT_CHANNEL_GROUP,
+ "connection", mConn->service(),
+ "object-path", chanPathLatin1.data(),
+ "detailed", TRUE,
+ "properties", TRUE,
+ NULL));
+ QVERIFY(mChanService != 0);
+
+ TpIntSet *members = tp_intset_sized_new(1);
+ tp_intset_add(members, mConn->client()->selfHandle());
+
+ QVERIFY(tp_group_mixin_change_members(G_OBJECT(mChanService), "be there or be []",
+ members, NULL, NULL, NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE));
+
+ tp_intset_destroy(members);
+
+ mChan = Channel::create(mConn->client(), mChanObjectPath, QVariantMap());
+ QVERIFY(mChan);
+
+ // Should fail, because not ready (and thus we can't know how to leave)
+ QVERIFY(connect(mChan->requestLeave(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(), true);
+
+ // Should now work
+ QString leaveMessage(QLatin1String("I'm sick of this lot"));
+ QVERIFY(connect(mChan->requestLeave(leaveMessage),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // The Close fallback was triggered, so we weren't removed gracefully and the details were
+ // lost
+ QVERIFY(!mChan->groupSelfContactRemoveInfo().hasMessage());
+}
+
+void TestChanGroup::testGroupFlagsChange()
+{
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()),
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(contactRepo, "someone@localhost", 0, 0);
+
+ QString textChanPath = mConn->objectPath() + QLatin1String("/Channel");
+ QByteArray chanPath(textChanPath.toAscii());
+
+ TpTestsPropsGroupTextChannel *textChanService = TP_TESTS_PROPS_GROUP_TEXT_CHANNEL(g_object_new(
+ TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ TextChannelPtr textChan = TextChannel::create(mConn->client(), textChanPath, QVariantMap());
+ QVERIFY(connect(textChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(textChan->isReady(), true);
+
+ QVERIFY(textChan->interfaces().contains(TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP));
+ QVERIFY(!(textChan->groupFlags() & ChannelGroupFlagCanAdd));
+ QVERIFY(!textChan->canInviteContacts());
+
+ QVERIFY(connect(textChan.data(),
+ SIGNAL(groupFlagsChanged(Tp::ChannelGroupFlags,Tp::ChannelGroupFlags,Tp::ChannelGroupFlags)),
+ SLOT(onGroupFlagsChanged(Tp::ChannelGroupFlags,Tp::ChannelGroupFlags,Tp::ChannelGroupFlags))));
+ tp_group_mixin_change_flags(G_OBJECT(textChanService),
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD, (TpChannelGroupFlags) 0);
+ processDBusQueue(mConn->client().data());
+ while (!mGotGroupFlagsChanged) {
+ mLoop->processEvents();
+ }
+ QCOMPARE(textChan->groupFlags(), mGroupFlags);
+ QVERIFY(textChan->groupFlags() & ChannelGroupFlagCanAdd);
+ QVERIFY(textChan->canInviteContacts());
+ QCOMPARE(mGroupFlagsAdded, ChannelGroupFlagCanAdd);
+ QCOMPARE(mGroupFlagsRemoved, (ChannelGroupFlags) 0);
+}
+
+void TestChanGroup::cleanup()
+{
+ if (mChanService) {
+ g_object_unref(mChanService);
+ mChanService = 0;
+ }
+
+ // Avoid D-Bus event leak from one test case to another - I've seen this with the
+ // testCreateChannel groupMembersChanged leaking at least
+ processDBusQueue(mConn->client().data());
+ mChan.reset();
+
+ cleanupImpl();
+}
+
+void TestChanGroup::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestChanGroup)
+#include "_gen/chan-group.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/client-factories.cpp b/qt4/tests/dbus/client-factories.cpp
new file mode 100644
index 000000000..ef5aa445e
--- /dev/null
+++ b/qt4/tests/dbus/client-factories.cpp
@@ -0,0 +1,1184 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtDBus/QtDBus>
+#include <QtTest/QtTest>
+
+#include <QDateTime>
+#include <QString>
+#include <QVariantMap>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountFactory>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/AbstractClientObserver>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelClassSpecList>
+#include <TelepathyQt4/ChannelDispatchOperation>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/ClientHandlerInterface>
+#include <TelepathyQt4/ClientInterfaceRequestsInterface>
+#include <TelepathyQt4/ClientObserverInterface>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactSearchChannel>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/FileTransferChannel>
+#include <TelepathyQt4/IncomingFileTransferChannel>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/MethodInvocationContext>
+#include <TelepathyQt4/OutgoingFileTransferChannel>
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/StreamedMediaChannel>
+#include <TelepathyQt4/StreamTubeChannel>
+#include <TelepathyQt4/TextChannel>
+#include <TelepathyQt4/Types>
+
+#include <telepathy-glib/debug.h>
+
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/glib/echo/chan.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+class ChannelRequestAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelRequest")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelRequest\" >\n"
+" <property name=\"Account\" type=\"o\" access=\"read\" />\n"
+" <property name=\"UserActionTime\" type=\"x\" access=\"read\" />\n"
+" <property name=\"PreferredHandler\" type=\"s\" access=\"read\" />\n"
+" <property name=\"Requests\" type=\"aa{sv}\" access=\"read\" />\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <method name=\"Proceed\" />\n"
+" <method name=\"Cancel\" />\n"
+" <signal name=\"Failed\" >\n"
+" <arg name=\"Error\" type=\"s\" />\n"
+" <arg name=\"Message\" type=\"s\" />\n"
+" </signal>\n"
+" <signal name=\"Succeeded\" />"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Account READ Account)
+ Q_PROPERTY(qulonglong UserActionTime READ UserActionTime)
+ Q_PROPERTY(QString PreferredHandler READ PreferredHandler)
+ Q_PROPERTY(Tp::QualifiedPropertyValueMapList Requests READ Requests)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+
+public:
+ ChannelRequestAdaptor(QDBusObjectPath account,
+ qulonglong userActionTime,
+ QString preferredHandler,
+ QualifiedPropertyValueMapList requests,
+ QStringList interfaces,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mAccount(account), mUserActionTime(userActionTime),
+ mPreferredHandler(preferredHandler), mRequests(requests),
+ mInterfaces(interfaces)
+ {
+ }
+
+ virtual ~ChannelRequestAdaptor()
+ {
+ }
+
+public: // Properties
+ inline QDBusObjectPath Account() const
+ {
+ return mAccount;
+ }
+
+ inline qulonglong UserActionTime() const
+ {
+ return mUserActionTime;
+ }
+
+ inline QString PreferredHandler() const
+ {
+ return mPreferredHandler;
+ }
+
+ inline QualifiedPropertyValueMapList Requests() const
+ {
+ return mRequests;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+public Q_SLOTS: // Methods
+ void Proceed()
+ {
+ }
+
+ void Cancel()
+ {
+ }
+
+Q_SIGNALS: // Signals
+ void Failed(const QString &error, const QString &message);
+ void Succeeded();
+
+private:
+ QDBusObjectPath mAccount;
+ qulonglong mUserActionTime;
+ QString mPreferredHandler;
+ QualifiedPropertyValueMapList mRequests;
+ QStringList mInterfaces;
+};
+
+// Totally incomplete mini version of ChannelDispatchOperation
+class ChannelDispatchOperationAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelDispatchOperation")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelDispatchOperation\" >\n"
+" <property name=\"Account\" type=\"o\" access=\"read\" />\n"
+" <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
+" <property name=\"Channels\" type=\"a(oa{sv})\" access=\"read\" />\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"PossibleHandlers\" type=\"as\" access=\"read\" />\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Account READ Account)
+ Q_PROPERTY(QDBusObjectPath Connection READ Connection)
+ Q_PROPERTY(Tp::ChannelDetailsList Channels READ Channels)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+ Q_PROPERTY(QStringList PossibleHandlers READ PossibleHandlers)
+
+public:
+ ChannelDispatchOperationAdaptor(const QDBusObjectPath &acc, const QDBusObjectPath &conn,
+ const ChannelDetailsList &channels, const QStringList &possibleHandlers,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent), mAccount(acc), mConn(conn), mChannels(channels),
+ mPossibleHandlers(possibleHandlers)
+ {
+ }
+
+ virtual ~ChannelDispatchOperationAdaptor()
+ {
+ }
+
+public: // Properties
+ inline QDBusObjectPath Account() const
+ {
+ return mAccount;
+ }
+
+ inline QDBusObjectPath Connection() const
+ {
+ return mConn;
+ }
+
+ inline ChannelDetailsList Channels() const
+ {
+ return mChannels;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+ inline QStringList PossibleHandlers() const
+ {
+ return mPossibleHandlers;
+ }
+
+private:
+ QDBusObjectPath mAccount, mConn;
+ ChannelDetailsList mChannels;
+ QStringList mInterfaces;
+ QStringList mPossibleHandlers;
+};
+
+class MyClient : public QObject,
+ public AbstractClientObserver,
+ public AbstractClientApprover,
+ public AbstractClientHandler
+{
+ Q_OBJECT
+
+public:
+ static AbstractClientPtr create(const ChannelClassSpecList &channelFilter,
+ const AbstractClientHandler::Capabilities &capabilities,
+ bool bypassApproval = false,
+ bool wantsRequestNotification = false)
+ {
+ return AbstractClientPtr::dynamicCast(SharedPtr<MyClient>(
+ new MyClient(channelFilter, capabilities,
+ bypassApproval, wantsRequestNotification)));
+ }
+
+ MyClient(const ChannelClassSpecList &channelFilter,
+ const AbstractClientHandler::Capabilities &capabilities,
+ bool bypassApproval = false,
+ bool wantsRequestNotification = false)
+ : AbstractClientObserver(channelFilter),
+ AbstractClientApprover(channelFilter),
+ AbstractClientHandler(channelFilter, capabilities, wantsRequestNotification),
+ mBypassApproval(bypassApproval)
+ {
+ }
+
+ ~MyClient()
+ {
+ }
+
+ void observeChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const ChannelDispatchOperationPtr &dispatchOperation,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const AbstractClientObserver::ObserverInfo &observerInfo)
+ {
+ mObserveChannelsAccount = account;
+ mObserveChannelsConnection = connection;
+ mObserveChannelsChannels = channels;
+ mObserveChannelsDispatchOperation = dispatchOperation;
+ mObserveChannelsRequestsSatisfied = requestsSatisfied;
+ mObserveChannelsObserverInfo = observerInfo;
+
+ context->setFinished();
+ QTimer::singleShot(0, this, SIGNAL(observeChannelsFinished()));
+ }
+
+ void addDispatchOperation(const MethodInvocationContextPtr<> &context,
+ const ChannelDispatchOperationPtr &dispatchOperation)
+ {
+ mAddDispatchOperationChannels = dispatchOperation->channels();
+ mAddDispatchOperationDispatchOperation = dispatchOperation;
+
+ context->setFinished();
+ QTimer::singleShot(0, this, SIGNAL(addDispatchOperationFinished()));
+ }
+
+ bool bypassApproval() const
+ {
+ return mBypassApproval;
+ }
+
+ void handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const AbstractClientHandler::HandlerInfo &handlerInfo)
+ {
+ mHandleChannelsAccount = account;
+ mHandleChannelsConnection = connection;
+ mHandleChannelsChannels = channels;
+ mHandleChannelsRequestsSatisfied = requestsSatisfied;
+ mHandleChannelsUserActionTime = userActionTime;
+ mHandleChannelsHandlerInfo = handlerInfo;
+
+ Q_FOREACH (const ChannelPtr &channel, channels) {
+ connect(channel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SIGNAL(channelClosed()));
+ }
+
+ context->setFinished();
+ QTimer::singleShot(0, this, SIGNAL(handleChannelsFinished()));
+ }
+
+ void addRequest(const ChannelRequestPtr &request)
+ {
+ mAddRequestRequest = request;
+ Q_EMIT requestAdded(request);
+ }
+
+ void removeRequest(const ChannelRequestPtr &request,
+ const QString &errorName, const QString &errorMessage)
+ {
+ mRemoveRequestRequest = request;
+ mRemoveRequestErrorName = errorName;
+ mRemoveRequestErrorMessage = errorMessage;
+ Q_EMIT requestRemoved(request, errorName, errorMessage);
+ }
+
+ AccountPtr mObserveChannelsAccount;
+ ConnectionPtr mObserveChannelsConnection;
+ QList<ChannelPtr> mObserveChannelsChannels;
+ ChannelDispatchOperationPtr mObserveChannelsDispatchOperation;
+ QList<ChannelRequestPtr> mObserveChannelsRequestsSatisfied;
+ AbstractClientObserver::ObserverInfo mObserveChannelsObserverInfo;
+
+ QList<ChannelPtr> mAddDispatchOperationChannels;
+ ChannelDispatchOperationPtr mAddDispatchOperationDispatchOperation;
+
+ bool mBypassApproval;
+ AccountPtr mHandleChannelsAccount;
+ ConnectionPtr mHandleChannelsConnection;
+ QList<ChannelPtr> mHandleChannelsChannels;
+ QList<ChannelRequestPtr> mHandleChannelsRequestsSatisfied;
+ QDateTime mHandleChannelsUserActionTime;
+ AbstractClientHandler::HandlerInfo mHandleChannelsHandlerInfo;
+ ChannelRequestPtr mAddRequestRequest;
+ ChannelRequestPtr mRemoveRequestRequest;
+ QString mRemoveRequestErrorName;
+ QString mRemoveRequestErrorMessage;
+
+Q_SIGNALS:
+ void observeChannelsFinished();
+ void addDispatchOperationFinished();
+ void handleChannelsFinished();
+ void requestAdded(const Tp::ChannelRequestPtr &request);
+ void requestRemoved(const Tp::ChannelRequestPtr &request,
+ const QString &errorName, const QString &errorMessage);
+ void channelClosed();
+};
+
+class TestClientFactories : public Test
+{
+ Q_OBJECT
+
+public:
+ TestClientFactories(QObject *parent = 0)
+ : Test(parent),
+ mConnService(0), mBaseConnService(0), mContactRepo(0),
+ mText1ChanService(0)
+ { }
+
+ void testObserveChannelsCommon(const AbstractClientPtr &clientObject,
+ const QString &clientBusName, const QString &clientObjectPath);
+
+protected Q_SLOTS:
+ void expectSignalEmission();
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testFactoryAccess();
+ void testRegister();
+ void testCapabilities();
+ void testObserveChannels();
+ void testAddDispatchOperation();
+ void testRequests();
+ void testHandleChannels();
+ void testChannelFactoryAccessors();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TpTestsContactsConnection *mConnService;
+ TpBaseConnection *mBaseConnService;
+ TpHandleRepoIface *mContactRepo;
+ ExampleEchoChannel *mText1ChanService;
+ ExampleEchoChannel *mText2ChanService;
+
+ AccountManagerPtr mAM;
+ AccountPtr mAccount;
+ ConnectionPtr mConn;
+ QString mText1ChanPath;
+ QString mText2ChanPath;
+ QString mConnName;
+ QString mConnPath;
+
+ ClientRegistrarPtr mClientRegistrar;
+ QString mChannelDispatcherBusName;
+ QString mChannelRequestPath;
+ ChannelDispatchOperationAdaptor *mCDO;
+ QString mCDOPath;
+ AbstractClientHandler::Capabilities mClientCapabilities;
+ AbstractClientPtr mClientObject1;
+ QString mClientObject1BusName;
+ QString mClientObject1Path;
+ AbstractClientPtr mClientObject2;
+ QString mClientObject2BusName;
+ QString mClientObject2Path;
+ uint mUserActionTime;
+};
+
+void TestClientFactories::expectSignalEmission()
+{
+ mLoop->exit(0);
+}
+
+void TestClientFactories::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("client-factories");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+
+ ChannelFactoryPtr chanFact = ChannelFactory::create(bus);
+
+ chanFact->addFeaturesForTextChats(TextChannel::FeatureChatState |
+ TextChannel::FeatureMessageQueue);
+ chanFact->addCommonFeatures(Channel::FeatureCore);
+
+ QCOMPARE(chanFact->commonFeatures().size(), 1);
+
+ QCOMPARE(chanFact->featuresForTextChats().size(), 3);
+ QVERIFY(chanFact->featuresForTextChats().contains(TextChannel::FeatureMessageQueue));
+ QVERIFY(chanFact->featuresForTextChats().contains(Channel::FeatureCore));
+ QVERIFY(!chanFact->featuresForTextChats().contains(TextChannel::FeatureMessageSentSignal));
+
+ mAM = AccountManager::create(AccountFactory::create(bus, Account::FeatureCore),
+ ConnectionFactory::create(bus,
+ Connection::FeatureCore | Connection::FeatureSimplePresence),
+ chanFact);
+ PendingReady *amReadyOp = mAM->becomeReady();
+ QVERIFY(amReadyOp != NULL);
+ QVERIFY(connect(amReadyOp,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAM->isReady(), true);
+
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ PendingAccount *pacc = mAM->createAccount(QLatin1String("foo"),
+ QLatin1String("bar"), QLatin1String("foobar"), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pacc->account());
+ mAccount = pacc->account();
+
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ mConnService = TP_TESTS_CONTACTS_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL));
+ QVERIFY(mConnService != 0);
+ mBaseConnService = TP_BASE_CONNECTION(mConnService);
+ QVERIFY(mBaseConnService != 0);
+
+ QVERIFY(tp_base_connection_register(mBaseConnService,
+ "example", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName = QLatin1String(name);
+ mConnPath = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+
+ mConn = ConnectionPtr::qObjectCast(mAccount->connectionFactory()->proxy(mConnName, mConnPath,
+ ChannelFactory::create(bus), ContactFactory::create())->proxy());
+ QCOMPARE(mConn->isReady(), false);
+
+ PendingReady *mConnReady = mConn->lowlevel()->requestConnect();
+ QVERIFY(mConnReady != NULL);
+ QVERIFY(connect(mConnReady,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusConnected));
+
+ // create a Channel by magic, rather than doing D-Bus round-trips for it
+
+ mContactRepo = tp_base_connection_get_handles(mBaseConnService,
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(mContactRepo, "someone@localhost", 0, 0);
+
+ mText1ChanPath = mConnPath + QLatin1String("/TextChannel1");
+ QByteArray chanPath(mText1ChanPath.toAscii());
+ mText1ChanService = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConnService,
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ mText2ChanPath = mConnPath + QLatin1String("/TextChannel2");
+ chanPath = mText2ChanPath.toAscii();
+ mText2ChanService = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConnService,
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ tp_handle_unref(mContactRepo, handle);
+
+ mClientRegistrar = ClientRegistrar::create(mAM);
+
+ // Fake ChannelRequest
+
+ mChannelDispatcherBusName = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER);
+ mChannelRequestPath = QLatin1String("/org/freedesktop/Telepathy/ChannelRequest/Request1");
+
+ QObject *request = new QObject(this);
+
+ mUserActionTime = QDateTime::currentDateTime().toTime_t();
+ new ChannelRequestAdaptor(QDBusObjectPath(mAccount->objectPath()),
+ mUserActionTime,
+ QString(),
+ QualifiedPropertyValueMapList(),
+ QStringList(),
+ request);
+ QVERIFY(bus.registerService(mChannelDispatcherBusName));
+ QVERIFY(bus.registerObject(mChannelRequestPath, request));
+
+ // Fake ChannelDispatchOperation
+
+ mCDOPath = QLatin1String("/org/freedesktop/Telepathy/ChannelDispatchOperation/Operation1");
+
+ QObject *cdo = new QObject(this);
+
+ // Initialize this here so we can actually set it in possibleHandlers
+ mClientObject1Path = QLatin1String("/org/freedesktop/Telepathy/Client/foo");
+
+ ChannelDetailsList channelDetailsList;
+ ChannelDetails channelDetails = { QDBusObjectPath(mText1ChanPath),
+ ChannelClassSpec::textChat().allProperties() };
+ channelDetailsList.append(channelDetails);
+
+ mCDO = new ChannelDispatchOperationAdaptor(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()), channelDetailsList,
+ QStringList() << mClientObject1BusName, cdo);
+ QVERIFY(bus.registerObject(mCDOPath, cdo));
+}
+
+void TestClientFactories::init()
+{
+ initImpl();
+}
+
+void TestClientFactories::testFactoryAccess()
+{
+ AccountFactoryConstPtr accFact = mClientRegistrar->accountFactory();
+ QVERIFY(!accFact.isNull());
+ QCOMPARE(accFact.data(), mAM->accountFactory().data());
+
+ QCOMPARE(accFact->features(), Features(Account::FeatureCore));
+
+ ConnectionFactoryConstPtr connFact = mClientRegistrar->connectionFactory();
+ QVERIFY(!connFact.isNull());
+ QCOMPARE(connFact.data(), mAM->connectionFactory().data());
+
+ QCOMPARE(connFact->features(), Connection::FeatureCore | Connection::FeatureSimplePresence);
+
+ ChannelFactoryConstPtr chanFact = mClientRegistrar->channelFactory();
+ QVERIFY(!chanFact.isNull());
+ QCOMPARE(chanFact.data(), mAM->channelFactory().data());
+
+ ContactFactoryConstPtr contactFact = mClientRegistrar->contactFactory();
+ QVERIFY(!contactFact.isNull());
+ QCOMPARE(contactFact.data(), mAM->contactFactory().data());
+}
+
+void TestClientFactories::testRegister()
+{
+ // invalid client
+ QVERIFY(!mClientRegistrar->registerClient(AbstractClientPtr(), QLatin1String("foo")));
+
+ mClientCapabilities.setICEUDPNATTraversalToken();
+ mClientCapabilities.setAudioCodecToken(QLatin1String("speex"));
+
+ ChannelClassSpecList filters;
+ filters.append(ChannelClassSpec::textChat());
+
+ mClientObject1 = MyClient::create(filters, mClientCapabilities, false, true);
+ QVERIFY(mClientRegistrar->registerClient(mClientObject1, QLatin1String("foo")));
+ QVERIFY(mClientRegistrar->registeredClients().contains(mClientObject1));
+
+ // no op - client already registered
+ QVERIFY(mClientRegistrar->registerClient(mClientObject1, QLatin1String("foo")));
+
+ filters.clear();
+ filters.append(ChannelClassSpec::streamedMediaCall());
+ mClientObject2 = MyClient::create(filters, mClientCapabilities, true, true);
+ QVERIFY(mClientRegistrar->registerClient(mClientObject2, QLatin1String("foo"), true));
+ QVERIFY(mClientRegistrar->registeredClients().contains(mClientObject2));
+
+ // no op - client already registered
+ QVERIFY(mClientRegistrar->registerClient(mClientObject2, QLatin1String("foo"), true));
+
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+ QDBusConnectionInterface *busIface = bus.interface();
+ QStringList registeredServicesNames = busIface->registeredServiceNames();
+ QVERIFY(registeredServicesNames.filter(
+ QRegExp(QLatin1String("^" "org.freedesktop.Telepathy.Client.foo"
+ ".([_A-Za-z][_A-Za-z0-9]*)"))).size() == 1);
+
+ mClientObject1BusName = QLatin1String("org.freedesktop.Telepathy.Client.foo");
+ mClientObject1Path = QLatin1String("/org/freedesktop/Telepathy/Client/foo");
+
+ mClientObject2BusName = registeredServicesNames.filter(
+ QRegExp(QLatin1String("org.freedesktop.Telepathy.Client.foo._*"))).first();
+ mClientObject2Path = QString(QLatin1String("/%1")).arg(mClientObject2BusName);
+ mClientObject2Path.replace(QLatin1String("."), QLatin1String("/"));
+}
+
+void TestClientFactories::testCapabilities()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+ QStringList normalizedClientCaps = mClientCapabilities.allTokens();
+ normalizedClientCaps.sort();
+
+ QStringList normalizedHandlerCaps;
+
+ // object 1
+ ClientHandlerInterface *handler1Iface = new ClientHandlerInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyCapabilities(), &normalizedHandlerCaps));
+ normalizedHandlerCaps.sort();
+ QCOMPARE(normalizedHandlerCaps, normalizedClientCaps);
+
+ // object 2
+ ClientHandlerInterface *handler2Iface = new ClientHandlerInterface(bus,
+ mClientObject2BusName, mClientObject2Path, this);
+
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyCapabilities(), &normalizedHandlerCaps));
+ normalizedHandlerCaps.sort();
+ QCOMPARE(normalizedHandlerCaps, normalizedClientCaps);
+}
+
+void TestClientFactories::testRequests()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+ ClientInterfaceRequestsInterface *handlerRequestsIface = new ClientInterfaceRequestsInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+
+ MyClient *client = dynamic_cast<MyClient*>(mClientObject1.data());
+ connect(client,
+ SIGNAL(requestAdded(const Tp::ChannelRequestPtr &)),
+ SLOT(expectSignalEmission()));
+
+ QVariantMap requestProps;
+ requestProps.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Account"),
+ QVariant::fromValue(QDBusObjectPath(mAccount->objectPath())));
+ requestProps.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST
+ ".Interface.DomainSpecific.IntegerProp"),
+ QVariant::fromValue(3));
+
+ handlerRequestsIface->AddRequest(QDBusObjectPath(mChannelRequestPath), requestProps);
+
+ if (!client->mAddRequestRequest) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(client->mAddRequestRequest->objectPath(),
+ mChannelRequestPath);
+ QCOMPARE(client->mAddRequestRequest->account().data(),
+ mAccount.data());
+
+ QVERIFY(client->mAddRequestRequest->immutableProperties().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST
+ ".Interface.DomainSpecific.IntegerProp")));
+ QCOMPARE(qdbus_cast<int>(client->mAddRequestRequest->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST
+ ".Interface.DomainSpecific.IntegerProp"))),
+ 3);
+
+ QVERIFY(connect(client->mAddRequestRequest->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(client->mAddRequestRequest->immutableProperties().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".UserActionTime")));
+ QCOMPARE(qdbus_cast<uint>(client->mAddRequestRequest->immutableProperties().value(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".UserActionTime"))),
+ mUserActionTime);
+
+ connect(client,
+ SIGNAL(requestRemoved(const Tp::ChannelRequestPtr &,
+ const QString &,
+ const QString &)),
+ SLOT(expectSignalEmission()));
+ handlerRequestsIface->RemoveRequest(QDBusObjectPath(mChannelRequestPath),
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Not available"));
+ if (!client->mRemoveRequestRequest) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(client->mRemoveRequestRequest->objectPath(),
+ mChannelRequestPath);
+// QCOMPARE(client->mRemoveRequestRequest->account().data(),
+// mAccount.data());
+ QCOMPARE(client->mRemoveRequestErrorName,
+ QString(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)));
+ QCOMPARE(client->mRemoveRequestErrorMessage,
+ QString(QLatin1String("Not available")));
+}
+
+void TestClientFactories::testObserveChannelsCommon(const AbstractClientPtr &clientObject,
+ const QString &clientBusName, const QString &clientObjectPath)
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ ClientObserverInterface *observeIface = new ClientObserverInterface(bus,
+ clientBusName, clientObjectPath, this);
+ MyClient *client = dynamic_cast<MyClient*>(clientObject.data());
+ connect(client,
+ SIGNAL(observeChannelsFinished()),
+ SLOT(expectSignalEmission()));
+ ChannelDetailsList channelDetailsList;
+ ChannelDetails channelDetails = { QDBusObjectPath(mText1ChanPath),
+ ChannelClassSpec::textChat().allProperties() };
+ channelDetailsList.append(channelDetails);
+ observeIface->ObserveChannels(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ channelDetailsList,
+ QDBusObjectPath(mCDOPath),
+ ObjectPathList() << QDBusObjectPath(mChannelRequestPath),
+ QVariantMap());
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client->mObserveChannelsAccount->objectPath(), mAccount->objectPath());
+ QCOMPARE(client->mObserveChannelsAccount.data(), mAccount.data());
+ QVERIFY(client->mObserveChannelsAccount->isReady(Account::FeatureCore));
+
+ QCOMPARE(client->mObserveChannelsConnection->objectPath(), mConn->objectPath());
+ QCOMPARE(client->mObserveChannelsConnection.data(), mConn.data());
+ QVERIFY(client->mObserveChannelsConnection->isReady(
+ Connection::FeatureCore | Connection::FeatureSimplePresence));
+
+ QCOMPARE(client->mObserveChannelsChannels.size(), 1);
+ QCOMPARE(client->mObserveChannelsChannels.first()->objectPath(), mText1ChanPath);
+
+ QVERIFY(!client->mObserveChannelsDispatchOperation.isNull());
+ QCOMPARE(client->mObserveChannelsDispatchOperation->account().data(), mAccount.data());
+ QCOMPARE(client->mObserveChannelsDispatchOperation->connection().data(), mConn.data());
+
+ QCOMPARE(client->mObserveChannelsRequestsSatisfied.size(), 1);
+ QCOMPARE(client->mObserveChannelsRequestsSatisfied.first()->objectPath(), mChannelRequestPath);
+ QVERIFY(client->mObserveChannelsRequestsSatisfied.first()->isReady());
+ QCOMPARE(client->mObserveChannelsRequestsSatisfied.first()->account().data(),
+ mAccount.data());
+}
+
+void TestClientFactories::testObserveChannels()
+{
+ testObserveChannelsCommon(mClientObject1,
+ mClientObject1BusName, mClientObject1Path);
+ testObserveChannelsCommon(mClientObject2,
+ mClientObject2BusName, mClientObject2Path);
+}
+
+void TestClientFactories::testAddDispatchOperation()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ ClientApproverInterface *approverIface = new ClientApproverInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+ MyClient *client = dynamic_cast<MyClient*>(mClientObject1.data());
+ connect(client,
+ SIGNAL(addDispatchOperationFinished()),
+ SLOT(expectSignalEmission()));
+
+ QVariantMap dispatchOperationProperties;
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"),
+ QVariant::fromValue(mCDO->Connection()));
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Account"),
+ QVariant::fromValue(mCDO->Account()));
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".PossibleHandlers"),
+ QVariant::fromValue(mCDO->PossibleHandlers()));
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Interfaces"),
+ QVariant::fromValue(mCDO->Interfaces()));
+
+ approverIface->AddDispatchOperation(mCDO->Channels(), QDBusObjectPath(mCDOPath),
+ dispatchOperationProperties);
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client->mAddDispatchOperationChannels.first()->objectPath(), mText1ChanPath);
+
+ QCOMPARE(client->mAddDispatchOperationChannels.first()->connection().data(), mConn.data());
+ QVERIFY(client->mAddDispatchOperationChannels.first()->connection()->isReady(
+ Connection::FeatureCore | Connection::FeatureSimplePresence));
+
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->channels().first().data(),
+ client->mAddDispatchOperationChannels.first().data());
+
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->objectPath(), mCDOPath);
+ QVERIFY(client->mAddDispatchOperationDispatchOperation->isReady());
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->account().data(), mAccount.data());
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->connection().data(), mConn.data());
+
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->possibleHandlers().size(), 1);
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->possibleHandlers(),
+ mCDO->PossibleHandlers());
+}
+
+void TestClientFactories::testHandleChannels()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ // object 1
+ ClientHandlerInterface *handler1Iface = new ClientHandlerInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+ MyClient *client1 = dynamic_cast<MyClient*>(mClientObject1.data());
+ connect(client1,
+ SIGNAL(handleChannelsFinished()),
+ SLOT(expectSignalEmission()));
+ ChannelDetailsList channelDetailsList;
+ ChannelDetails channelDetails = { QDBusObjectPath(mText1ChanPath),
+ ChannelClassSpec::textChat().allProperties() };
+ channelDetailsList.append(channelDetails);
+ handler1Iface->HandleChannels(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ channelDetailsList,
+ ObjectPathList() << QDBusObjectPath(mChannelRequestPath),
+ mUserActionTime,
+ QVariantMap());
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client1->mHandleChannelsAccount->objectPath(), mAccount->objectPath());
+ QCOMPARE(client1->mHandleChannelsAccount.data(), mAccount.data());
+ QVERIFY(client1->mHandleChannelsAccount->isReady());
+
+ QCOMPARE(client1->mHandleChannelsConnection->objectPath(), mConn->objectPath());
+ QCOMPARE(client1->mHandleChannelsConnection.data(), mConn.data());
+ QVERIFY(client1->mHandleChannelsConnection->isReady(
+ Connection::FeatureCore | Connection::FeatureSimplePresence));
+
+ QCOMPARE(client1->mHandleChannelsChannels.first()->objectPath(), mText1ChanPath);
+
+ TextChannelPtr textChan = TextChannelPtr::qObjectCast(client1->mHandleChannelsChannels.first());
+
+ QVERIFY(!textChan.isNull());
+
+ QVERIFY(textChan->isReady());
+ QVERIFY(textChan->isReady(Channel::FeatureCore));
+ QVERIFY(textChan->isReady(TextChannel::FeatureMessageQueue));
+ QVERIFY(textChan->isReady(TextChannel::FeatureChatState));
+
+ QCOMPARE(client1->mHandleChannelsRequestsSatisfied.first()->objectPath(), mChannelRequestPath);
+ QVERIFY(client1->mHandleChannelsRequestsSatisfied.first()->isReady());
+ QCOMPARE(client1->mHandleChannelsRequestsSatisfied.first()->account().data(),
+ mAccount.data());
+
+ QCOMPARE(client1->mHandleChannelsUserActionTime.toTime_t(), mUserActionTime);
+
+ Tp::ObjectPathList handledChannels;
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText1ChanPath)));
+
+ // object 2
+ ClientHandlerInterface *handler2Iface = new ClientHandlerInterface(bus,
+ mClientObject2BusName, mClientObject2Path, this);
+ MyClient *client2 = dynamic_cast<MyClient*>(mClientObject2.data());
+ connect(client2,
+ SIGNAL(handleChannelsFinished()),
+ SLOT(expectSignalEmission()));
+ channelDetailsList.clear();
+ channelDetails.channel = QDBusObjectPath(mText2ChanPath);
+ channelDetailsList.append(channelDetails);
+ handler2Iface->HandleChannels(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ channelDetailsList,
+ ObjectPathList() << QDBusObjectPath(mChannelRequestPath),
+ mUserActionTime,
+ QVariantMap());
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client2->mHandleChannelsAccount->objectPath(), mAccount->objectPath());
+ QCOMPARE(client2->mHandleChannelsAccount.data(), mAccount.data());
+ QVERIFY(client2->mHandleChannelsAccount->isReady());
+
+ QCOMPARE(client2->mHandleChannelsConnection->objectPath(), mConn->objectPath());
+ QCOMPARE(client2->mHandleChannelsConnection.data(), mConn.data());
+ QVERIFY(client2->mHandleChannelsConnection->isReady(
+ Connection::FeatureCore | Connection::FeatureSimplePresence));
+
+ QCOMPARE(client2->mHandleChannelsChannels.first()->objectPath(), mText2ChanPath);
+
+ QCOMPARE(client2->mHandleChannelsRequestsSatisfied.first()->objectPath(), mChannelRequestPath);
+ QVERIFY(client2->mHandleChannelsRequestsSatisfied.first()->isReady());
+ QCOMPARE(client2->mHandleChannelsRequestsSatisfied.first()->account().data(),
+ mAccount.data());
+
+ QCOMPARE(client2->mHandleChannelsUserActionTime.toTime_t(), mUserActionTime);
+
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText1ChanPath)));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText2ChanPath)));
+
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText1ChanPath)));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText2ChanPath)));
+
+ // Handler.HandledChannels will now return all channels that are not invalidated/destroyed
+ // even if the handler for such channels was already unregistered
+ g_object_unref(mText1ChanService);
+ connect(client1,
+ SIGNAL(channelClosed()),
+ SLOT(expectSignalEmission()));
+ QCOMPARE(mLoop->exec(), 0);
+
+ mClientRegistrar->unregisterClient(mClientObject1);
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText2ChanPath)));
+
+ g_object_unref(mText2ChanService);
+ connect(client2,
+ SIGNAL(channelClosed()),
+ SLOT(expectSignalEmission()));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.isEmpty());
+}
+
+void TestClientFactories::testChannelFactoryAccessors()
+{
+ QDBusConnection bus = QDBusConnection::sessionBus();
+
+ ChannelFactoryPtr chanFact = ChannelFactory::create(bus);
+
+ QCOMPARE(chanFact->featuresForTextChats(), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::textChat()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedTextChat()), Features());
+
+ QCOMPARE(chanFact->featuresForTextChatrooms(), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::textChatroom()), Features());
+
+ QCOMPARE(chanFact->featuresForStreamedMediaCalls(), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaCall()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaAudioCall()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCall()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCallWithAudio()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaAudioCall()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCall()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio()), Features());
+
+ QCOMPARE(chanFact->featuresForRoomLists(), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::roomList()), Features());
+
+ QCOMPARE(chanFact->featuresForOutgoingFileTransfers(), Features());
+ QCOMPARE(chanFact->featuresForIncomingFileTransfers(), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::outgoingFileTransfer()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::incomingFileTransfer()), Features());
+
+ QCOMPARE(chanFact->featuresForOutgoingStreamTubes(), Features());
+ QCOMPARE(chanFact->featuresForIncomingStreamTubes(), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::outgoingStreamTube()), Features());
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::incomingStreamTube()), Features());
+
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::contactSearch()), Features());
+ QCOMPARE(chanFact->featuresForContactSearches(), Features());
+
+ Features commonFeatures;
+ commonFeatures.insert(Channel::FeatureCore);
+ chanFact->addCommonFeatures(commonFeatures);
+ QCOMPARE(chanFact->featuresForTextChats(), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::textChat()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedTextChat()), commonFeatures);
+
+ QCOMPARE(chanFact->featuresForTextChatrooms(), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::textChatroom()), commonFeatures);
+
+ QCOMPARE(chanFact->featuresForStreamedMediaCalls(), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaCall()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaAudioCall()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCall()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCallWithAudio()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaAudioCall()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCall()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio()), commonFeatures);
+
+ QCOMPARE(chanFact->featuresForRoomLists(), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::roomList()), commonFeatures);
+
+ QCOMPARE(chanFact->featuresForOutgoingFileTransfers(), commonFeatures);
+ QCOMPARE(chanFact->featuresForIncomingFileTransfers(), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::outgoingFileTransfer()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::incomingFileTransfer()), commonFeatures);
+
+ QCOMPARE(chanFact->featuresForOutgoingStreamTubes(), commonFeatures);
+ QCOMPARE(chanFact->featuresForIncomingStreamTubes(), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::outgoingStreamTube()), commonFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::incomingStreamTube()), commonFeatures);
+
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::contactSearch()), commonFeatures);
+ QCOMPARE(chanFact->featuresForContactSearches(), commonFeatures);
+
+ Features textChatFeatures;
+ textChatFeatures.insert(TextChannel::FeatureCore);
+ textChatFeatures.insert(TextChannel::FeatureMessageQueue);
+ chanFact->addFeaturesForTextChats(textChatFeatures);
+ textChatFeatures |= commonFeatures;
+
+ Features textChatroomFeatures;
+ textChatroomFeatures.insert(TextChannel::FeatureCore);
+ textChatroomFeatures.insert(TextChannel::FeatureMessageCapabilities);
+ chanFact->addFeaturesForTextChatrooms(textChatroomFeatures);
+ textChatroomFeatures |= commonFeatures;
+
+ Features streamedMediaFeatures;
+ streamedMediaFeatures.insert(StreamedMediaChannel::FeatureStreams);
+ chanFact->addFeaturesForStreamedMediaCalls(streamedMediaFeatures);
+ streamedMediaFeatures |= commonFeatures;
+
+ // RoomListChannel has no feature, let's use FeatureConferenceInitialInviteeContacts just for
+ // testing purposes
+ Features roomListFeatures;
+ roomListFeatures.insert(Channel::FeatureConferenceInitialInviteeContacts);
+ chanFact->addFeaturesForRoomLists(roomListFeatures);
+ roomListFeatures |= commonFeatures;
+
+ Features outFtFeatures;
+ outFtFeatures.insert(FileTransferChannel::FeatureCore);
+ outFtFeatures.insert(OutgoingFileTransferChannel::FeatureCore);
+ chanFact->addFeaturesForOutgoingFileTransfers(outFtFeatures);
+ outFtFeatures |= commonFeatures;
+ Features inFtFeatures;
+ inFtFeatures.insert(FileTransferChannel::FeatureCore);
+ inFtFeatures.insert(IncomingFileTransferChannel::FeatureCore);
+ chanFact->addFeaturesForIncomingFileTransfers(inFtFeatures);
+ inFtFeatures |= commonFeatures;
+
+ Features outStubeFeatures;
+ outStubeFeatures.insert(StreamTubeChannel::FeatureCore);
+ outStubeFeatures.insert(OutgoingStreamTubeChannel::FeatureCore);
+ chanFact->addFeaturesForOutgoingStreamTubes(outStubeFeatures);
+ outStubeFeatures |= commonFeatures;
+ Features inStubeFeatures = commonFeatures;
+ outStubeFeatures.insert(StreamTubeChannel::FeatureCore);
+ outStubeFeatures.insert(IncomingStreamTubeChannel::FeatureCore);
+ chanFact->addFeaturesForIncomingStreamTubes(inStubeFeatures);
+ inStubeFeatures |= commonFeatures;
+
+ Features contactSearchFeatures;
+ contactSearchFeatures.insert(ContactSearchChannel::FeatureCore);
+ chanFact->addFeaturesForContactSearches(contactSearchFeatures);
+ contactSearchFeatures |= commonFeatures;
+
+ QCOMPARE(chanFact->featuresForTextChats(), textChatFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::textChat()), textChatFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedTextChat()), textChatFeatures);
+
+ QCOMPARE(chanFact->featuresForTextChatrooms(), textChatroomFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::textChatroom()), textChatroomFeatures);
+
+ QCOMPARE(chanFact->featuresForStreamedMediaCalls(), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaAudioCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCallWithAudio()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaAudioCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio()), streamedMediaFeatures);
+
+ QCOMPARE(chanFact->featuresForRoomLists(), roomListFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::roomList()), roomListFeatures);
+
+ QCOMPARE(chanFact->featuresForOutgoingFileTransfers(), outFtFeatures);
+ QCOMPARE(chanFact->featuresForIncomingFileTransfers(), inFtFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::outgoingFileTransfer()), outFtFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::incomingFileTransfer()), inFtFeatures);
+
+ QCOMPARE(chanFact->featuresForOutgoingStreamTubes(), outStubeFeatures);
+ QCOMPARE(chanFact->featuresForIncomingStreamTubes(), inStubeFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::outgoingStreamTube()), outStubeFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::incomingStreamTube()), inStubeFeatures);
+
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::contactSearch()), contactSearchFeatures);
+ QCOMPARE(chanFact->featuresForContactSearches(), contactSearchFeatures);
+
+ Features streamedMediaAudioFeatures;
+ streamedMediaAudioFeatures.insert(StreamedMediaChannel::FeatureStreams);
+ chanFact->addFeaturesFor(ChannelClassSpec::streamedMediaAudioCall(),
+ streamedMediaAudioFeatures);
+ streamedMediaAudioFeatures |= streamedMediaFeatures;
+
+ QCOMPARE(chanFact->featuresForStreamedMediaCalls(), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaAudioCall()), streamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCallWithAudio()), streamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaAudioCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio()), streamedMediaFeatures);
+
+ Features unnamedStreamedMediaAudioFeatures;
+ unnamedStreamedMediaAudioFeatures.insert(StreamedMediaChannel::FeatureLocalHoldState);
+ chanFact->addFeaturesFor(ChannelClassSpec::unnamedStreamedMediaAudioCall(),
+ unnamedStreamedMediaAudioFeatures);
+ unnamedStreamedMediaAudioFeatures |= streamedMediaFeatures;
+
+ QCOMPARE(chanFact->featuresForStreamedMediaCalls(), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaAudioCall()), streamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCallWithAudio()), streamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaAudioCall()), unnamedStreamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio()), unnamedStreamedMediaAudioFeatures);
+
+ QVariantMap otherProps;
+ otherProps.insert(QLatin1String("ping"), QLatin1String("pong"));
+ Features specificUnnamedStreamedMediaFeatures;
+ specificUnnamedStreamedMediaFeatures.insert(Feature(QLatin1String("TestClass"), 1234));
+ chanFact->addFeaturesFor(ChannelClassSpec::unnamedStreamedMediaCall(otherProps),
+ specificUnnamedStreamedMediaFeatures);
+ specificUnnamedStreamedMediaFeatures |= streamedMediaFeatures;
+
+ QCOMPARE(chanFact->featuresForStreamedMediaCalls(), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaAudioCall()), streamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::streamedMediaVideoCallWithAudio()), streamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaCall(otherProps)), specificUnnamedStreamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaAudioCall()), unnamedStreamedMediaAudioFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCall()), streamedMediaFeatures);
+ QCOMPARE(chanFact->featuresFor(ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio()), unnamedStreamedMediaAudioFeatures);
+}
+
+void TestClientFactories::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestClientFactories::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestClientFactories)
+#include "_gen/client-factories.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/client.cpp b/qt4/tests/dbus/client.cpp
new file mode 100644
index 000000000..91aea854f
--- /dev/null
+++ b/qt4/tests/dbus/client.cpp
@@ -0,0 +1,858 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/glib/echo/chan.h>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/AbstractClientHandler>
+#include <TelepathyQt4/AbstractClientObserver>
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ChannelDispatchOperation>
+#include <TelepathyQt4/ChannelRequest>
+#include <TelepathyQt4/ClientHandlerInterface>
+#include <TelepathyQt4/ClientInterfaceRequestsInterface>
+#include <TelepathyQt4/ClientObserverInterface>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/MethodInvocationContext>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingReady>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+class ChannelRequestAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelRequest")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelRequest\" >\n"
+" <property name=\"Account\" type=\"o\" access=\"read\" />\n"
+" <property name=\"UserActionTime\" type=\"x\" access=\"read\" />\n"
+" <property name=\"PreferredHandler\" type=\"s\" access=\"read\" />\n"
+" <property name=\"Requests\" type=\"aa{sv}\" access=\"read\" />\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <method name=\"Proceed\" />\n"
+" <method name=\"Cancel\" />\n"
+" <signal name=\"Failed\" >\n"
+" <arg name=\"Error\" type=\"s\" />\n"
+" <arg name=\"Message\" type=\"s\" />\n"
+" </signal>\n"
+" <signal name=\"Succeeded\" />"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Account READ Account)
+ Q_PROPERTY(qulonglong UserActionTime READ UserActionTime)
+ Q_PROPERTY(QString PreferredHandler READ PreferredHandler)
+ Q_PROPERTY(Tp::QualifiedPropertyValueMapList Requests READ Requests)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+
+public:
+ ChannelRequestAdaptor(QDBusObjectPath account,
+ qulonglong userActionTime,
+ QString preferredHandler,
+ QualifiedPropertyValueMapList requests,
+ QStringList interfaces,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mAccount(account), mUserActionTime(userActionTime),
+ mPreferredHandler(preferredHandler), mRequests(requests),
+ mInterfaces(interfaces)
+ {
+ }
+
+ virtual ~ChannelRequestAdaptor()
+ {
+ }
+
+public: // Properties
+ inline QDBusObjectPath Account() const
+ {
+ return mAccount;
+ }
+
+ inline qulonglong UserActionTime() const
+ {
+ return mUserActionTime;
+ }
+
+ inline QString PreferredHandler() const
+ {
+ return mPreferredHandler;
+ }
+
+ inline QualifiedPropertyValueMapList Requests() const
+ {
+ return mRequests;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+public Q_SLOTS: // Methods
+ void Proceed()
+ {
+ }
+
+ void Cancel()
+ {
+ }
+
+Q_SIGNALS: // Signals
+ void Failed(const QString &error, const QString &message);
+ void Succeeded();
+
+private:
+ QDBusObjectPath mAccount;
+ qulonglong mUserActionTime;
+ QString mPreferredHandler;
+ QualifiedPropertyValueMapList mRequests;
+ QStringList mInterfaces;
+};
+
+// Totally incomplete mini version of ChannelDispatchOperation
+class ChannelDispatchOperationAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelDispatchOperation")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelDispatchOperation\" >\n"
+" <property name=\"Account\" type=\"o\" access=\"read\" />\n"
+" <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
+" <property name=\"Channels\" type=\"a(oa{sv})\" access=\"read\" />\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"PossibleHandlers\" type=\"as\" access=\"read\" />\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Account READ Account)
+ Q_PROPERTY(QDBusObjectPath Connection READ Connection)
+ Q_PROPERTY(Tp::ChannelDetailsList Channels READ Channels)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+ Q_PROPERTY(QStringList PossibleHandlers READ PossibleHandlers)
+
+public:
+ ChannelDispatchOperationAdaptor(const QDBusObjectPath &acc, const QDBusObjectPath &conn,
+ const ChannelDetailsList &channels, const QStringList &possibleHandlers,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent), mAccount(acc), mConn(conn), mChannels(channels),
+ mPossibleHandlers(possibleHandlers)
+ {
+ }
+
+ virtual ~ChannelDispatchOperationAdaptor()
+ {
+ }
+
+public: // Properties
+ inline QDBusObjectPath Account() const
+ {
+ return mAccount;
+ }
+
+ inline QDBusObjectPath Connection() const
+ {
+ return mConn;
+ }
+
+ inline ChannelDetailsList Channels() const
+ {
+ return mChannels;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+ inline QStringList PossibleHandlers() const
+ {
+ return mPossibleHandlers;
+ }
+
+public Q_SLOTS:
+ inline void Claim()
+ {
+ // do nothing = no fail
+ }
+
+private:
+ QDBusObjectPath mAccount, mConn;
+ ChannelDetailsList mChannels;
+ QStringList mInterfaces;
+ QStringList mPossibleHandlers;
+};
+
+class MyClient : public QObject,
+ public AbstractClientObserver,
+ public AbstractClientApprover,
+ public AbstractClientHandler
+{
+ Q_OBJECT
+
+public:
+ static AbstractClientPtr create(const ChannelClassSpecList &channelFilter,
+ const AbstractClientHandler::Capabilities &capabilities,
+ bool bypassApproval = false,
+ bool wantsRequestNotification = false)
+ {
+ return AbstractClientPtr::dynamicCast(SharedPtr<MyClient>(
+ new MyClient(channelFilter, capabilities,
+ bypassApproval, wantsRequestNotification)));
+ }
+
+ MyClient(const ChannelClassSpecList &channelFilter,
+ const AbstractClientHandler::Capabilities &capabilities,
+ bool bypassApproval = false,
+ bool wantsRequestNotification = false)
+ : AbstractClientObserver(channelFilter),
+ AbstractClientApprover(channelFilter),
+ AbstractClientHandler(channelFilter, capabilities, wantsRequestNotification),
+ mBypassApproval(bypassApproval)
+ {
+ }
+
+ ~MyClient()
+ {
+ }
+
+ void observeChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const ChannelDispatchOperationPtr &dispatchOperation,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const AbstractClientObserver::ObserverInfo &observerInfo)
+ {
+ mObserveChannelsAccount = account;
+ mObserveChannelsConnection = connection;
+ mObserveChannelsChannels = channels;
+ mObserveChannelsDispatchOperation = dispatchOperation;
+ mObserveChannelsRequestsSatisfied = requestsSatisfied;
+ mObserveChannelsObserverInfo = observerInfo;
+
+ context->setFinished();
+ QTimer::singleShot(0, this, SIGNAL(observeChannelsFinished()));
+ }
+
+ void addDispatchOperation(const MethodInvocationContextPtr<> &context,
+ const ChannelDispatchOperationPtr &dispatchOperation)
+ {
+ mAddDispatchOperationChannels = dispatchOperation->channels();
+ mAddDispatchOperationDispatchOperation = dispatchOperation;
+
+ QVERIFY(connect(dispatchOperation->claim(AbstractClientHandlerPtr(this)),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SIGNAL(claimFinished())));
+
+ context->setFinished();
+ QTimer::singleShot(0, this, SIGNAL(addDispatchOperationFinished()));
+ }
+
+ bool bypassApproval() const
+ {
+ return mBypassApproval;
+ }
+
+ void handleChannels(const MethodInvocationContextPtr<> &context,
+ const AccountPtr &account,
+ const ConnectionPtr &connection,
+ const QList<ChannelPtr> &channels,
+ const QList<ChannelRequestPtr> &requestsSatisfied,
+ const QDateTime &userActionTime,
+ const AbstractClientHandler::HandlerInfo &handlerInfo)
+ {
+ mHandleChannelsAccount = account;
+ mHandleChannelsConnection = connection;
+ mHandleChannelsChannels = channels;
+ mHandleChannelsRequestsSatisfied = requestsSatisfied;
+ mHandleChannelsUserActionTime = userActionTime;
+ mHandleChannelsHandlerInfo = handlerInfo;
+
+ Q_FOREACH (const ChannelPtr &channel, channels) {
+ connect(channel.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SIGNAL(channelClosed()));
+ }
+
+ context->setFinished();
+ QTimer::singleShot(0, this, SIGNAL(handleChannelsFinished()));
+ }
+
+ void addRequest(const ChannelRequestPtr &request)
+ {
+ mAddRequestRequest = request;
+ Q_EMIT requestAdded(request);
+ }
+
+ void removeRequest(const ChannelRequestPtr &request,
+ const QString &errorName, const QString &errorMessage)
+ {
+ mRemoveRequestRequest = request;
+ mRemoveRequestErrorName = errorName;
+ mRemoveRequestErrorMessage = errorMessage;
+ Q_EMIT requestRemoved(request, errorName, errorMessage);
+ }
+
+ AccountPtr mObserveChannelsAccount;
+ ConnectionPtr mObserveChannelsConnection;
+ QList<ChannelPtr> mObserveChannelsChannels;
+ ChannelDispatchOperationPtr mObserveChannelsDispatchOperation;
+ QList<ChannelRequestPtr> mObserveChannelsRequestsSatisfied;
+ AbstractClientObserver::ObserverInfo mObserveChannelsObserverInfo;
+
+ QList<ChannelPtr> mAddDispatchOperationChannels;
+ ChannelDispatchOperationPtr mAddDispatchOperationDispatchOperation;
+
+ bool mBypassApproval;
+ AccountPtr mHandleChannelsAccount;
+ ConnectionPtr mHandleChannelsConnection;
+ QList<ChannelPtr> mHandleChannelsChannels;
+ QList<ChannelRequestPtr> mHandleChannelsRequestsSatisfied;
+ QDateTime mHandleChannelsUserActionTime;
+ AbstractClientHandler::HandlerInfo mHandleChannelsHandlerInfo;
+ ChannelRequestPtr mAddRequestRequest;
+ ChannelRequestPtr mRemoveRequestRequest;
+ QString mRemoveRequestErrorName;
+ QString mRemoveRequestErrorMessage;
+
+Q_SIGNALS:
+ void observeChannelsFinished();
+ void addDispatchOperationFinished();
+ void handleChannelsFinished();
+ void claimFinished();
+ void requestAdded(const Tp::ChannelRequestPtr &request);
+ void requestRemoved(const Tp::ChannelRequestPtr &request,
+ const QString &errorName, const QString &errorMessage);
+ void channelClosed();
+};
+
+class TestClient : public Test
+{
+ Q_OBJECT
+
+public:
+ TestClient(QObject *parent = 0)
+ : Test(parent),
+ mConn(0), mContactRepo(0),
+ mText1ChanService(0), mText2ChanService(0), mCDO(0),
+ mClaimFinished(false)
+ { }
+
+ void testObserveChannelsCommon(const AbstractClientPtr &clientObject,
+ const QString &clientBusName, const QString &clientObjectPath);
+
+protected Q_SLOTS:
+ void expectSignalEmission();
+ void onClaimFinished();
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRegister();
+ void testCapabilities();
+ void testObserveChannels();
+ void testAddDispatchOperation();
+ void testRequests();
+ void testHandleChannels();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ AccountManagerPtr mAM;
+ AccountPtr mAccount;
+ TestConnHelper *mConn;
+ TpHandleRepoIface *mContactRepo;
+
+ ExampleEchoChannel *mText1ChanService;
+ ExampleEchoChannel *mText2ChanService;
+ QString mText1ChanPath;
+ QString mText2ChanPath;
+
+ ClientRegistrarPtr mClientRegistrar;
+ QString mChannelDispatcherBusName;
+ QString mChannelRequestPath;
+ ChannelDispatchOperationAdaptor *mCDO;
+ QString mCDOPath;
+ AbstractClientHandler::Capabilities mClientCapabilities;
+ AbstractClientPtr mClientObject1;
+ QString mClientObject1BusName;
+ QString mClientObject1Path;
+ AbstractClientPtr mClientObject2;
+ QString mClientObject2BusName;
+ QString mClientObject2Path;
+ uint mUserActionTime;
+
+ bool mClaimFinished;
+};
+
+void TestClient::expectSignalEmission()
+{
+ mLoop->exit(0);
+}
+
+void TestClient::onClaimFinished()
+{
+ mClaimFinished = true;
+}
+
+void TestClient::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("client");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mAM = AccountManager::create();
+ QVERIFY(connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAM->isReady(), true);
+
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ PendingAccount *pacc = mAM->createAccount(QLatin1String("foo"),
+ QLatin1String("bar"), QLatin1String("foobar"), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pacc->account());
+ mAccount = pacc->account();
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+
+ mContactRepo = tp_base_connection_get_handles(TP_BASE_CONNECTION(mConn->service()),
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(mContactRepo, "someone@localhost", 0, 0);
+
+ // create a Channel by magic, rather than doing D-Bus round-trips for it
+ mText1ChanPath = mConn->objectPath() + QLatin1String("/TextChannel1");
+ QByteArray chanPath(mText1ChanPath.toAscii());
+ mText1ChanService = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ mText2ChanPath = mConn->objectPath() + QLatin1String("/TextChannel2");
+ chanPath = mText2ChanPath.toAscii();
+ mText2ChanService = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ tp_handle_unref(mContactRepo, handle);
+
+ mClientRegistrar = ClientRegistrar::create();
+
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ // Fake ChannelRequest
+
+ mChannelDispatcherBusName = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER);
+ mChannelRequestPath = QLatin1String("/org/freedesktop/Telepathy/ChannelRequest/Request1");
+
+ QObject *request = new QObject(this);
+
+ mUserActionTime = QDateTime::currentDateTime().toTime_t();
+ new ChannelRequestAdaptor(QDBusObjectPath(mAccount->objectPath()),
+ mUserActionTime,
+ QString(),
+ QualifiedPropertyValueMapList(),
+ QStringList(),
+ request);
+ QVERIFY(bus.registerService(mChannelDispatcherBusName));
+ QVERIFY(bus.registerObject(mChannelRequestPath, request));
+
+ // Fake ChannelDispatchOperation
+
+ mCDOPath = QLatin1String("/org/freedesktop/Telepathy/ChannelDispatchOperation/Operation1");
+
+ QObject *cdo = new QObject(this);
+
+ // Initialize this here so we can actually set it in possibleHandlers
+ mClientObject1BusName = QLatin1String("org.freedesktop.Telepathy.Client.foo");
+
+ ChannelDetailsList channelDetailsList;
+ ChannelDetails channelDetails = { QDBusObjectPath(mText1ChanPath), QVariantMap() };
+ channelDetailsList.append(channelDetails);
+
+ mCDO = new ChannelDispatchOperationAdaptor(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()), channelDetailsList,
+ QStringList() << mClientObject1BusName, cdo);
+ QVERIFY(bus.registerObject(mCDOPath, cdo));
+}
+
+void TestClient::init()
+{
+ initImpl();
+ mClaimFinished = false;
+}
+
+void TestClient::testRegister()
+{
+ // invalid client
+ QVERIFY(!mClientRegistrar->registerClient(AbstractClientPtr(), QLatin1String("foo")));
+
+ mClientCapabilities.setICEUDPNATTraversalToken();
+ mClientCapabilities.setToken(TP_QT4_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING +
+ QLatin1String("/audio/speex=true"));
+
+ ChannelClassSpecList filters;
+ filters.append(ChannelClassSpec::textChat());
+ mClientObject1 = MyClient::create(filters, mClientCapabilities, false, true);
+ MyClient *client = dynamic_cast<MyClient*>(mClientObject1.data());
+ QVERIFY(!client->isRegistered());
+ QVERIFY(mClientRegistrar->registerClient(mClientObject1, QLatin1String("foo")));
+ QVERIFY(client->isRegistered());
+ QVERIFY(mClientRegistrar->registeredClients().contains(mClientObject1));
+
+ AbstractClientPtr clientObjectRedundant = MyClient::create(
+ filters, mClientCapabilities, false, true);
+ client = dynamic_cast<MyClient*>(clientObjectRedundant.data());
+ QVERIFY(!client->isRegistered());
+ // try to register using a name already registered and a different object, it should fail
+ // and not report isRegistered
+ QVERIFY(!mClientRegistrar->registerClient(clientObjectRedundant, QLatin1String("foo")));
+ QVERIFY(!client->isRegistered());
+ QVERIFY(!mClientRegistrar->registeredClients().contains(clientObjectRedundant));
+
+ client = dynamic_cast<MyClient*>(mClientObject1.data());
+
+ // no op - client already registered with same object and name
+ QVERIFY(mClientRegistrar->registerClient(mClientObject1, QLatin1String("foo")));
+
+ // unregister client
+ QVERIFY(mClientRegistrar->unregisterClient(mClientObject1));
+ QVERIFY(!client->isRegistered());
+
+ // register again
+ QVERIFY(mClientRegistrar->registerClient(mClientObject1, QLatin1String("foo")));
+ QVERIFY(client->isRegistered());
+
+ filters.clear();
+ filters.append(ChannelClassSpec::streamedMediaCall());
+ mClientObject2 = MyClient::create(filters, mClientCapabilities, true, true);
+ QVERIFY(mClientRegistrar->registerClient(mClientObject2, QLatin1String("foo"), true));
+ QVERIFY(mClientRegistrar->registeredClients().contains(mClientObject2));
+
+ // no op - client already registered
+ QVERIFY(mClientRegistrar->registerClient(mClientObject2, QLatin1String("foo"), true));
+
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+ QDBusConnectionInterface *busIface = bus.interface();
+ QStringList registeredServicesNames = busIface->registeredServiceNames();
+ QVERIFY(registeredServicesNames.filter(
+ QRegExp(QLatin1String("^" "org.freedesktop.Telepathy.Client.foo"
+ ".([_A-Za-z][_A-Za-z0-9]*)"))).size() == 1);
+
+ mClientObject1BusName = QLatin1String("org.freedesktop.Telepathy.Client.foo");
+ mClientObject1Path = QLatin1String("/org/freedesktop/Telepathy/Client/foo");
+
+ mClientObject2BusName = registeredServicesNames.filter(
+ QRegExp(QLatin1String("org.freedesktop.Telepathy.Client.foo._*"))).first();
+ mClientObject2Path = QString(QLatin1String("/%1")).arg(mClientObject2BusName);
+ mClientObject2Path.replace(QLatin1String("."), QLatin1String("/"));
+}
+
+void TestClient::testCapabilities()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+ QStringList normalizedClientCaps = mClientCapabilities.allTokens();
+ normalizedClientCaps.sort();
+
+ QStringList normalizedHandlerCaps;
+
+ // object 1
+ ClientHandlerInterface *handler1Iface = new ClientHandlerInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyCapabilities(), &normalizedHandlerCaps));
+ normalizedHandlerCaps.sort();
+ QCOMPARE(normalizedHandlerCaps, normalizedClientCaps);
+
+ // object 2
+ ClientHandlerInterface *handler2Iface = new ClientHandlerInterface(bus,
+ mClientObject2BusName, mClientObject2Path, this);
+
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyCapabilities(), &normalizedHandlerCaps));
+ normalizedHandlerCaps.sort();
+ QCOMPARE(normalizedHandlerCaps, normalizedClientCaps);
+}
+
+void TestClient::testRequests()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+ ClientInterfaceRequestsInterface *handlerRequestsIface = new ClientInterfaceRequestsInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+
+ MyClient *client = dynamic_cast<MyClient*>(mClientObject1.data());
+ connect(client,
+ SIGNAL(requestAdded(const Tp::ChannelRequestPtr &)),
+ SLOT(expectSignalEmission()));
+ handlerRequestsIface->AddRequest(QDBusObjectPath(mChannelRequestPath), QVariantMap());
+ if (!client->mAddRequestRequest) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(client->mAddRequestRequest->objectPath(),
+ mChannelRequestPath);
+
+ connect(client,
+ SIGNAL(requestRemoved(const Tp::ChannelRequestPtr &,
+ const QString &,
+ const QString &)),
+ SLOT(expectSignalEmission()));
+ handlerRequestsIface->RemoveRequest(QDBusObjectPath(mChannelRequestPath),
+ QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE),
+ QLatin1String("Not available"));
+ if (!client->mRemoveRequestRequest) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(client->mRemoveRequestRequest->objectPath(),
+ mChannelRequestPath);
+ QCOMPARE(client->mRemoveRequestErrorName,
+ QString(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)));
+ QCOMPARE(client->mRemoveRequestErrorMessage,
+ QString(QLatin1String("Not available")));
+}
+
+void TestClient::testObserveChannelsCommon(const AbstractClientPtr &clientObject,
+ const QString &clientBusName, const QString &clientObjectPath)
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ ClientObserverInterface *observeIface = new ClientObserverInterface(bus,
+ clientBusName, clientObjectPath, this);
+ MyClient *client = dynamic_cast<MyClient*>(clientObject.data());
+ connect(client,
+ SIGNAL(observeChannelsFinished()),
+ SLOT(expectSignalEmission()));
+ ChannelDetailsList channelDetailsList;
+ ChannelDetails channelDetails = { QDBusObjectPath(mText1ChanPath), QVariantMap() };
+ channelDetailsList.append(channelDetails);
+
+ QVariantMap observerInfo;
+ ObjectImmutablePropertiesMap reqPropsMap;
+ QVariantMap channelReqImmutableProps;
+ channelReqImmutableProps.insert(
+ QLatin1String(
+ TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Interface.DomainSpecific.IntegerProp"), 3);
+ channelReqImmutableProps.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Account"),
+ qVariantFromValue(QDBusObjectPath(mAccount->objectPath())));
+ reqPropsMap.insert(QDBusObjectPath(mChannelRequestPath), channelReqImmutableProps);
+ observerInfo.insert(QLatin1String("request-properties"), qVariantFromValue(reqPropsMap));
+ observeIface->ObserveChannels(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ channelDetailsList,
+ QDBusObjectPath("/"),
+ ObjectPathList() << QDBusObjectPath(mChannelRequestPath),
+ observerInfo);
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client->mObserveChannelsAccount->objectPath(), mAccount->objectPath());
+ QCOMPARE(client->mObserveChannelsConnection->objectPath(), mConn->objectPath());
+ QCOMPARE(client->mObserveChannelsChannels.first()->objectPath(), mText1ChanPath);
+ QVERIFY(client->mObserveChannelsDispatchOperation.isNull());
+ QCOMPARE(client->mObserveChannelsRequestsSatisfied.first()->objectPath(), mChannelRequestPath);
+ QCOMPARE(client->mObserveChannelsRequestsSatisfied.first()->immutableProperties().contains(
+ QLatin1String(
+ TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Interface.DomainSpecific.IntegerProp")), true);
+ QCOMPARE(qdbus_cast<int>(client->mObserveChannelsRequestsSatisfied.first()->immutableProperties().value(
+ QLatin1String(
+ TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Interface.DomainSpecific.IntegerProp"))), 3);
+}
+
+void TestClient::testObserveChannels()
+{
+ testObserveChannelsCommon(mClientObject1,
+ mClientObject1BusName, mClientObject1Path);
+ testObserveChannelsCommon(mClientObject2,
+ mClientObject2BusName, mClientObject2Path);
+}
+
+void TestClient::testAddDispatchOperation()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ ClientApproverInterface *approverIface = new ClientApproverInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+ ClientHandlerInterface *handler1Iface = new ClientHandlerInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+ MyClient *client = dynamic_cast<MyClient*>(mClientObject1.data());
+ connect(client,
+ SIGNAL(addDispatchOperationFinished()),
+ SLOT(expectSignalEmission()));
+ connect(client,
+ SIGNAL(claimFinished()),
+ SLOT(onClaimFinished()));
+
+ QVariantMap dispatchOperationProperties;
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"),
+ QVariant::fromValue(QDBusObjectPath(mConn->objectPath())));
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Account"),
+ QVariant::fromValue(QDBusObjectPath(mAccount->objectPath())));
+ dispatchOperationProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".PossibleHandlers"),
+ QVariant::fromValue(ObjectPathList() << QDBusObjectPath(mClientObject1Path)
+ << QDBusObjectPath(mClientObject2Path)));
+
+ // Handler.HandledChannels should be empty here, CDO::claim(handler) will populate it on
+ // success
+ Tp::ObjectPathList handledChannels;
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.isEmpty());
+
+ approverIface->AddDispatchOperation(mCDO->Channels(), QDBusObjectPath(mCDOPath),
+ dispatchOperationProperties);
+ QCOMPARE(mLoop->exec(), 0);
+ while (!mClaimFinished) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(client->mAddDispatchOperationChannels.first()->objectPath(), mText1ChanPath);
+ QCOMPARE(client->mAddDispatchOperationDispatchOperation->objectPath(), mCDOPath);
+
+ // Claim finished, Handler.HandledChannels should be populated now
+ handledChannels.clear();
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(!handledChannels.isEmpty());
+ qSort(handledChannels);
+ Tp::ObjectPathList expectedHandledChannels;
+ Q_FOREACH (const ChannelDetails &details, mCDO->Channels()) {
+ expectedHandledChannels << details.channel;
+ }
+ qSort(expectedHandledChannels);
+ QCOMPARE(handledChannels, expectedHandledChannels);
+}
+
+void TestClient::testHandleChannels()
+{
+ QDBusConnection bus = mClientRegistrar->dbusConnection();
+
+ // object 1
+ ClientHandlerInterface *handler1Iface = new ClientHandlerInterface(bus,
+ mClientObject1BusName, mClientObject1Path, this);
+ MyClient *client1 = dynamic_cast<MyClient*>(mClientObject1.data());
+ connect(client1,
+ SIGNAL(handleChannelsFinished()),
+ SLOT(expectSignalEmission()));
+ ChannelDetailsList channelDetailsList;
+ ChannelDetails channelDetails = { QDBusObjectPath(mText1ChanPath), QVariantMap() };
+ channelDetailsList.append(channelDetails);
+ handler1Iface->HandleChannels(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ channelDetailsList,
+ ObjectPathList() << QDBusObjectPath(mChannelRequestPath),
+ mUserActionTime,
+ QVariantMap());
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client1->mHandleChannelsAccount->objectPath(), mAccount->objectPath());
+ QCOMPARE(client1->mHandleChannelsConnection->objectPath(), mConn->objectPath());
+ QCOMPARE(client1->mHandleChannelsChannels.first()->objectPath(), mText1ChanPath);
+ QCOMPARE(client1->mHandleChannelsRequestsSatisfied.first()->objectPath(), mChannelRequestPath);
+ QCOMPARE(client1->mHandleChannelsUserActionTime.toTime_t(), mUserActionTime);
+
+ Tp::ObjectPathList handledChannels;
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText1ChanPath)));
+
+ // object 2
+ ClientHandlerInterface *handler2Iface = new ClientHandlerInterface(bus,
+ mClientObject2BusName, mClientObject2Path, this);
+ MyClient *client2 = dynamic_cast<MyClient*>(mClientObject2.data());
+ connect(client2,
+ SIGNAL(handleChannelsFinished()),
+ SLOT(expectSignalEmission()));
+ channelDetailsList.clear();
+ channelDetails.channel = QDBusObjectPath(mText2ChanPath);
+ channelDetailsList.append(channelDetails);
+ handler2Iface->HandleChannels(QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ channelDetailsList,
+ ObjectPathList() << QDBusObjectPath(mChannelRequestPath),
+ mUserActionTime,
+ QVariantMap());
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(client2->mHandleChannelsAccount->objectPath(), mAccount->objectPath());
+ QCOMPARE(client2->mHandleChannelsConnection->objectPath(), mConn->objectPath());
+ QCOMPARE(client2->mHandleChannelsChannels.first()->objectPath(), mText2ChanPath);
+ QCOMPARE(client2->mHandleChannelsRequestsSatisfied.first()->objectPath(), mChannelRequestPath);
+ QCOMPARE(client2->mHandleChannelsUserActionTime.toTime_t(), mUserActionTime);
+
+ QVERIFY(waitForProperty(handler1Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText1ChanPath)));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText2ChanPath)));
+
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText1ChanPath)));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText2ChanPath)));
+
+ // Handler.HandledChannels will now return all channels that are not invalidated/destroyed
+ // even if the handler for such channels was already unregistered
+ g_object_unref(mText1ChanService);
+ connect(client1,
+ SIGNAL(channelClosed()),
+ SLOT(expectSignalEmission()));
+ QCOMPARE(mLoop->exec(), 0);
+
+ mClientRegistrar->unregisterClient(mClientObject1);
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.contains(QDBusObjectPath(mText2ChanPath)));
+
+ g_object_unref(mText2ChanService);
+ connect(client2,
+ SIGNAL(channelClosed()),
+ SLOT(expectSignalEmission()));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(waitForProperty(handler2Iface->requestPropertyHandledChannels(), &handledChannels));
+ QVERIFY(handledChannels.isEmpty());
+}
+
+void TestClient::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestClient::cleanupTestCase()
+{
+ if (mConn) {
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestClient)
+#include "_gen/client.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/cm-basics.cpp b/qt4/tests/dbus/cm-basics.cpp
new file mode 100644
index 000000000..6dea73e24
--- /dev/null
+++ b/qt4/tests/dbus/cm-basics.cpp
@@ -0,0 +1,308 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib/simple-manager.h>
+#include <tests/lib/glib/echo2/connection-manager.h>
+
+#include <TelepathyQt4/ConnectionCapabilities>
+#include <TelepathyQt4/ConnectionManager>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingStringList>
+#include <TelepathyQt4/PresenceSpec>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+namespace
+{
+
+PresenceSpec getPresenceSpec(const PresenceSpecList &specs, const QString &status)
+{
+ Q_FOREACH (const PresenceSpec &spec, specs) {
+ if (spec.presence().status() == status) {
+ return spec;
+ }
+ }
+ return PresenceSpec();
+}
+
+}
+
+class TestCmBasics : public Test
+{
+ Q_OBJECT
+
+public:
+ TestCmBasics(QObject *parent = 0)
+ : Test(parent), mCMService(0)
+ { }
+
+protected Q_SLOTS:
+ void expectListNamesFinished(Tp::PendingOperation *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testBasics();
+ void testLegacy();
+ void testListNames();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TpBaseConnectionManager *mCMService;
+ Tp::ConnectionManagerPtr mCM;
+
+ TpBaseConnectionManager *mCMServiceLegacy;
+ Tp::ConnectionManagerPtr mCMLegacy;
+
+ QStringList mCMNames;
+};
+
+void TestCmBasics::expectListNamesFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingStringList *ps = qobject_cast<PendingStringList*>(op);
+ mCMNames = ps->result();
+ mLoop->exit(0);
+}
+
+void TestCmBasics::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("cm-basics");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mCMService = TP_BASE_CONNECTION_MANAGER(g_object_new(
+ EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER,
+ NULL));
+ QVERIFY(mCMService != 0);
+
+ mCMServiceLegacy = TP_BASE_CONNECTION_MANAGER(g_object_new(
+ TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER,
+ NULL));
+ QVERIFY(mCMServiceLegacy != 0);
+
+ QVERIFY(tp_base_connection_manager_register(mCMService));
+ QVERIFY(tp_base_connection_manager_register(mCMServiceLegacy));
+}
+
+void TestCmBasics::init()
+{
+ initImpl();
+}
+
+void TestCmBasics::testBasics()
+{
+ mCM = ConnectionManager::create(QLatin1String("example_echo_2"));
+ QCOMPARE(mCM->isReady(), false);
+
+ QVERIFY(connect(mCM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mCM->isReady(), true);
+
+ // calling becomeReady() twice is a no-op
+ QVERIFY(connect(mCM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mCM->isReady(), true);
+
+ QCOMPARE(mCM->interfaces(), QStringList());
+ QCOMPARE(mCM->supportedProtocols(), QStringList() << QLatin1String("example"));
+
+ QVERIFY(mCM->hasProtocol(QLatin1String("example")));
+ QVERIFY(!mCM->hasProtocol(QLatin1String("not-there")));
+
+ ProtocolInfo info = mCM->protocol(QLatin1String("example"));
+ QVERIFY(info.isValid());
+
+ QCOMPARE(info.cmName(), QLatin1String("example_echo_2"));
+ QCOMPARE(info.name(), QLatin1String("example"));
+
+ QCOMPARE(info.hasParameter(QLatin1String("account")), true);
+ QCOMPARE(info.hasParameter(QLatin1String("not-there")), false);
+
+ QCOMPARE(info.parameters().size(), 1);
+
+ ProtocolParameter param = info.parameters().at(0);
+ QCOMPARE(param.name(), QLatin1String("account"));
+ QCOMPARE(static_cast<uint>(param.type()), static_cast<uint>(QVariant::String));
+ QCOMPARE(param.defaultValue().isNull(), true);
+ QCOMPARE(param.dbusSignature().signature(), QLatin1String("s"));
+ QCOMPARE(param.isRequired(), true);
+ QCOMPARE(param.isRequiredForRegistration(), true); // though it can't register!
+ QCOMPARE(param.isSecret(), false);
+
+ QVERIFY(param == QLatin1String("account"));
+
+ ProtocolParameter otherParam = info.parameters().value(1);
+ QVERIFY(!otherParam.isValid());
+ QCOMPARE(otherParam.name(), QString());
+ QCOMPARE(otherParam.dbusSignature(), QDBusSignature());
+ QCOMPARE(otherParam.type(), QVariant::Invalid);
+ QCOMPARE(otherParam.defaultValue(), QVariant());
+ QCOMPARE(otherParam.isRequired(), false);
+ QCOMPARE(otherParam.isSecret(), false);
+ QCOMPARE(otherParam.isRequiredForRegistration(), false);
+
+ QCOMPARE(info.canRegister(), false);
+
+ QCOMPARE(info.capabilities().isSpecificToContact(), false);
+ QCOMPARE(info.capabilities().textChatrooms(), false);
+ QCOMPARE(info.capabilities().textChats(), true);
+ QCOMPARE(info.capabilities().streamedMediaCalls(), false);
+ QCOMPARE(info.capabilities().streamedMediaAudioCalls(), false);
+ QCOMPARE(info.capabilities().streamedMediaVideoCalls(), false);
+ QCOMPARE(info.capabilities().streamedMediaVideoCallsWithAudio(), false);
+ QCOMPARE(info.capabilities().upgradingStreamedMediaCalls(), false);
+
+ QCOMPARE(info.vcardField(), QLatin1String("x-telepathy-example"));
+ QCOMPARE(info.englishName(), QLatin1String("Echo II example"));
+ QCOMPARE(info.iconName(), QLatin1String("im-icq"));
+
+ PresenceSpecList statuses = info.allowedPresenceStatuses();
+ QCOMPARE(statuses.size(), 3);
+ PresenceSpec spec;
+ spec = getPresenceSpec(statuses, QLatin1String("offline"));
+ QCOMPARE(spec.isValid(), true);
+ QVERIFY(spec.presence().type() == ConnectionPresenceTypeOffline);
+ QCOMPARE(spec.maySetOnSelf(), false);
+ QCOMPARE(spec.canHaveStatusMessage(), false);
+ spec = getPresenceSpec(statuses, QLatin1String("dnd"));
+ QCOMPARE(spec.isValid(), true);
+ QVERIFY(spec.presence().type() == ConnectionPresenceTypeBusy);
+ QCOMPARE(spec.maySetOnSelf(), true);
+ QCOMPARE(spec.canHaveStatusMessage(), false);
+ spec = getPresenceSpec(statuses, QLatin1String("available"));
+ QCOMPARE(spec.isValid(), true);
+ QVERIFY(spec.presence().type() == ConnectionPresenceTypeAvailable);
+ QCOMPARE(spec.maySetOnSelf(), true);
+ QCOMPARE(spec.canHaveStatusMessage(), true);
+
+ AvatarSpec avatarReqs = info.avatarRequirements();
+ QStringList supportedMimeTypes = avatarReqs.supportedMimeTypes();
+ supportedMimeTypes.sort();
+ QCOMPARE(supportedMimeTypes,
+ QStringList() << QLatin1String("image/gif") << QLatin1String("image/jpeg") <<
+ QLatin1String("image/png"));
+ QCOMPARE(avatarReqs.minimumHeight(), (uint) 32);
+ QCOMPARE(avatarReqs.maximumHeight(), (uint) 96);
+ QCOMPARE(avatarReqs.recommendedHeight(), (uint) 64);
+ QCOMPARE(avatarReqs.minimumWidth(), (uint) 32);
+ QCOMPARE(avatarReqs.maximumWidth(), (uint) 96);
+ QCOMPARE(avatarReqs.recommendedWidth(), (uint) 64);
+ QCOMPARE(avatarReqs.maximumBytes(), (uint) 37748736);
+
+ QCOMPARE(mCM->supportedProtocols(), QStringList() << QLatin1String("example"));
+}
+
+// Test for a CM which doesn't implement Protocol objects
+void TestCmBasics::testLegacy()
+{
+ mCMLegacy = ConnectionManager::create(QLatin1String("simple"));
+ QCOMPARE(mCMLegacy->isReady(), false);
+
+ QVERIFY(connect(mCMLegacy->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mCMLegacy->isReady(), true);
+
+ QCOMPARE(mCMLegacy->interfaces(), QStringList());
+ QCOMPARE(mCMLegacy->supportedProtocols(), QStringList() << QLatin1String("simple"));
+
+ QVERIFY(mCMLegacy->hasProtocol(QLatin1String("simple")));
+ QVERIFY(!mCMLegacy->hasProtocol(QLatin1String("not-there")));
+
+ ProtocolInfo info = mCMLegacy->protocol(QLatin1String("simple"));
+ QVERIFY(info.isValid());
+
+ QCOMPARE(info.cmName(), QLatin1String("simple"));
+ QCOMPARE(info.name(), QLatin1String("simple"));
+
+ QCOMPARE(info.hasParameter(QLatin1String("account")), true);
+ QCOMPARE(info.hasParameter(QLatin1String("not-there")), false);
+
+ QCOMPARE(info.parameters().size(), 1);
+
+ ProtocolParameter param = info.parameters().at(0);
+ QCOMPARE(param.name(), QLatin1String("account"));
+ QCOMPARE(static_cast<uint>(param.type()), static_cast<uint>(QVariant::String));
+ QCOMPARE(param.defaultValue().isNull(), true);
+ QCOMPARE(param.dbusSignature().signature(), QLatin1String("s"));
+ QCOMPARE(param.isRequired(), true);
+ QCOMPARE(param.isRequiredForRegistration(), true);
+ QCOMPARE(param.isSecret(), false);
+
+ QVERIFY(param == QLatin1String("account"));
+
+ QCOMPARE(info.canRegister(), false);
+
+ // Protocol capabilities semantics is "an actual connection supports whatever I claim, or
+ // less", so for a service with no actual Protocol implementation everything should be
+ // assumed to be possible at this point
+ QCOMPARE(info.capabilities().isSpecificToContact(), false);
+ QCOMPARE(info.capabilities().textChatrooms(), true);
+ QCOMPARE(info.capabilities().textChats(), true);
+ QCOMPARE(info.capabilities().streamedMediaCalls(), true);
+ QCOMPARE(info.capabilities().streamedMediaAudioCalls(), true);
+ QCOMPARE(info.capabilities().streamedMediaVideoCalls(), true);
+ QCOMPARE(info.capabilities().streamedMediaVideoCallsWithAudio(), true);
+ QCOMPARE(info.capabilities().upgradingStreamedMediaCalls(), true);
+
+ QCOMPARE(info.vcardField(), QLatin1String(""));
+ QCOMPARE(info.englishName(), QLatin1String("Simple"));
+ QCOMPARE(info.iconName(), QLatin1String("im-simple"));
+
+ QCOMPARE(mCMLegacy->supportedProtocols(), QStringList() << QLatin1String("simple"));
+}
+
+// TODO add a test for the case of getting the information from a .manager file, and if possible,
+// also for using the fallbacks for the CM::Protocols property not being present.
+
+void TestCmBasics::testListNames()
+{
+ QVERIFY(connect(ConnectionManager::listNames(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectListNamesFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mCMNames.size(), 3);
+ QVERIFY(mCMNames.contains(QLatin1String("simple")));
+ QVERIFY(mCMNames.contains(QLatin1String("example_echo_2")));
+ QVERIFY(mCMNames.contains(QLatin1String("spurious")));
+}
+
+void TestCmBasics::cleanup()
+{
+ mCM.reset();
+ mCMLegacy.reset();
+
+ cleanupImpl();
+}
+
+void TestCmBasics::cleanupTestCase()
+{
+ if (mCMService) {
+ g_object_unref(mCMService);
+ mCMService = 0;
+ }
+
+ if (mCMServiceLegacy) {
+ g_object_unref(mCMServiceLegacy);
+ mCMServiceLegacy = 0;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestCmBasics)
+#include "_gen/cm-basics.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-basics.cpp b/qt4/tests/dbus/conn-basics.cpp
new file mode 100644
index 000000000..a5eac4576
--- /dev/null
+++ b/qt4/tests/dbus/conn-basics.cpp
@@ -0,0 +1,302 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <QtDBus/QtDBus>
+
+#include <QtTest/QtTest>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Debug>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/debug.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestConnBasics : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnBasics(QObject *parent = 0)
+ : Test(parent), mConnService(0)
+ { }
+
+protected Q_SLOTS:
+ void expectConnReady(Tp::ConnectionStatus);
+ void expectConnInvalidated();
+ void expectPresenceAvailable(const Tp::SimplePresence &);
+ void onRequestConnectFinished(Tp::PendingOperation *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testBasics();
+ void testSimplePresence();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QString mConnName, mConnPath;
+ TpTestsContactsConnection *mConnService;
+ ConnectionPtr mConn;
+ QList<ConnectionStatus> mStatuses;
+};
+
+void TestConnBasics::expectConnReady(Tp::ConnectionStatus newStatus)
+{
+ qDebug() << "connection changed to status" << newStatus;
+ switch (newStatus) {
+ case ConnectionStatusDisconnected:
+ qWarning() << "Disconnected";
+ break;
+ case ConnectionStatusConnecting:
+ QCOMPARE(mConn->isReady(Connection::FeatureConnected), false);
+ mStatuses << newStatus;
+ qDebug() << "Connecting";
+ break;
+ case ConnectionStatusConnected:
+ QCOMPARE(mConn->isReady(Connection::FeatureConnected), true);
+ mStatuses << newStatus;
+ qDebug() << "Connected";
+ break;
+ default:
+ qWarning().nospace() << "What sort of status is "
+ << newStatus << "?!";
+ break;
+ }
+}
+
+void TestConnBasics::expectConnInvalidated()
+{
+ qDebug() << "conn invalidated";
+
+ mLoop->exit(0);
+}
+
+void TestConnBasics::expectPresenceAvailable(const Tp::SimplePresence &presence)
+{
+ if (presence.type == Tp::ConnectionPresenceTypeAvailable) {
+ mLoop->exit(0);
+ return;
+ }
+ mLoop->exit(1);
+}
+
+void TestConnBasics::onRequestConnectFinished(Tp::PendingOperation *op)
+{
+ QCOMPARE(mConn->status(), ConnectionStatusConnected);
+ QVERIFY(mStatuses.contains(ConnectionStatusConnected));
+ mLoop->exit(0);
+}
+
+void TestConnBasics::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-basics");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+}
+
+void TestConnBasics::init()
+{
+ initImpl();
+
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ mConnService = TP_TESTS_CONTACTS_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contacts",
+ NULL));
+ QVERIFY(mConnService != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService),
+ "contacts", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName = QLatin1String(name);
+ mConnPath = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(statusChanged(Tp::ConnectionStatus)),
+ SLOT(expectConnReady(Tp::ConnectionStatus))));
+
+ qDebug() << "waiting connection to become connected";
+ PendingOperation *pr = mConn->becomeReady(Connection::FeatureConnected);
+ QVERIFY(connect(pr,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ PendingOperation *pc = mConn->lowlevel()->requestConnect();
+ QVERIFY(connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onRequestConnectFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(pr->isFinished(), true);
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(pc->isFinished(), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureConnected), true);
+ qDebug() << "connection is now ready";
+
+ QVERIFY(disconnect(mConn.data(),
+ SIGNAL(statusChanged(Tp::ConnectionStatus)),
+ this,
+ SLOT(expectConnReady(Tp::ConnectionStatus))));
+}
+
+void TestConnBasics::testBasics()
+{
+ QCOMPARE(static_cast<uint>(mConn->statusReason()),
+ static_cast<uint>(ConnectionStatusReasonRequested));
+}
+
+void TestConnBasics::testSimplePresence()
+{
+ qDebug() << "Making SimplePresence ready";
+
+ Features features = Features() << Connection::FeatureSimplePresence;
+ QCOMPARE(mConn->isReady(features), false);
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ qDebug() << "SimplePresence ready";
+ qDebug() << "mConn->status:" << mConn->status();
+
+ const QStringList canSetNames = QStringList()
+ << QLatin1String("available")
+ << QLatin1String("busy")
+ << QLatin1String("away");
+
+ const QStringList cantSetNames = QStringList()
+ << QLatin1String("offline")
+ << QLatin1String("unknown")
+ << QLatin1String("error");
+
+ const QStringList expectedNames = canSetNames + cantSetNames;
+
+ const ConnectionPresenceType expectedTypes[] = {
+ ConnectionPresenceTypeAvailable,
+ ConnectionPresenceTypeBusy,
+ ConnectionPresenceTypeAway,
+ ConnectionPresenceTypeOffline,
+ ConnectionPresenceTypeUnknown,
+ ConnectionPresenceTypeError
+ };
+
+ SimpleStatusSpecMap statuses = mConn->lowlevel()->allowedPresenceStatuses();
+ Q_FOREACH (QString name, statuses.keys()) {
+ QVERIFY(expectedNames.contains(name));
+
+ if (canSetNames.contains(name)) {
+ QVERIFY(statuses[name].maySetOnSelf);
+ QVERIFY(statuses[name].canHaveMessage);
+ } else {
+ QVERIFY(cantSetNames.contains(name));
+ QVERIFY(!statuses[name].maySetOnSelf);
+ QVERIFY(!statuses[name].canHaveMessage);
+ }
+
+ QCOMPARE(statuses[name].type,
+ static_cast<uint>(expectedTypes[expectedNames.indexOf(name)]));
+ }
+
+ QCOMPARE(mConn->lowlevel()->maxPresenceStatusMessageLength(), (uint) 512);
+}
+
+void TestConnBasics::cleanup()
+{
+ if (mConn) {
+ QVERIFY(mConn->isValid());
+
+ GHashTable *details = tp_asv_new(
+ "debug-message", G_TYPE_STRING, "woo i'm going doooooown",
+ "x-tpqt4-test-rgba-herring-color", G_TYPE_UINT, 0xff0000ffU,
+ NULL
+ );
+
+ // Disconnect and wait for invalidation
+ tp_base_connection_disconnect_with_dbus_error(TP_BASE_CONNECTION(mConnService),
+ TELEPATHY_ERROR_CANCELLED,
+ details,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ g_hash_table_destroy(details);
+
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(expectConnInvalidated())));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(!mConn->isValid());
+
+ // Check that we got the connection error details
+ QCOMPARE(static_cast<uint>(mConn->statusReason()),
+ static_cast<uint>(ConnectionStatusReasonRequested));
+
+ QVERIFY(mConn->errorDetails().isValid());
+
+ QVERIFY(mConn->errorDetails().hasDebugMessage());
+ QCOMPARE(mConn->errorDetails().debugMessage(), QLatin1String("woo i'm going doooooown"));
+
+#if 0
+ // Not yet there
+ QVERIFY(!mConn->errorDetails().hasExpectedHostname());
+ QVERIFY(!mConn->errorDetails().hasCertificateHostname());
+#endif
+
+ QVERIFY(mConn->errorDetails().allDetails().contains(
+ QLatin1String("x-tpqt4-test-rgba-herring-color")));
+ QCOMPARE(qdbus_cast<uint>(mConn->errorDetails().allDetails().value(
+ QLatin1String("x-tpqt4-test-rgba-herring-color"))),
+ 0xff0000ffU);
+
+ processDBusQueue(mConn.data());
+
+ mConn.reset();
+ }
+
+ if (mConnService != 0) {
+ g_object_unref(mConnService);
+ mConnService = 0;
+ }
+
+ cleanupImpl();
+}
+
+void TestConnBasics::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnBasics)
+#include "_gen/conn-basics.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-capabilities.cpp b/qt4/tests/dbus/conn-capabilities.cpp
new file mode 100644
index 000000000..edc8568ed
--- /dev/null
+++ b/qt4/tests/dbus/conn-capabilities.cpp
@@ -0,0 +1,107 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo2/conn.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionCapabilities>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestConnCapabilities : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnCapabilities(QObject *parent = 0)
+ : Test(parent), conn(0)
+ { }
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testCapabilities();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *conn;
+};
+
+void TestConnCapabilities::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-capabilities");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+}
+
+void TestConnCapabilities::init()
+{
+ initImpl();
+}
+
+void TestConnCapabilities::testCapabilities()
+{
+ TestConnHelper *conn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contacts",
+ NULL);
+ QCOMPARE(conn->isReady(), false);
+
+ // Before the connection is Ready, it doesn't guarantee support for anything but doesn't crash
+ // either if we ask it for something
+ QCOMPARE(conn->client()->capabilities().textChats(), false);
+ QCOMPARE(conn->client()->capabilities().textChatrooms(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaAudioCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaVideoCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaVideoCallsWithAudio(), false);
+ QCOMPARE(conn->client()->capabilities().upgradingStreamedMediaCalls(), false);
+
+ QCOMPARE(conn->connect(), true);
+
+ // Now we should have the real information on what the connection supports
+ QCOMPARE(conn->client()->capabilities().textChats(), true);
+ QCOMPARE(conn->client()->capabilities().textChatrooms(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaAudioCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaVideoCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaVideoCallsWithAudio(), false);
+ QCOMPARE(conn->client()->capabilities().upgradingStreamedMediaCalls(), false);
+
+ // Now, invalidate the connection by disconnecting it
+ QCOMPARE(conn->disconnect(), true);
+
+ // Check that no support for anything is again reported
+ QCOMPARE(conn->client()->capabilities().textChats(), false);
+ QCOMPARE(conn->client()->capabilities().textChatrooms(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaAudioCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaVideoCalls(), false);
+ QCOMPARE(conn->client()->capabilities().streamedMediaVideoCallsWithAudio(), false);
+ QCOMPARE(conn->client()->capabilities().upgradingStreamedMediaCalls(), false);
+
+ delete conn;
+}
+
+void TestConnCapabilities::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestConnCapabilities::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnCapabilities)
+#include "_gen/conn-capabilities.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-introspect-cornercases.cpp b/qt4/tests/dbus/conn-introspect-cornercases.cpp
new file mode 100644
index 000000000..692bb7374
--- /dev/null
+++ b/qt4/tests/dbus/conn-introspect-cornercases.cpp
@@ -0,0 +1,504 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <QtDBus/QtDBus>
+
+#include <QtTest/QtTest>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/debug.h>
+
+#include <tests/lib/glib/bug16307-conn.h>
+#include <tests/lib/glib/contacts-noroster-conn.h>
+#include <tests/lib/glib/simple-conn.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestConnIntrospectCornercases : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnIntrospectCornercases(QObject *parent = 0)
+ : Test(parent), mConnService(0), mNumSelfHandleChanged(0)
+ { }
+
+protected Q_SLOTS:
+ void expectConnInvalidated();
+ void onSelfHandleChanged(uint);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testSelfHandleChangeBeforeConnecting();
+ void testSelfHandleChangeWhileBuilding();
+ void testSlowpath();
+ void testStatusChange();
+ void testNoRoster();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TpBaseConnection *mConnService;
+ ConnectionPtr mConn;
+ QList<ConnectionStatus> mStatuses;
+ int mNumSelfHandleChanged;
+};
+
+void TestConnIntrospectCornercases::expectConnInvalidated()
+{
+ qDebug() << "conn invalidated";
+
+ mLoop->exit(0);
+}
+
+void TestConnIntrospectCornercases::onSelfHandleChanged(uint handle)
+{
+ qDebug() << "got new self handle" << handle;
+ mNumSelfHandleChanged++;
+}
+
+void TestConnIntrospectCornercases::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-introspect-cornercases");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+}
+
+void TestConnIntrospectCornercases::init()
+{
+ initImpl();
+
+ QVERIFY(mConn.isNull());
+ QVERIFY(mConnService == 0);
+
+ QVERIFY(mStatuses.empty());
+ QCOMPARE(mNumSelfHandleChanged, 0);
+
+ // don't create the client- or service-side connection objects here, as it's expected that many
+ // different types of service connections with different initial states need to be used
+}
+
+void TestConnIntrospectCornercases::testSelfHandleChangeBeforeConnecting()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ TpTestsSimpleConnection *simpleConnService =
+ TP_TESTS_SIMPLE_CONNECTION(
+ g_object_new(
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(simpleConnService != 0);
+
+ mConnService = TP_BASE_CONNECTION(simpleConnService);
+ QVERIFY(mConnService != 0);
+
+ QVERIFY(tp_base_connection_register(mConnService, "simple",
+ &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ mConn = Connection::create(QLatin1String(name), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ g_free(name); name = 0;
+ g_free(connPath); connPath = 0;
+
+ // Set the initial self handle (we're not using the conn service normally, so it doesn't do this
+ // by itself)
+ tp_tests_simple_connection_set_identifier(simpleConnService, "me@example.com");
+
+ // Make the conn Connecting, and with FeatureCore ready
+
+ tp_base_connection_change_status(mConnService, TP_CONNECTION_STATUS_CONNECTING,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ PendingOperation *op = mConn->becomeReady();
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(op->isFinished());
+ QVERIFY(mConn->isValid());
+ QVERIFY(op->isValid());
+
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(Tp::ConnectionStatusConnecting));
+
+ // Start introspecting the SelfContact feature
+
+ op = mConn->becomeReady(Connection::FeatureSelfContact | Connection::FeatureConnected);
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ // Change the self handle, before the connection is Connected
+
+ tp_tests_simple_connection_set_identifier(simpleConnService, "myself@example.com");
+
+ // Now change it to Connected
+ tp_base_connection_change_status(mConnService, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ // Try to finish the SelfContact operation, running the mainloop for a while
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(op->isFinished(), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureSelfContact), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusConnected));
+}
+
+void TestConnIntrospectCornercases::testSelfHandleChangeWhileBuilding()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ TpTestsSimpleConnection *simpleConnService =
+ TP_TESTS_SIMPLE_CONNECTION(
+ g_object_new(
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(simpleConnService != 0);
+
+ mConnService = TP_BASE_CONNECTION(simpleConnService);
+ QVERIFY(mConnService != 0);
+
+ QVERIFY(tp_base_connection_register(mConnService, "simple",
+ &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ mConn = Connection::create(QLatin1String(name), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ g_free(name); name = 0;
+ g_free(connPath); connPath = 0;
+
+ // Make the conn Connected, and with FeatureCore ready
+
+ PendingOperation *op = mConn->lowlevel()->requestConnect();
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(op->isFinished());
+ QVERIFY(mConn->isValid());
+ QVERIFY(op->isValid());
+
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(Tp::ConnectionStatusConnected));
+
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QVERIFY(mConn->selfHandle() != 0);
+
+ // Start introspecting the SelfContact feature
+
+ op = mConn->becomeReady(Connection::FeatureSelfContact);
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ // Run one mainloop iteration, so ReadinessHelper calls introspectSelfContact
+ mLoop->processEvents();
+
+ // Change the self handle, so a rebuild has to be done after the first build finishes
+ tp_tests_simple_connection_set_identifier(simpleConnService, "myself@example.com");
+
+ // Try to finish the SelfContact operation, running the mainloop for a while
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(op->isFinished(), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureSelfContact), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusConnected));
+ QCOMPARE(mConn->selfContact()->id(), QString::fromLatin1("me@example.com"));
+
+ // We should shortly also receive a self contact change to the rebuilt contact
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(selfContactChanged()),
+ mLoop,
+ SLOT(quit())));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->selfContact()->id(), QString::fromLatin1("myself@example.com"));
+ QCOMPARE(mConn->selfContact()->handle()[0], mConn->selfHandle());
+
+ // Change the self handle yet again, which should cause a self handle and self contact change to be signalled
+ // (in that order)
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(selfHandleChanged(uint)),
+ SLOT(onSelfHandleChanged(uint))));
+
+ tp_tests_simple_connection_set_identifier(simpleConnService, "irene@example.com");
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureSelfContact), true);
+
+ // We should've received a single self handle change and the self contact should've changed
+ // (exiting the mainloop)
+ QCOMPARE(mNumSelfHandleChanged, 1);
+ QCOMPARE(mConn->selfContact()->id(), QString::fromLatin1("irene@example.com"));
+ QCOMPARE(mConn->selfContact()->handle()[0], mConn->selfHandle());
+
+ // Last but not least, try two consequtive changes
+ tp_tests_simple_connection_set_identifier(simpleConnService, "me@example.com");
+ tp_tests_simple_connection_set_identifier(simpleConnService, "myself@example.com");
+
+ // We should receive two more self handle changes in total, and one self contact change for
+ // each mainloop run
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->selfContact()->id(), QString::fromLatin1("me@example.com"));
+
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->selfContact()->id(), QString::fromLatin1("myself@example.com"));
+
+ QCOMPARE(mNumSelfHandleChanged, 3);
+}
+
+void TestConnIntrospectCornercases::testSlowpath()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ TpTestsBug16307Connection *bugConnService =
+ TP_TESTS_BUG16307_CONNECTION(
+ g_object_new(
+ TP_TESTS_TYPE_BUG16307_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(bugConnService != 0);
+
+ mConnService = TP_BASE_CONNECTION(bugConnService);
+ QVERIFY(mConnService != 0);
+
+ QVERIFY(tp_base_connection_register(mConnService, "simple",
+ &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ mConn = Connection::create(QLatin1String(name), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ g_free(name); name = 0;
+ g_free(connPath); connPath = 0;
+
+ PendingOperation *op = mConn->becomeReady();
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ tp_tests_bug16307_connection_inject_get_status_return(bugConnService);
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(op->isFinished(), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusConnected));
+}
+
+void TestConnIntrospectCornercases::testStatusChange()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ TpTestsSimpleConnection *simpleConnService =
+ TP_TESTS_SIMPLE_CONNECTION(
+ g_object_new(
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(simpleConnService != 0);
+
+ mConnService = TP_BASE_CONNECTION(simpleConnService);
+ QVERIFY(mConnService != 0);
+
+ QVERIFY(tp_base_connection_register(mConnService, "simple",
+ &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ mConn = Connection::create(QLatin1String(name), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ g_free(name); name = 0;
+ g_free(connPath); connPath = 0;
+
+ // Make core ready first, because Connection has internal handling for the status changing
+ // during core introspection, and we rather want to test the more general ReadinessHelper
+ // mechanism
+
+ PendingOperation *op = mConn->becomeReady();
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(op->isFinished(), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusDisconnected));
+
+ // Now, begin making Connected ready
+
+ op = mConn->becomeReady(Connection::FeatureConnected);
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+
+ mLoop->processEvents();
+
+ // But disturb it by changing the status!
+
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles(mConnService,
+ TP_HANDLE_TYPE_CONTACT);
+
+ mConnService->self_handle = tp_handle_ensure(contact_repo, "me@example.com",
+ NULL, NULL);
+
+ tp_base_connection_change_status(mConnService,
+ TP_CONNECTION_STATUS_CONNECTING,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ // Do that again! (The earlier op stil hasn't finished by definition)
+ mConn->becomeReady(Features() << Connection::FeatureConnected);
+
+ tp_base_connection_change_status(mConnService,
+ TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(op->isFinished(), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureCore), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureConnected), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusConnected));
+}
+
+void TestConnIntrospectCornercases::testNoRoster()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ TpTestsContactsNorosterConnection *connService =
+ TP_TESTS_CONTACTS_NOROSTER_CONNECTION(
+ g_object_new(
+ TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contacts-noroster",
+ NULL));
+ QVERIFY(connService != 0);
+
+ mConnService = TP_BASE_CONNECTION(connService);
+ QVERIFY(mConnService != 0);
+
+ QVERIFY(tp_base_connection_register(mConnService, "simple",
+ &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ mConn = Connection::create(QLatin1String(name), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ g_free(name); name = 0;
+ g_free(connPath); connPath = 0;
+
+ PendingOperation *op = mConn->lowlevel()->requestConnect();
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->status(), Tp::ConnectionStatusConnected);
+
+ op = mConn->becomeReady(Connection::FeatureRoster);
+ QVERIFY(connect(op,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(op->isFinished());
+ QVERIFY(mConn->isReady(Connection::FeatureRoster));
+ QVERIFY(!mConn->actualFeatures().contains(Connection::FeatureRoster));
+}
+
+void TestConnIntrospectCornercases::cleanup()
+{
+ if (mConn) {
+ QVERIFY(mConnService != 0);
+
+ // Disconnect and wait for invalidation
+ tp_base_connection_change_status(
+ mConnService,
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(expectConnInvalidated())));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(!mConn->isValid());
+
+ processDBusQueue(mConn.data());
+
+ mConn.reset();
+ }
+
+ if (mConnService != 0) {
+ g_object_unref(mConnService);
+ mConnService = 0;
+ }
+
+ mStatuses.clear();
+ mNumSelfHandleChanged = 0;
+
+ cleanupImpl();
+}
+
+void TestConnIntrospectCornercases::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnIntrospectCornercases)
+#include "_gen/conn-introspect-cornercases.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-requests.cpp b/qt4/tests/dbus/conn-requests.cpp
new file mode 100644
index 000000000..b9fd25153
--- /dev/null
+++ b/qt4/tests/dbus/conn-requests.cpp
@@ -0,0 +1,165 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo2/conn.h>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestConnRequests : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnRequests(QObject *parent = 0)
+ : Test(parent), mConn(0), mHandle(0)
+ { }
+
+protected Q_SLOTS:
+ void expectPendingHandleFinished(Tp::PendingOperation*);
+ void expectCreateChannelFinished(Tp::PendingOperation *);
+ void expectEnsureChannelFinished(Tp::PendingOperation *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRequestHandle();
+ void testCreateChannel();
+ void testEnsureChannel();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ QString mChanObjectPath;
+ uint mHandle;
+};
+
+void TestConnRequests::expectPendingHandleFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingHandles *pending = qobject_cast<PendingHandles*>(op);
+ mHandle = pending->handles().at(0);
+ mLoop->exit(0);
+}
+
+void TestConnRequests::expectCreateChannelFinished(PendingOperation* op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingChannel *pc = qobject_cast<PendingChannel*>(op);
+ ChannelPtr chan = pc->channel();
+ mChanObjectPath = chan->objectPath();
+ mLoop->exit(0);
+}
+
+void TestConnRequests::expectEnsureChannelFinished(PendingOperation* op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingChannel *pc = qobject_cast<PendingChannel*>(op);
+ ChannelPtr chan = pc->channel();
+ QCOMPARE(pc->yours(), false);
+ QCOMPARE(chan->objectPath(), mChanObjectPath);
+ mLoop->exit(0);
+}
+
+void TestConnRequests::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-requests");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contacts",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestConnRequests::init()
+{
+ initImpl();
+}
+
+void TestConnRequests::testRequestHandle()
+{
+ // Test identifiers
+ QStringList ids = QStringList() << QLatin1String("alice");
+
+ // Request handles for the identifiers and wait for the request to process
+ PendingHandles *pending = mConn->client()->lowlevel()->requestHandles(Tp::HandleTypeContact, ids);
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingHandleFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(disconnect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ this,
+ SLOT(expectPendingHandleFinished(Tp::PendingOperation*))));
+ QVERIFY(mHandle != 0);
+}
+
+void TestConnRequests::testCreateChannel()
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ mHandle);
+ QVERIFY(connect(mConn->client()->lowlevel()->createChannel(request),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectCreateChannelFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestConnRequests::testEnsureChannel()
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ mHandle);
+ QVERIFY(connect(mConn->client()->lowlevel()->ensureChannel(request),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectEnsureChannelFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestConnRequests::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestConnRequests::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnRequests)
+#include "_gen/conn-requests.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-roster-groups-legacy.cpp b/qt4/tests/dbus/conn-roster-groups-legacy.cpp
new file mode 100644
index 000000000..a34bf9bbe
--- /dev/null
+++ b/qt4/tests/dbus/conn-roster-groups-legacy.cpp
@@ -0,0 +1,849 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <QtDBus/QtDBus>
+
+#include <QtTest/QtTest>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Debug>
+
+#include <telepathy-glib/debug.h>
+
+#include <tests/lib/glib/contactlist/conn.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestConnRosterGroupsLegacy : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnRosterGroupsLegacy(QObject *parent = 0)
+ : Test(parent), mConnService(0),
+ mContactsAddedToGroup(0), mContactsRemovedFromGroup(0)
+ { }
+
+protected Q_SLOTS:
+ void onGroupAdded(const QString &group);
+ void onGroupRemoved(const QString &group);
+ void onContactAddedToGroup(const QString &group);
+ void onContactRemovedFromGroup(const QString &group);
+ void expectConnInvalidated();
+ void expectContact(Tp::PendingOperation*);
+ void exitOnStateSuccess(Tp::ContactListState);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testGroupsAfterStateChange();
+ void testIntrospectAfterStateChange();
+ void testRosterGroups();
+ void testNotADeathTrap();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QString mConnName, mConnPath;
+ ExampleContactListConnection *mConnService;
+ ConnectionPtr mConn;
+ ContactPtr mContact;
+
+ QString mGroupAdded;
+ QString mGroupRemoved;
+ int mContactsAddedToGroup;
+ int mContactsRemovedFromGroup;
+ bool mConnInvalidated;
+};
+
+void TestConnRosterGroupsLegacy::onGroupAdded(const QString &group)
+{
+ mGroupAdded = group;
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroupsLegacy::onGroupRemoved(const QString &group)
+{
+ mGroupRemoved = group;
+ mLoop->exit(0);
+}
+
+
+void TestConnRosterGroupsLegacy::onContactAddedToGroup(const QString &group)
+{
+ mContactsAddedToGroup++;
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroupsLegacy::onContactRemovedFromGroup(const QString &group)
+{
+ mContactsRemovedFromGroup++;
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroupsLegacy::expectConnInvalidated()
+{
+ mConnInvalidated = true;
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroupsLegacy::expectContact(Tp::PendingOperation *op)
+{
+ PendingContacts *contacts = qobject_cast<PendingContacts *>(op);
+ QVERIFY(contacts != 0);
+
+ QVERIFY(contacts->isValid());
+ QCOMPARE(contacts->contacts().length(), 1);
+
+ mContact = contacts->contacts()[0];
+
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroupsLegacy::exitOnStateSuccess(Tp::ContactListState state)
+{
+ qDebug() << "got contact list state" << state;
+
+ if (state == ContactListStateSuccess) {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRosterGroupsLegacy::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-roster-groups");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+}
+
+void TestConnRosterGroupsLegacy::init()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ mConnService = EXAMPLE_CONTACT_LIST_CONNECTION(g_object_new(
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", "me@example.com",
+ "simulation-delay", 0,
+ "protocol", "example-contact-list",
+ NULL));
+ QVERIFY(mConnService != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService),
+ "foo", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName = QLatin1String(name);
+ mConnPath = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+ initImpl();
+
+ mConnInvalidated = false;
+}
+
+void TestConnRosterGroupsLegacy::testGroupsAfterStateChange()
+{
+ // Create a conn and make the roster groups related features ready
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ ContactManagerPtr contactManager = mConn->contactManager();
+
+ Features features = Features() << Connection::FeatureRoster << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), true);
+
+ // Now start connecting it, and wait for the ContactManager state to turn to Success
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(stateChanged(Tp::ContactListState)),
+ SLOT(exitOnStateSuccess(Tp::ContactListState))));
+
+ mConn->lowlevel()->requestConnect();
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(static_cast<uint>(contactManager->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ // The conn should be valid and have the roster groups features ready when it emits Success
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), true);
+
+ // We should have all the group data downloaded now, check for that
+ QStringList expectedGroups;
+ expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones")
+ << QLatin1String("Montreal");
+ expectedGroups.sort();
+ QStringList groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ // Cambridge
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("geraldine@example.com")
+ << QLatin1String("helen@example.com")
+ << QLatin1String("guillaume@example.com")
+ << QLatin1String("sjoerd@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ // Francophones
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("olivier@example.com")
+ << QLatin1String("geraldine@example.com")
+ << QLatin1String("guillaume@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Francophones"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ // Montreal
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("olivier@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Montreal"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+}
+
+void TestConnRosterGroupsLegacy::testIntrospectAfterStateChange()
+{
+ // Create a conn and make the roster feature ready
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ ContactManagerPtr contactManager = mConn->contactManager();
+
+ Features features = Features() << Connection::FeatureRoster;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), false);
+
+ // Now start connecting it, and wait for the ContactManager state to turn to Success
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(stateChanged(Tp::ContactListState)),
+ SLOT(exitOnStateSuccess(Tp::ContactListState))));
+
+ mConn->lowlevel()->requestConnect();
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(static_cast<uint>(contactManager->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ // The conn should be valid and have the roster feature ready when it emits Success, but not
+ // RosterGroups because we didn't request it
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), false);
+
+ // We should have roster contacts now, but no groups
+ QVERIFY(!contactManager->allKnownContacts().isEmpty());
+ QVERIFY(contactManager->allKnownGroups().isEmpty());
+
+ // Make RosterGroups ready too
+ features = Features() << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), true);
+
+ // We should still have the contacts, and the state should be success
+ QVERIFY(!contactManager->allKnownContacts().isEmpty());
+ QCOMPARE(static_cast<uint>(contactManager->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ // We should have all the group data downloaded now, check for that
+ QStringList expectedGroups;
+ expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones")
+ << QLatin1String("Montreal");
+ expectedGroups.sort();
+ QStringList groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ // Cambridge
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("geraldine@example.com")
+ << QLatin1String("helen@example.com")
+ << QLatin1String("guillaume@example.com")
+ << QLatin1String("sjoerd@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+}
+
+void TestConnRosterGroupsLegacy::testRosterGroups()
+{
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(connect(mConn->lowlevel()->requestConnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(), true);
+ QCOMPARE(mConn->status(), ConnectionStatusConnected);
+
+ Features features = Features() << Connection::FeatureRoster << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ ContactManagerPtr contactManager = mConn->contactManager();
+
+ QStringList expectedGroups;
+ expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones")
+ << QLatin1String("Montreal");
+ expectedGroups.sort();
+ QStringList groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ // Cambridge
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("geraldine@example.com")
+ << QLatin1String("helen@example.com")
+ << QLatin1String("guillaume@example.com")
+ << QLatin1String("sjoerd@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ // Francophones
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("olivier@example.com")
+ << QLatin1String("geraldine@example.com")
+ << QLatin1String("guillaume@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Francophones"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ // Montreal
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("olivier@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Montreal"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ QString group(QLatin1String("foo"));
+ QVERIFY(contactManager->groupContacts(group).isEmpty());
+
+ // add group foo
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(groupAdded(const QString&)),
+ SLOT(onGroupAdded(const QString&))));
+ QVERIFY(connect(contactManager->addGroup(group),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mGroupAdded.isEmpty()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mGroupAdded, group);
+
+ expectedGroups << group;
+ expectedGroups.sort();
+ groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ // add Montreal contacts to group foo
+ Contacts contacts = contactManager->groupContacts(QLatin1String("Montreal"));
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(connect(contact.data(),
+ SIGNAL(addedToGroup(const QString&)),
+ SLOT(onContactAddedToGroup(const QString&))));
+ }
+ QVERIFY(connect(contactManager->addContactsToGroup(group, contacts.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mContactsAddedToGroup != contacts.size()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(contact->groups().contains(group));
+ }
+
+ // remove all contacts from group foo
+ contacts = contactManager->groupContacts(group);
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(connect(contact.data(),
+ SIGNAL(removedFromGroup(const QString&)),
+ SLOT(onContactRemovedFromGroup(const QString&))));
+ }
+ QVERIFY(connect(contactManager->removeContactsFromGroup(group, contacts.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mContactsRemovedFromGroup != contacts.size()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(!contact->groups().contains(group));
+ }
+
+ // add group foo
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(groupRemoved(const QString&)),
+ SLOT(onGroupRemoved(const QString&))));
+ QVERIFY(connect(contactManager->removeGroup(group),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mGroupRemoved.isEmpty()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mGroupRemoved, group);
+
+ expectedGroups.removeOne(group);
+ expectedGroups.sort();
+ groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+}
+
+/**
+ * Verify that ContactManager isn't a death-trap.
+ *
+ * Background: Connection::contactManager() used to unpredictably waver between NULL and the real
+ * manager when the connection was in the process of being disconnected / otherwise invalidated,
+ * which led to a great many segfaults, which was especially unfortunate considering the
+ * ContactManager methods didn't do much any checks at all.
+ */
+void TestConnRosterGroupsLegacy::testNotADeathTrap()
+{
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ // Check that the contact manager doesn't crash, but returns an error (because the conn isn't
+ // ready)
+ QVERIFY(!mConn->contactManager().isNull());
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->lowlevel()->requestConnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(), true);
+ QCOMPARE(mConn->status(), ConnectionStatusConnected);
+
+ // As the conn is now ready, the contact building functions shouldn't return an error now
+ QVERIFY(!mConn->contactManager().isNull());
+
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(),
+ Features()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // In fact, let's build a contact for future use
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(
+ QStringList() << QLatin1String("friendorfoe@example.com")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectContact(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mContact->id() == QLatin1String("friendorfoe@example.com"));
+
+ // Roster operations SHOULD still fail though, as FeatureRoster isn't ready
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->blockContacts(QList<ContactPtr>() << mContact, true),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Now, make Roster ready
+ Features features = Features() << Connection::FeatureRoster;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ // The roster functions should work now
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mContact->subscriptionState() != Contact::PresenceStateNo);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mContact->subscriptionState(), Contact::PresenceStateNo);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // The test CM doesn't support block, so it will never be successful
+
+ // ... but still not the RosterGroup ones
+ QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Those who failed")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("Those who failed")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("Those who failed"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("Those who failed"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Make RosterGroups ready too
+ features = Features() << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ // Now that Core, Roster and RosterGroups are all ready, everything should work
+ QVERIFY(!mConn->contactManager().isNull());
+
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(),
+ Features()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mContact->subscriptionState() != Contact::PresenceStateNo);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mContact->subscriptionState(), Contact::PresenceStateNo);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // The test CM doesn't support block, so it will never be successful
+
+ QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("My successful entourage")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ qDebug() << "waiting for group to be added";
+
+ // FIXME: Remove this once fd.o #29728 is fixed
+ while (!mConn->contactManager()->allKnownGroups().contains(QLatin1String("My successful entourage"))) {
+ mLoop->processEvents();
+ }
+
+ qDebug() << "group has been added";
+
+ QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("My successful entourage"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("My successful entourage"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("My successful entourage")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Now, invalidate the connection by disconnecting it
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(expectConnInvalidated())));
+ mConn->lowlevel()->requestDisconnect();
+
+ // Check that contactManager doesn't go NULL in the process of the connection going invalidated
+ do {
+ QVERIFY(!mConn->contactManager().isNull());
+ mLoop->processEvents();
+ } while (!mConnInvalidated);
+
+ QVERIFY(!mConn->isValid());
+ QCOMPARE(mConn->status(), ConnectionStatusDisconnected);
+
+ // Now that the conn is invalidated NOTHING should work anymore
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(),
+ Features()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->blockContacts(QList<ContactPtr>() << mContact, true),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Future failures")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("Future failures")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("Future failures"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("Future failures"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestConnRosterGroupsLegacy::cleanup()
+{
+ mContact.reset();
+
+ if (mConn && mConn->requestedFeatures().contains(Connection::FeatureCore)) {
+ QVERIFY(mConnService != NULL);
+
+ if (TP_BASE_CONNECTION(mConnService)->status != TP_CONNECTION_STATUS_DISCONNECTED) {
+ tp_base_connection_change_status(TP_BASE_CONNECTION(mConnService),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+ }
+
+ while (mConn->isValid()) {
+ mLoop->processEvents();
+ }
+
+ }
+ mConn.reset();
+
+ if (mConnService != 0) {
+ g_object_unref(mConnService);
+ mConnService = 0;
+ }
+
+ cleanupImpl();
+}
+
+void TestConnRosterGroupsLegacy::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnRosterGroupsLegacy)
+#include "_gen/conn-roster-groups-legacy.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-roster-groups.cpp b/qt4/tests/dbus/conn-roster-groups.cpp
new file mode 100644
index 000000000..7d5a3e6d3
--- /dev/null
+++ b/qt4/tests/dbus/conn-roster-groups.cpp
@@ -0,0 +1,856 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <QtDBus/QtDBus>
+
+#include <QtTest/QtTest>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Debug>
+
+#include <telepathy-glib/debug.h>
+
+#include <tests/lib/glib/contactlist2/conn.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestConnRosterGroups : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnRosterGroups(QObject *parent = 0)
+ : Test(parent), mConnService(0),
+ mContactsAddedToGroup(0), mContactsRemovedFromGroup(0)
+ { }
+
+private:
+ void causeCongestion(const ConnectionPtr &conn, const ContactPtr &contact);
+
+protected Q_SLOTS:
+ void onGroupAdded(const QString &group);
+ void onGroupRemoved(const QString &group);
+ void onContactAddedToGroup(const QString &group);
+ void onContactRemovedFromGroup(const QString &group);
+ void expectConnInvalidated();
+ void expectContact(Tp::PendingOperation*);
+ void exitOnStateSuccess(Tp::ContactListState);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testGroupsAfterStateChange();
+ void testIntrospectAfterStateChange();
+ void testRosterGroups();
+ void testNotADeathTrap();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QString mConnName, mConnPath;
+ ExampleContactListConnection *mConnService;
+ ConnectionPtr mConn;
+ ContactPtr mContact;
+
+ QString mGroupAdded;
+ QString mGroupRemoved;
+ int mContactsAddedToGroup;
+ int mContactsRemovedFromGroup;
+ bool mConnInvalidated;
+};
+
+void TestConnRosterGroups::causeCongestion(const ConnectionPtr &conn, const ContactPtr &contact)
+{
+ // Cause some congestion in the roster events queue so we can check that it doesn't cause
+ // inconsistent event reordering
+ for (int i = 0; i < 5; i++) {
+ QString name = QString(QLatin1String("Rush%1")).arg(i);
+ conn->contactManager()->addGroup(name);
+ conn->contactManager()->addContactsToGroup(name, QList<ContactPtr>() << contact);
+ contact->requestPresenceSubscription();
+ contact->removePresenceSubscription();
+ conn->contactManager()->removeGroup(name);
+ }
+}
+
+void TestConnRosterGroups::onGroupAdded(const QString &group)
+{
+ if (group.startsWith(QLatin1String("Rush"))) {
+ return;
+ }
+
+ mGroupAdded = group;
+}
+
+void TestConnRosterGroups::onGroupRemoved(const QString &group)
+{
+ if (group.startsWith(QLatin1String("Rush"))) {
+ return;
+ }
+
+ mGroupRemoved = group;
+}
+
+void TestConnRosterGroups::onContactAddedToGroup(const QString &group)
+{
+ if (group.startsWith(QLatin1String("Rush"))) {
+ return;
+ }
+
+ mContactsAddedToGroup++;
+}
+
+void TestConnRosterGroups::onContactRemovedFromGroup(const QString &group)
+{
+ if (group.startsWith(QLatin1String("Rush"))) {
+ return;
+ }
+
+ mContactsRemovedFromGroup++;
+}
+
+void TestConnRosterGroups::expectConnInvalidated()
+{
+ mConnInvalidated = true;
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroups::expectContact(Tp::PendingOperation *op)
+{
+ PendingContacts *contacts = qobject_cast<PendingContacts *>(op);
+ QVERIFY(contacts != 0);
+
+ QVERIFY(contacts->isValid());
+ QCOMPARE(contacts->contacts().length(), 1);
+
+ mContact = contacts->contacts()[0];
+
+ mLoop->exit(0);
+}
+
+void TestConnRosterGroups::exitOnStateSuccess(Tp::ContactListState state)
+{
+ qDebug() << "got contact list state" << state;
+
+ if (state == ContactListStateSuccess) {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRosterGroups::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-roster-groups");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+}
+
+void TestConnRosterGroups::init()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ mConnService = EXAMPLE_CONTACT_LIST_CONNECTION(g_object_new(
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", "me@example.com",
+ "simulation-delay", 0,
+ "protocol", "example-contact-list",
+ NULL));
+ QVERIFY(mConnService != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService),
+ "foo", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName = QLatin1String(name);
+ mConnPath = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+ initImpl();
+
+ mConnInvalidated = false;
+}
+
+void TestConnRosterGroups::testGroupsAfterStateChange()
+{
+ // Create a conn and make the roster groups related features ready
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ ContactManagerPtr contactManager = mConn->contactManager();
+
+ Features features = Features() << Connection::FeatureRoster << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), true);
+
+ // Now start connecting it, and wait for the ContactManager state to turn to Success
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(stateChanged(Tp::ContactListState)),
+ SLOT(exitOnStateSuccess(Tp::ContactListState))));
+
+ mConn->lowlevel()->requestConnect();
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(static_cast<uint>(contactManager->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ // The conn should be valid and have the roster groups features ready when it emits Success
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), true);
+
+ // We should have all the group data downloaded now, check for that
+ QStringList expectedGroups;
+ expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones")
+ << QLatin1String("Montreal");
+ expectedGroups.sort();
+ QStringList groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ // Cambridge
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("geraldine@example.com")
+ << QLatin1String("helen@example.com")
+ << QLatin1String("guillaume@example.com")
+ << QLatin1String("sjoerd@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ // Francophones
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("olivier@example.com")
+ << QLatin1String("geraldine@example.com")
+ << QLatin1String("guillaume@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Francophones"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+
+ // Montreal
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("olivier@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Montreal"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+}
+
+void TestConnRosterGroups::testIntrospectAfterStateChange()
+{
+ // Create a conn and make the roster feature ready
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ ContactManagerPtr contactManager = mConn->contactManager();
+
+ Features features = Features() << Connection::FeatureRoster;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), false);
+
+ // Now start connecting it, and wait for the ContactManager state to turn to Success
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(stateChanged(Tp::ContactListState)),
+ SLOT(exitOnStateSuccess(Tp::ContactListState))));
+
+ mConn->lowlevel()->requestConnect();
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(static_cast<uint>(contactManager->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ // The conn should be valid and have the roster feature ready when it emits Success, but not
+ // RosterGroups because we didn't request it
+ QVERIFY(mConn->isValid());
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), false);
+
+ // We should have roster contacts now, but no groups
+ QVERIFY(!contactManager->allKnownContacts().isEmpty());
+ QVERIFY(contactManager->allKnownGroups().isEmpty());
+
+ // Make RosterGroups ready too
+ features = Features() << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(Connection::FeatureRoster), true);
+ QCOMPARE(mConn->isReady(Connection::FeatureRosterGroups), true);
+
+ // We should still have the contacts, and the state should be success
+ QVERIFY(!contactManager->allKnownContacts().isEmpty());
+ QCOMPARE(static_cast<uint>(contactManager->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ // We should have all the group data downloaded now, check for that
+ QStringList expectedGroups;
+ expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones")
+ << QLatin1String("Montreal");
+ expectedGroups.sort();
+ QStringList groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ // Cambridge
+ {
+ QStringList expectedContacts;
+ expectedContacts << QLatin1String("geraldine@example.com")
+ << QLatin1String("helen@example.com")
+ << QLatin1String("guillaume@example.com")
+ << QLatin1String("sjoerd@example.com");
+ expectedContacts.sort();
+ QStringList contacts;
+ Q_FOREACH (const ContactPtr &contact, contactManager->groupContacts(QLatin1String("Cambridge"))) {
+ contacts << contact->id();
+ }
+ contacts.sort();
+ QCOMPARE(contacts, expectedContacts);
+ }
+}
+
+void TestConnRosterGroups::testRosterGroups()
+{
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(connect(mConn->lowlevel()->requestConnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(), true);
+ QCOMPARE(mConn->status(), ConnectionStatusConnected);
+
+ Features features = Features() << Connection::FeatureRoster << Connection::FeatureRosterGroups
+ << Connection::FeatureSelfContact;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ QCOMPARE(static_cast<uint>(mConn->contactManager()->state()),
+ static_cast<uint>(ContactListStateSuccess));
+
+ ContactManagerPtr contactManager = mConn->contactManager();
+
+ QStringList expectedGroups;
+ expectedGroups << QLatin1String("Cambridge") << QLatin1String("Francophones")
+ << QLatin1String("Montreal");
+ expectedGroups.sort();
+ QStringList groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ QString group(QLatin1String("foo"));
+ QVERIFY(contactManager->groupContacts(group).isEmpty());
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ // add group foo
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(groupAdded(const QString&)),
+ SLOT(onGroupAdded(const QString&))));
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ QVERIFY(connect(contactManager->addGroup(group),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(!mGroupAdded.isEmpty());
+ QCOMPARE(mGroupAdded, group);
+
+ expectedGroups << group;
+ expectedGroups.sort();
+ groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ // add Montreal contacts to group foo
+ Contacts contacts = contactManager->groupContacts(QLatin1String("Montreal"));
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(connect(contact.data(),
+ SIGNAL(addedToGroup(const QString&)),
+ SLOT(onContactAddedToGroup(const QString&))));
+ }
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ QVERIFY(connect(contactManager->addContactsToGroup(group, contacts.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mContactsAddedToGroup, contacts.size());
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(contact->groups().contains(group));
+ }
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ // remove all contacts from group foo
+ contacts = contactManager->groupContacts(group);
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(connect(contact.data(),
+ SIGNAL(removedFromGroup(const QString&)),
+ SLOT(onContactRemovedFromGroup(const QString&))));
+ }
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ QVERIFY(connect(contactManager->removeContactsFromGroup(group, contacts.toList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mContactsRemovedFromGroup, contacts.size());
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(!contact->groups().contains(group));
+ }
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ // remove group foo
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(groupRemoved(const QString&)),
+ SLOT(onGroupRemoved(const QString&))));
+
+ causeCongestion(mConn, mConn->selfContact());
+
+ QVERIFY(connect(contactManager->removeGroup(group),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(!mGroupRemoved.isEmpty());
+ QCOMPARE(mGroupRemoved, group);
+
+ expectedGroups.removeOne(group);
+ expectedGroups.sort();
+ groups = contactManager->allKnownGroups();
+ groups.sort();
+ QCOMPARE(groups, expectedGroups);
+}
+
+/**
+ * Verify that ContactManager isn't a death-trap.
+ *
+ * Background: Connection::contactManager() used to unpredictably waver between NULL and the real
+ * manager when the connection was in the process of being disconnected / otherwise invalidated,
+ * which led to a great many segfaults, which was especially unfortunate considering the
+ * ContactManager methods didn't do much any checks at all.
+ */
+void TestConnRosterGroups::testNotADeathTrap()
+{
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ // Check that the contact manager doesn't crash, but returns an error (because the conn isn't
+ // ready)
+ QVERIFY(!mConn->contactManager().isNull());
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->lowlevel()->requestConnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(), true);
+ QCOMPARE(mConn->status(), ConnectionStatusConnected);
+
+ // As the conn is now ready, the contact building functions shouldn't return an error now
+ QVERIFY(!mConn->contactManager().isNull());
+
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(),
+ Features()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // In fact, let's build a contact for future use
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(
+ QStringList() << QLatin1String("friendorfoe@example.com")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectContact(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mContact->id() == QLatin1String("friendorfoe@example.com"));
+
+ // Roster operations SHOULD still fail though, as FeatureRoster isn't ready
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("I just want to see you fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Now, make Roster ready
+ Features features = Features() << Connection::FeatureRoster;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ causeCongestion(mConn, mContact);
+
+ // The roster functions should work now
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mContact->subscriptionState() != Contact::PresenceStateNo);
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mContact->subscriptionState(), Contact::PresenceStateNo);
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // ... but still not the RosterGroup ones
+ QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Those who failed")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("Those who failed")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("Those who failed"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("Those who failed"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Make RosterGroups ready too
+ features = Features() << Connection::FeatureRosterGroups;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ // Now that Core, Roster and RosterGroups are all ready, everything should work
+ QVERIFY(!mConn->contactManager().isNull());
+
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(),
+ Features()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mContact->subscriptionState() != Contact::PresenceStateNo);
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mContact->subscriptionState(), Contact::PresenceStateNo);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(
+ QList<ContactPtr>() << mContact,
+ QLatin1String("Please don't fail")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("My successful entourage")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mConn->contactManager()->allKnownGroups().contains(QLatin1String("My successful entourage")));
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("My successful entourage"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mConn->contactManager()->
+ groupContacts(QLatin1String("My successful entourage")).contains(mContact));
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("My successful entourage"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mConn->contactManager()->
+ groupContacts(QLatin1String("My successful entourage")).contains(mContact));
+
+ causeCongestion(mConn, mContact);
+
+ QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("My successful entourage")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mConn->contactManager()->allKnownGroups().contains(QLatin1String("My successful entourage")));
+
+ // Now, invalidate the connection by disconnecting it
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(expectConnInvalidated())));
+ mConn->lowlevel()->requestDisconnect();
+
+ // Check that contactManager doesn't go NULL in the process of the connection going invalidated
+ do {
+ QVERIFY(!mConn->contactManager().isNull());
+ mLoop->processEvents();
+ } while (!mConnInvalidated);
+
+ QVERIFY(!mConn->isValid());
+ QCOMPARE(mConn->status(), ConnectionStatusDisconnected);
+
+ // Now that the conn is invalidated NOTHING should work anymore
+ QVERIFY(connect(mConn->contactManager()->contactsForIdentifiers(QStringList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->contactsForHandles(UIntList()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->upgradeContacts(QList<ContactPtr>(),
+ Features()),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->requestPresenceSubscription(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresenceSubscription(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->authorizePresencePublication(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removePresencePublication(QList<ContactPtr>(),
+ QLatin1String("You fail at life")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->addGroup(QLatin1String("Future failures")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeGroup(QLatin1String("Future failures")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->addContactsToGroup(QLatin1String("Future failures"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mConn->contactManager()->removeContactsFromGroup(QLatin1String("Future failures"),
+ QList<ContactPtr>() << mContact),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestConnRosterGroups::cleanup()
+{
+ mContact.reset();
+
+ if (mConn && mConn->requestedFeatures().contains(Connection::FeatureCore)) {
+ QVERIFY(mConnService != NULL);
+
+ if (TP_BASE_CONNECTION(mConnService)->status != TP_CONNECTION_STATUS_DISCONNECTED) {
+ tp_base_connection_change_status(TP_BASE_CONNECTION(mConnService),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+ }
+
+ while (mConn->isValid()) {
+ mLoop->processEvents();
+ }
+
+ }
+ mConn.reset();
+
+ if (mConnService != 0) {
+ g_object_unref(mConnService);
+ mConnService = 0;
+ }
+
+ cleanupImpl();
+}
+
+void TestConnRosterGroups::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnRosterGroups)
+#include "_gen/conn-roster-groups.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-roster-legacy.cpp b/qt4/tests/dbus/conn-roster-legacy.cpp
new file mode 100644
index 000000000..80d153d14
--- /dev/null
+++ b/qt4/tests/dbus/conn-roster-legacy.cpp
@@ -0,0 +1,475 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contactlist/conn.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestConnRosterLegacy : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnRosterLegacy(QObject *parent = 0)
+ : Test(parent), mConn(0),
+ mBlockingContactsFinished(false), mHowManyKnownContacts(0),
+ mGotPresenceStateChanged(false)
+ { }
+
+protected Q_SLOTS:
+ void expectBlockingContactsFinished(Tp::PendingOperation *op);
+ void expectBlockStatusChanged(bool blocked);
+ void expectBlockedContactsChanged(const Tp::Contacts &added, const Tp::Contacts &removed,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void expectPresenceStateChanged(Tp::Contact::PresenceState);
+ void expectAllKnownContactsChanged(const Tp::Contacts &added, const Tp::Contacts &removed,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRoster();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ QSet<QString> mContactsExpectingBlockStatusChange;
+ bool mBlockingContactsFinished;
+ int mHowManyKnownContacts;
+ bool mGotPresenceStateChanged;
+};
+
+void TestConnRosterLegacy::expectBlockingContactsFinished(Tp::PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ qDebug() << "blocking contacts finished";
+ mBlockingContactsFinished = true;
+
+ if (mContactsExpectingBlockStatusChange.isEmpty()) {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRosterLegacy::expectBlockStatusChanged(bool blocked)
+{
+ Q_UNUSED(blocked);
+
+ Contact *c = qobject_cast<Contact*>(sender());
+ QVERIFY(c);
+
+ ContactPtr contact(c);
+ mContactsExpectingBlockStatusChange.remove(contact->id());
+
+ if (mContactsExpectingBlockStatusChange.isEmpty() && mBlockingContactsFinished) {
+ mLoop->exit(0);
+ }
+}
+
+// This connects to allKnownContactsChanged() but it is only used in the last contact blocking test
+void TestConnRosterLegacy::expectBlockedContactsChanged(const Tp::Contacts &added,
+ const Tp::Contacts &removed, const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ Q_UNUSED(details);
+
+ Q_FOREACH(const ContactPtr &contact, added) {
+ mContactsExpectingBlockStatusChange.remove(contact->id());
+ }
+ Q_FOREACH(const ContactPtr &contact, removed) {
+ mContactsExpectingBlockStatusChange.remove(contact->id());
+ }
+
+ if (mContactsExpectingBlockStatusChange.isEmpty() && mBlockingContactsFinished) {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRosterLegacy::expectAllKnownContactsChanged(const Tp::Contacts& added, const Tp::Contacts& removed,
+ const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ qDebug() << added.size() << " contacts added, " << removed.size() << " contacts removed";
+ mHowManyKnownContacts += added.size();
+ mHowManyKnownContacts -= removed.size();
+
+ if (details.hasMessage()) {
+ QCOMPARE(details.message(), QLatin1String("add me now"));
+ }
+
+ if (mConn->client()->contactManager()->allKnownContacts().size() != mHowManyKnownContacts) {
+ qWarning() << "Contacts number mismatch! Watched value: " << mHowManyKnownContacts <<
+ "allKnownContacts(): " << mConn->client()->contactManager()->allKnownContacts().size();
+ mLoop->exit(1);
+ } else {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRosterLegacy::expectPresenceStateChanged(Contact::PresenceState state)
+{
+ mGotPresenceStateChanged = true;
+}
+
+void TestConnRosterLegacy::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-roster-legacy");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contactlist",
+ "simulation-delay", 1,
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestConnRosterLegacy::init()
+{
+ initImpl();
+}
+
+void TestConnRosterLegacy::testRoster()
+{
+ Features features = Features() << Connection::FeatureRoster;
+ QCOMPARE(mConn->enableFeatures(features), true);
+
+ ContactManagerPtr contactManager = mConn->client()->contactManager();
+
+ QCOMPARE(contactManager->state(), ContactListStateSuccess);
+
+ QStringList toCheck = QStringList() <<
+ QLatin1String("sjoerd@example.com") <<
+ QLatin1String("travis@example.com") <<
+ QLatin1String("wim@example.com") <<
+ QLatin1String("olivier@example.com") <<
+ QLatin1String("helen@example.com") <<
+ QLatin1String("geraldine@example.com") <<
+ QLatin1String("guillaume@example.com") <<
+ QLatin1String("christian@example.com") <<
+ QLatin1String("bill@example.com") <<
+ QLatin1String("steve@example.com");
+ QStringList ids;
+ QList<ContactPtr> pendingSubscription;
+ QList<ContactPtr> pendingPublish;
+ Q_FOREACH (const ContactPtr &contact, contactManager->allKnownContacts()) {
+ qDebug() << " contact:" << contact->id() <<
+ "- subscription:" << contact->subscriptionState() <<
+ "- publish:" << contact->publishState();
+ ids << contact->id();
+ if (contact->subscriptionState() == Contact::PresenceStateAsk) {
+ pendingSubscription.append(contact);
+ } else if (contact->publishState() == Contact::PresenceStateAsk) {
+ pendingPublish.append(contact);
+ }
+ }
+ ids.sort();
+ toCheck.sort();
+ QCOMPARE(ids, toCheck);
+ QCOMPARE(pendingSubscription.size(), 2);
+ QCOMPARE(pendingPublish.size(), 2);
+
+ // Wait for the contacts to be built
+ ids = QStringList() << QString(QLatin1String("john@example.com"))
+ << QString(QLatin1String("mary@example.com"));
+ QList<ContactPtr> contacts = mConn->contacts(ids);
+ QCOMPARE(contacts.size(), ids.size());
+
+ int i = 0;
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ mGotPresenceStateChanged = false;
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState))));
+ QVERIFY(connect(contact.data(),
+ SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState))));
+ if ((i % 2) == 0) {
+ contact->requestPresenceSubscription(QLatin1String("please add me"));
+ } else {
+ contact->requestPresenceSubscription(QLatin1String("add me now"));
+ }
+
+ while (!mGotPresenceStateChanged) {
+ mLoop->processEvents();
+ }
+
+ if ((i % 2) == 0) {
+ // I asked to see his presence - he might have already accepted it, though
+ QVERIFY(contact->subscriptionState() == Contact::PresenceStateAsk
+ || contact->subscriptionState() == Contact::PresenceStateYes);
+
+ // if he accepted it already, one iteration won't be enough as the
+ // first iteration will just flush the subscription -> Yes event
+ while (contact->publishState() != Contact::PresenceStateAsk) {
+ mLoop->processEvents();
+ }
+
+ contact->authorizePresencePublication();
+ while (contact->publishState() != Contact::PresenceStateYes) {
+ mLoop->processEvents();
+ }
+ // I authorized him to see my presence
+ QCOMPARE(static_cast<uint>(contact->publishState()),
+ static_cast<uint>(Contact::PresenceStateYes));
+ // He replied the presence request
+ QCOMPARE(static_cast<uint>(contact->subscriptionState()),
+ static_cast<uint>(Contact::PresenceStateYes));
+
+ contact->removePresenceSubscription();
+
+ while (contact->subscriptionState() != Contact::PresenceStateNo) {
+ mLoop->processEvents();
+ }
+ } else {
+ // I asked to see her presence - she might have already rejected it, though
+ QVERIFY(contact->subscriptionState() == Contact::PresenceStateAsk
+ || contact->subscriptionState() == Contact::PresenceStateNo);
+
+ // If she didn't already reject it, wait until she does
+ while (contact->subscriptionState() != Contact::PresenceStateNo) {
+ mLoop->processEvents();
+ }
+ }
+
+ ++i;
+
+ // Disconnect the signals so the contacts doing something won't early-exit future mainloop
+ // iterations (the simulation CM does things like - after a delay since we removed them, try
+ // to re-add us - and such, which mess up the test if the simulated network event happens
+ // before we've finished with the next contact)
+ QVERIFY(contact->disconnect(this));
+
+ // TODO: The roster API, frankly speaking, seems rather error/race prone, as evidenced by
+ // this test. Should we perhaps change its semantics? Then again, this test also simulates
+ // the remote user accepting/rejecting the request with a quite unpredictable timer delay,
+ // while real-world applications don't do any such assumptions about the timing of the
+ // remote user actions, so most of the problems won't be applicable there.
+ }
+
+ i = 0;
+ Contact::PresenceState expectedPresenceState;
+ Q_FOREACH (const ContactPtr &contact, pendingPublish) {
+ mGotPresenceStateChanged = false;
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState))));
+
+ if ((i++ % 2) == 0) {
+ expectedPresenceState = Contact::PresenceStateYes;
+ contact->authorizePresencePublication();
+ } else {
+ expectedPresenceState = Contact::PresenceStateNo;
+ contact->removePresencePublication();
+ }
+
+ while (!mGotPresenceStateChanged) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(static_cast<uint>(contact->publishState()),
+ static_cast<uint>(expectedPresenceState));
+ }
+
+ // Test allKnownContactsChanged.
+ // In this test, everytime a subscription is requested or rejected, allKnownContacts changes
+ // Cache the current value
+ mHowManyKnownContacts = contactManager->allKnownContacts().size();
+ // Watch for contacts changed
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectAllKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails))));
+
+ // Wait for the contacts to be built
+ ids = QStringList() << QString(QLatin1String("kctest1@example.com"))
+ << QString(QLatin1String("kctest2@example.com"));
+ contacts = mConn->contacts(ids);
+ QCOMPARE(contacts.size(), ids.size());
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ contact->requestPresenceSubscription(QLatin1String("add me now"));
+
+ // allKnownContacts is supposed to change here.
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+
+ QVERIFY(disconnect(contactManager.data(),
+ SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ this,
+ SLOT(expectAllKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails))));
+
+ // verify that the CM supports contact blocking
+ QVERIFY(contactManager->canBlockContacts());
+
+ // check if the initially blocked contacts are there
+ ids.clear();
+ toCheck = QStringList() <<
+ QLatin1String("bill@example.com") <<
+ QLatin1String("steve@example.com");
+ Q_FOREACH (const ContactPtr &contact, contactManager->allKnownContacts()) {
+ if (contact->isBlocked()) {
+ qDebug() << "blocked contact:" << contact->id();
+ ids << contact->id();
+ }
+ }
+ ids.sort();
+ toCheck.sort();
+ QCOMPARE(ids, toCheck);
+
+ // block all contacts
+ QList<ContactPtr> contactsList = contactManager->allKnownContacts().toList();
+ QSet<QString> contactIdsList;
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ QVERIFY(connect(contact.data(),
+ SIGNAL(blockStatusChanged(bool)),
+ SLOT(expectBlockStatusChanged(bool))));
+ contactIdsList.insert(contact->id());
+ }
+
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = contactIdsList;
+
+ // those are already blocked; do not expect their status to change
+ mContactsExpectingBlockStatusChange.remove(QLatin1String("bill@example.com"));
+ mContactsExpectingBlockStatusChange.remove(QLatin1String("steve@example.com"));
+
+ QVERIFY(connect(contactManager->blockContacts(contactsList),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // verify all contacts have been blocked
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ QCOMPARE(contact->isBlocked(), true);
+ QVERIFY(contactManager->allKnownContacts().contains(contact));
+ }
+
+ // now remove kctest1 from the server
+ ContactPtr kctest1;
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ if (contact->id() == QLatin1String("kctest1@example.com")) {
+ kctest1 = contact;
+ }
+ }
+ QVERIFY(!kctest1.isNull());
+ QVERIFY(connect(contactManager->removeContacts(QList<ContactPtr>() << kctest1),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ mLoop, SLOT(quit())));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // allKnownContacts must still contain kctest1, since it is in the deny list
+ QVERIFY(contactManager->allKnownContacts().contains(kctest1));
+ kctest1.reset(); //no longer needed
+
+ // unblock all contacts
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = contactIdsList;
+
+ QVERIFY(connect(contactManager->unblockContacts(contactsList),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // verify all contacts have been unblocked
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ QCOMPARE(contact->isBlocked(), false);
+
+ // ...and that bill, steve and kctest1 have also been removed from allKnownContacts()
+ // note: allKnownContacts() changes here because bill, steve and kctest,
+ // which were only in the deny list, do not exist in any other list, so
+ // they are removed as soon as they get unblocked.
+ if (contact->id() == QLatin1String("bill@example.com") ||
+ contact->id() == QLatin1String("steve@example.com") ||
+ contact->id() == QLatin1String("kctest1@example.com")) {
+ QVERIFY(!contactManager->allKnownContacts().contains(contact));
+ } else {
+ QVERIFY(contactManager->allKnownContacts().contains(contact));
+ }
+ }
+
+ // block some contacts that are not already known
+ ids = QStringList() <<
+ QLatin1String("blocktest1@example.com") <<
+ QLatin1String("blocktest2@example.com");
+ contacts = mConn->contacts(ids);
+
+ // Watch changes in allKnownContacts() instead of watching the Contacts' block status
+ // as we want to destroy the Contact objects and verify that they are being re-created correctly
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectBlockedContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails))));
+
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = ids.toSet();
+
+ QVERIFY(connect(contactManager->blockContacts(contacts),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+
+ // destroy the Contact objects to let them be re-created when the block operation finishes
+ contacts.clear();
+ QCOMPARE(mLoop->exec(), 0);
+
+ // construct the same contacts again and verify that they are blocked
+ contacts = mConn->contacts(ids);
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QCOMPARE(contact->isBlocked(), true);
+ QVERIFY(contactManager->allKnownContacts().contains(contact));
+ }
+
+ // now unblock them again
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = ids.toSet();
+
+ QVERIFY(connect(contactManager->unblockContacts(contacts),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+
+ // note: allKnownContacts() is expected to change again, so we expect
+ // to quit from expectBlockedContactsChanged()
+ QCOMPARE(mLoop->exec(), 0);
+
+ // and verify that they are not in allKnownContacts()
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QCOMPARE(contact->isBlocked(), false);
+ QVERIFY(!contactManager->allKnownContacts().contains(contact));
+ }
+}
+
+void TestConnRosterLegacy::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestConnRosterLegacy::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnRosterLegacy)
+#include "_gen/conn-roster-legacy.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/conn-roster.cpp b/qt4/tests/dbus/conn-roster.cpp
new file mode 100644
index 000000000..7440f5279
--- /dev/null
+++ b/qt4/tests/dbus/conn-roster.cpp
@@ -0,0 +1,496 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contactlist2/conn.h>
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestConnRoster : public Test
+{
+ Q_OBJECT
+
+public:
+ TestConnRoster(QObject *parent = 0)
+ : Test(parent), mConn(0),
+ mBlockingContactsFinished(false), mHowManyKnownContacts(0),
+ mGotPresenceStateChanged(false), mGotPPR(false)
+ { }
+
+protected Q_SLOTS:
+ void expectBlockingContactsFinished(Tp::PendingOperation *op);
+ void expectBlockStatusChanged(bool blocked);
+ void expectBlockedContactsChanged(const Tp::Contacts &added, const Tp::Contacts &removed,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+ void expectPresencePublicationRequested(const Tp::Contacts &);
+ void expectPresenceStateChanged(Tp::Contact::PresenceState);
+ void expectAllKnownContactsChanged(const Tp::Contacts &added, const Tp::Contacts &removed,
+ const Tp::Channel::GroupMemberChangeDetails &details);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRoster();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ QSet<QString> mContactsExpectingBlockStatusChange;
+ bool mBlockingContactsFinished;
+ int mHowManyKnownContacts;
+ bool mGotPresenceStateChanged;
+ bool mGotPPR;
+};
+
+void TestConnRoster::expectBlockingContactsFinished(Tp::PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ qDebug() << "blocking contacts finished";
+ mBlockingContactsFinished = true;
+
+ if (mContactsExpectingBlockStatusChange.isEmpty()) {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRoster::expectBlockStatusChanged(bool blocked)
+{
+ Q_UNUSED(blocked);
+
+ Contact *c = qobject_cast<Contact*>(sender());
+ QVERIFY(c);
+
+ ContactPtr contact(c);
+ mContactsExpectingBlockStatusChange.remove(contact->id());
+
+ if (mContactsExpectingBlockStatusChange.isEmpty() && mBlockingContactsFinished) {
+ mLoop->exit(0);
+ }
+}
+
+// This connects to allKnownContactsChanged() but it is only used in the last contact blocking test
+void TestConnRoster::expectBlockedContactsChanged(const Tp::Contacts &added,
+ const Tp::Contacts &removed, const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ Q_UNUSED(details);
+
+ Q_FOREACH(const ContactPtr &contact, added) {
+ mContactsExpectingBlockStatusChange.remove(contact->id());
+ }
+ Q_FOREACH(const ContactPtr &contact, removed) {
+ mContactsExpectingBlockStatusChange.remove(contact->id());
+ }
+
+ if (mContactsExpectingBlockStatusChange.isEmpty() && mBlockingContactsFinished) {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRoster::expectAllKnownContactsChanged(const Tp::Contacts& added, const Tp::Contacts& removed,
+ const Tp::Channel::GroupMemberChangeDetails &details)
+{
+ qDebug() << added.size() << " contacts added, " << removed.size() << " contacts removed";
+ mHowManyKnownContacts += added.size();
+ mHowManyKnownContacts -= removed.size();
+
+ if (details.hasMessage()) {
+ QCOMPARE(details.message(), QLatin1String("add me now"));
+ }
+
+ if (mConn->client()->contactManager()->allKnownContacts().size() != mHowManyKnownContacts) {
+ qWarning() << "Contacts number mismatch! Watched value: " << mHowManyKnownContacts <<
+ "allKnownContacts(): " << mConn->client()->contactManager()->allKnownContacts().size();
+ mLoop->exit(1);
+ } else {
+ mLoop->exit(0);
+ }
+}
+
+void TestConnRoster::expectPresencePublicationRequested(const Tp::Contacts &contacts)
+{
+ Q_FOREACH(Tp::ContactPtr contact, contacts) {
+ QCOMPARE(static_cast<uint>(contact->publishState()),
+ static_cast<uint>(Contact::PresenceStateAsk));
+ }
+
+ mGotPPR = true;
+}
+
+void TestConnRoster::expectPresenceStateChanged(Contact::PresenceState state)
+{
+ mGotPresenceStateChanged = true;
+}
+
+void TestConnRoster::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("conn-roster");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create(Contact::FeatureAlias),
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "contactlist",
+ "simulation-delay", 1,
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestConnRoster::init()
+{
+ initImpl();
+}
+
+void TestConnRoster::testRoster()
+{
+ Features features = Features() << Connection::FeatureRoster;
+ QCOMPARE(mConn->enableFeatures(features), true);
+
+ ContactManagerPtr contactManager = mConn->client()->contactManager();
+
+ QCOMPARE(contactManager->state(), ContactListStateSuccess);
+
+ QStringList toCheck = QStringList() <<
+ QLatin1String("sjoerd@example.com") <<
+ QLatin1String("travis@example.com") <<
+ QLatin1String("wim@example.com") <<
+ QLatin1String("olivier@example.com") <<
+ QLatin1String("helen@example.com") <<
+ QLatin1String("geraldine@example.com") <<
+ QLatin1String("guillaume@example.com") <<
+ QLatin1String("christian@example.com") <<
+ QLatin1String("bill@example.com") <<
+ QLatin1String("steve@example.com");
+ QStringList ids;
+ QList<ContactPtr> pendingSubscription;
+ QList<ContactPtr> pendingPublish;
+ Q_FOREACH (const ContactPtr &contact, contactManager->allKnownContacts()) {
+ QVERIFY(contact->requestedFeatures().contains(Contact::FeatureAlias));
+ qDebug() << " contact:" << contact->id() <<
+ "- subscription:" << contact->subscriptionState() <<
+ "- publish:" << contact->publishState();
+ ids << contact->id();
+ if (contact->subscriptionState() == Contact::PresenceStateAsk) {
+ pendingSubscription.append(contact);
+ } else if (contact->publishState() == Contact::PresenceStateAsk) {
+ pendingPublish.append(contact);
+ }
+ }
+ ids.sort();
+ toCheck.sort();
+ QCOMPARE(ids, toCheck);
+ QCOMPARE(pendingSubscription.size(), 2);
+ QCOMPARE(pendingPublish.size(), 2);
+
+ // Wait for the contacts to be built
+ ids = QStringList() << QString(QLatin1String("john@example.com"))
+ << QString(QLatin1String("mary@example.com"));
+ QList<ContactPtr> contacts = mConn->contacts(ids);
+ QCOMPARE(contacts.size(), ids.size());
+
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(presencePublicationRequested(Tp::Contacts,QString)),
+ SLOT(expectPresencePublicationRequested(Tp::Contacts))));
+ int i = 0;
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ mGotPresenceStateChanged = false;
+ mGotPPR = false;
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState))));
+ QVERIFY(connect(contact.data(),
+ SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState))));
+ if ((i % 2) == 0) {
+ contact->requestPresenceSubscription(QLatin1String("please add me"));
+ } else {
+ contact->requestPresenceSubscription(QLatin1String("add me now"));
+ }
+
+ while (!mGotPresenceStateChanged && !mGotPPR) {
+ mLoop->processEvents();
+ }
+
+ if ((i % 2) == 0) {
+ // I asked to see his presence - he might have already accepted it, though
+ QVERIFY(contact->subscriptionState() == Contact::PresenceStateAsk
+ || contact->subscriptionState() == Contact::PresenceStateYes);
+
+ // if he accepted it already, one iteration won't be enough as the
+ // first iteration will just flush the subscription -> Yes event
+ while (contact->publishState() != Contact::PresenceStateAsk) {
+ mLoop->processEvents();
+ }
+
+ contact->authorizePresencePublication();
+ while (contact->publishState() != Contact::PresenceStateYes) {
+ mLoop->processEvents();
+ }
+ // I authorized him to see my presence
+ QCOMPARE(static_cast<uint>(contact->publishState()),
+ static_cast<uint>(Contact::PresenceStateYes));
+ // He replied the presence request
+ QCOMPARE(static_cast<uint>(contact->subscriptionState()),
+ static_cast<uint>(Contact::PresenceStateYes));
+
+ contact->removePresenceSubscription();
+
+ while (contact->subscriptionState() != Contact::PresenceStateNo) {
+ mLoop->processEvents();
+ }
+ } else {
+ // I asked to see her presence - she might have already rejected it, though
+ QVERIFY(contact->subscriptionState() == Contact::PresenceStateAsk
+ || contact->subscriptionState() == Contact::PresenceStateNo);
+
+ // If she didn't already reject it, wait until she does
+ while (contact->subscriptionState() != Contact::PresenceStateNo) {
+ mLoop->processEvents();
+ }
+ }
+
+ ++i;
+
+ // Disconnect the signals so the contacts doing something won't early-exit future mainloop
+ // iterations (the simulation CM does things like - after a delay since we removed them, try
+ // to re-add us - and such, which mess up the test if the simulated network event happens
+ // before we've finished with the next contact)
+ QVERIFY(contact->disconnect(this));
+
+ // TODO: The roster API, frankly speaking, seems rather error/race prone, as evidenced by
+ // this test. Should we perhaps change its semantics? Then again, this test also simulates
+ // the remote user accepting/rejecting the request with a quite unpredictable timer delay,
+ // while real-world applications don't do any such assumptions about the timing of the
+ // remote user actions, so most of the problems won't be applicable there.
+ }
+
+ i = 0;
+ Contact::PresenceState expectedPresenceState;
+ Q_FOREACH (const ContactPtr &contact, pendingPublish) {
+ mGotPresenceStateChanged = false;
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectPresenceStateChanged(Tp::Contact::PresenceState))));
+
+ if ((i++ % 2) == 0) {
+ expectedPresenceState = Contact::PresenceStateYes;
+ contact->authorizePresencePublication();
+ } else {
+ expectedPresenceState = Contact::PresenceStateNo;
+ contact->removePresencePublication();
+ }
+
+ while (!mGotPresenceStateChanged) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(static_cast<uint>(contact->publishState()),
+ static_cast<uint>(expectedPresenceState));
+ }
+
+ // Test allKnownContactsChanged.
+ // In this test, everytime a subscription is requested or rejected, allKnownContacts changes
+ // Cache the current value
+ mHowManyKnownContacts = contactManager->allKnownContacts().size();
+ // Watch for contacts changed
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectAllKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails))));
+
+ // Wait for the contacts to be built
+ ids = QStringList() << QString(QLatin1String("kctest1@example.com"))
+ << QString(QLatin1String("kctest2@example.com"));
+ contacts = mConn->contacts(ids);
+ QCOMPARE(contacts.size(), ids.size());
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ contact->requestPresenceSubscription(QLatin1String("add me now"));
+
+ // allKnownContacts is supposed to change here.
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+
+ QVERIFY(disconnect(contactManager.data(),
+ SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ this,
+ SLOT(expectAllKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails))));
+
+ // verify that the CM supports contact blocking
+ QVERIFY(contactManager->canBlockContacts());
+
+ // check if the initially blocked contacts are there
+ ids.clear();
+ toCheck = QStringList() <<
+ QLatin1String("bill@example.com") <<
+ QLatin1String("steve@example.com");
+ Q_FOREACH (const ContactPtr &contact, contactManager->allKnownContacts()) {
+ if (contact->isBlocked()) {
+ qDebug() << "blocked contact:" << contact->id();
+ ids << contact->id();
+ }
+ }
+ ids.sort();
+ toCheck.sort();
+ QCOMPARE(ids, toCheck);
+
+ // block all contacts
+ QList<ContactPtr> contactsList = contactManager->allKnownContacts().toList();
+ QSet<QString> contactIdsList;
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ QVERIFY(connect(contact.data(),
+ SIGNAL(blockStatusChanged(bool)),
+ SLOT(expectBlockStatusChanged(bool))));
+ contactIdsList.insert(contact->id());
+ }
+
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = contactIdsList;
+
+ // those are already blocked; do not expect their status to change
+ mContactsExpectingBlockStatusChange.remove(QLatin1String("bill@example.com"));
+ mContactsExpectingBlockStatusChange.remove(QLatin1String("steve@example.com"));
+
+ QVERIFY(connect(contactManager->blockContacts(contactsList),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // verify all contacts have been blocked
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ QCOMPARE(contact->isBlocked(), true);
+ QVERIFY(contactManager->allKnownContacts().contains(contact));
+ }
+
+ // now remove kctest1 from the server
+ ContactPtr kctest1;
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ if (contact->id() == QLatin1String("kctest1@example.com")) {
+ kctest1 = contact;
+ }
+ }
+ QVERIFY(!kctest1.isNull());
+ QVERIFY(connect(contactManager->removeContacts(QList<ContactPtr>() << kctest1),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ mLoop, SLOT(quit())));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // allKnownContacts must still contain kctest1, since it is in the deny list
+ QVERIFY(contactManager->allKnownContacts().contains(kctest1));
+ kctest1.reset(); //no longer needed
+
+ // unblock all contacts
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = contactIdsList;
+
+ QVERIFY(connect(contactManager->unblockContacts(contactsList),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // verify all contacts have been unblocked
+ Q_FOREACH (const ContactPtr &contact, contactsList) {
+ QCOMPARE(contact->isBlocked(), false);
+
+ // ...and that bill, steve and kctest1 have also been removed from allKnownContacts()
+ // note: allKnownContacts() changes here because bill, steve and kctest,
+ // which were only in the deny list, do not exist in any other list, so
+ // they are removed as soon as they get unblocked.
+ if (contact->id() == QLatin1String("bill@example.com") ||
+ contact->id() == QLatin1String("steve@example.com") ||
+ contact->id() == QLatin1String("kctest1@example.com")) {
+ QVERIFY(!contactManager->allKnownContacts().contains(contact));
+ } else {
+ QVERIFY(contactManager->allKnownContacts().contains(contact));
+ }
+ }
+
+ // block some contacts that are not already known
+ ids = QStringList() <<
+ QLatin1String("blocktest1@example.com") <<
+ QLatin1String("blocktest2@example.com");
+ contacts = mConn->contacts(ids);
+
+ // Watch changes in allKnownContacts() instead of watching the Contacts' block status
+ // as we want to destroy the Contact objects and verify that they are being re-created correctly
+ QVERIFY(connect(contactManager.data(),
+ SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails)),
+ SLOT(expectBlockedContactsChanged(Tp::Contacts,Tp::Contacts,
+ Tp::Channel::GroupMemberChangeDetails))));
+
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = ids.toSet();
+
+ QVERIFY(connect(contactManager->blockContacts(contacts),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+
+ // destroy the Contact objects to let them be re-created when the block operation finishes
+ contacts.clear();
+ QCOMPARE(mLoop->exec(), 0);
+
+ // construct the same contacts again and verify that they are blocked
+ contacts = mConn->contacts(ids);
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QCOMPARE(contact->isBlocked(), true);
+ QVERIFY(contactManager->allKnownContacts().contains(contact));
+ }
+
+ // now unblock them again
+ mBlockingContactsFinished = false;
+ mContactsExpectingBlockStatusChange = ids.toSet();
+
+ QVERIFY(connect(contactManager->unblockContacts(contacts),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBlockingContactsFinished(Tp::PendingOperation*))));
+
+ // note: allKnownContacts() is expected to change again, so we expect
+ // to quit from expectBlockedContactsChanged()
+ QCOMPARE(mLoop->exec(), 0);
+
+ // and verify that they are not in allKnownContacts()
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QCOMPARE(contact->isBlocked(), false);
+ QVERIFY(!contactManager->allKnownContacts().contains(contact));
+ }
+}
+
+void TestConnRoster::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestConnRoster::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestConnRoster)
+#include "_gen/conn-roster.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contact-factory.cpp b/qt4/tests/dbus/contact-factory.cpp
new file mode 100644
index 000000000..d7c566374
--- /dev/null
+++ b/qt4/tests/dbus/contact-factory.cpp
@@ -0,0 +1,87 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactFactory>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestContactFactory : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactFactory(QObject *parent = 0)
+ : Test(parent), mConn(0)
+ { }
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testConnectionSelfContactFeatures();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+};
+
+void TestContactFactory::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contact-factory");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create(Contact::FeatureAlias),
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL);
+ QCOMPARE(mConn->isReady(), false);
+}
+
+void TestContactFactory::init()
+{
+ initImpl();
+}
+
+void TestContactFactory::testConnectionSelfContactFeatures()
+{
+ QCOMPARE(mConn->client()->contactFactory()->features().size(), 1);
+ QVERIFY(mConn->client()->contactFactory()->features().contains(Contact::FeatureAlias));
+
+ QCOMPARE(mConn->connect(Connection::FeatureSelfContact), true);
+
+ ContactPtr selfContact = mConn->client()->selfContact();
+ QVERIFY(!selfContact.isNull());
+ QVERIFY(selfContact->requestedFeatures().contains(Contact::FeatureAlias));
+}
+
+void TestContactFactory::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContactFactory::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContactFactory)
+#include "_gen/contact-factory.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contact-messenger.cpp b/qt4/tests/dbus/contact-messenger.cpp
new file mode 100644
index 000000000..eafab493f
--- /dev/null
+++ b/qt4/tests/dbus/contact-messenger.cpp
@@ -0,0 +1,685 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtDBus/QtDBus>
+#include <QtTest/QtTest>
+
+#include <QDateTime>
+#include <QString>
+#include <QVariantMap>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/Client>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/ContactMessenger>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/MessageContentPart>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingSendMessage>
+#include <TelepathyQt4/TextChannel>
+#include <TelepathyQt4/Types>
+
+#include <telepathy-glib/cm-message.h>
+#include <telepathy-glib/debug.h>
+
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/glib/echo2/chan.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+class TestContactMessenger;
+
+class CDMessagesAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelDispatcher.Interface.Messages.DRAFT")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelDispatcher.Interface.Messages.DRAFT\" >\n"
+" <method name=\"SendMessage\" >\n"
+" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n"
+" <arg name=\"TargetID\" type=\"s\" direction=\"in\" />\n"
+" <arg name=\"Message\" type=\"aa{sv}\" direction=\"in\" />\n"
+" <arg name=\"Flags\" type=\"u\" direction=\"in\" />\n"
+" <arg name=\"Token\" type=\"s\" direction=\"out\" />\n"
+" </method>\n"
+" </interface>\n"
+ "")
+
+public:
+ CDMessagesAdaptor(const QDBusConnection &bus, TestContactMessenger *test, QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ test(test),
+ mBus(bus)
+ {
+ }
+
+
+ virtual ~CDMessagesAdaptor()
+ {
+ }
+
+ void setSimulatedSendError(const QString &error)
+ {
+ mSimulatedSendError = error;
+ }
+
+public Q_SLOTS: // Methods
+ QString SendMessage(const QDBusObjectPath &account,
+ const QString &targetID, const Tp::MessagePartList &message,
+ uint flags);
+
+private:
+
+ TestContactMessenger *test;
+ QDBusConnection mBus;
+ QString mSimulatedSendError;
+};
+
+class AccountAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Account")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Account\" >\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
+" <signal name=\"AccountPropertyChanged\" >\n"
+" <arg name=\"Properties\" type=\"a{sv}\" />\n"
+" </signal>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Connection READ Connection)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+
+public:
+ AccountAdaptor(QObject *parent)
+ : QDBusAbstractAdaptor(parent), mConnection(QLatin1String("/"))
+ {
+ }
+
+ virtual ~AccountAdaptor()
+ {
+ }
+
+ void setConnection(QString conn)
+ {
+ if (conn.isEmpty()) {
+ conn = QLatin1String("/");
+ }
+
+ mConnection = QDBusObjectPath(conn);
+ QVariantMap props;
+ props.insert(QLatin1String("Connection"), QVariant::fromValue(mConnection));
+ Q_EMIT AccountPropertyChanged(props);
+ }
+
+public: // Properties
+ inline QDBusObjectPath Connection() const
+ {
+ return mConnection;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return QStringList();
+ }
+
+Q_SIGNALS: // Signals
+ void AccountPropertyChanged(const QVariantMap &properties);
+
+private:
+ QDBusObjectPath mConnection;
+};
+
+class Dispatcher : public QObject, public QDBusContext
+{
+ Q_OBJECT;
+
+public:
+ Dispatcher(QObject *parent)
+ : QObject(parent)
+ {
+ }
+
+ ~Dispatcher()
+ {
+ }
+};
+
+class TestContactMessenger : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactMessenger(QObject *parent = 0)
+ : Test(parent),
+ mCDMessagesAdaptor(0), mAccountAdaptor(0),
+ // service side (telepathy-glib)
+ mConnService(0), mBaseConnService(0), mContactRepo(0),
+ mSendFinished(false), mGotMessageSent(false)
+ { }
+
+protected Q_SLOTS:
+ void expectPendingContactsFinished(Tp::PendingOperation *op);
+ void onSendFinished(Tp::PendingOperation *);
+ void onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken, const Tp::TextChannelPtr &channel);
+ void onMessageReceived(const Tp::ReceivedMessage &message,
+ const Tp::TextChannelPtr &channel);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testNoSupport();
+ void testObserverRegistration();
+ void testSimpleSend();
+ void testReceived();
+ void testReceivedFromContact();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+
+ friend class CDMessagesAdaptor;
+
+ QList<ClientObserverInterface *> ourObservers();
+
+ CDMessagesAdaptor *mCDMessagesAdaptor;
+ AccountAdaptor *mAccountAdaptor;
+ QString mAccountBusName, mAccountPath;
+
+ AccountManagerPtr mAM;
+ AccountPtr mAccount;
+ ConnectionPtr mConn;
+ TextChannelPtr mChan;
+
+ TpTestsContactsConnection *mConnService;
+ TpBaseConnection *mBaseConnService;
+ TpHandleRepoIface *mContactRepo;
+ ExampleEcho2Channel *mMessagesChanService;
+
+ QString mConnName;
+ QString mConnPath;
+ QString mMessagesChanPath;
+
+ bool mSendFinished, mGotMessageSent, mGotMessageReceived;
+ QString mSendError, mSendToken, mMessageSentText, mMessageSentToken, mMessageSentChannel;
+ QString mMessageReceivedText;
+ ChannelPtr mMessageReceivedChan;
+
+ QList<ContactPtr> mContacts;
+};
+
+QString CDMessagesAdaptor::SendMessage(const QDBusObjectPath &account,
+ const QString &targetID, const MessagePartList &message,
+ uint flags)
+{
+ if (!mSimulatedSendError.isEmpty()) {
+ dynamic_cast<QDBusContext *>(QObject::parent())->sendErrorReply(mSimulatedSendError,
+ QLatin1String("Let's pretend this interface and method don't exist, shall we?"));
+ return QString();
+ }
+
+ /*
+ * Sadly, the QDBus local-loop "optimization" prevents us from correctly waiting for the
+ * ObserveChannels call to return, and consequently prevents us from knowing when we can call
+ * Send, knowing that the observer has connected to the message sent signal.
+ *
+ * The real MC doesn't have this limitation because it actually really calls and waits our
+ * ObserveChannels method to finish, unlike dear QDBus here.
+ */
+ QList<ClientObserverInterface *> observers = test->ourObservers();
+ Q_FOREACH(ClientObserverInterface *iface, observers) {
+ ChannelDetails chan = { QDBusObjectPath(test->mChan->objectPath()), test->mChan->immutableProperties() };
+ QDBusPendingCallWatcher *watcher =
+ new QDBusPendingCallWatcher(iface->ObserveChannels(
+ QDBusObjectPath(test->mAccount->objectPath()),
+ QDBusObjectPath(test->mChan->connection()->objectPath()),
+ ChannelDetailsList() << chan,
+ QDBusObjectPath(QLatin1String("/")),
+ Tp::ObjectPathList(),
+ QVariantMap()));
+
+ connect(watcher,
+ SIGNAL(finished(QDBusPendingCallWatcher*)),
+ test->mLoop,
+ SLOT(quit()));
+ test->mLoop->exec();
+ QDBusPendingReply<void> reply = *watcher;
+ qDebug() << reply.error(); // Always gives out "local-loop messages can't have delayed replies"
+
+ delete watcher;
+ }
+
+ qDebug() << "Calling send"; // And this is always called before the observer manages to connect to messageSent. Bummer.
+
+ PendingSendMessage *msg = test->mChan->send(message, static_cast<MessageSendingFlags>(flags));
+ connect(msg,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ test,
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*)));
+ test->mLoop->exec();
+ return msg->sentMessageToken();
+}
+
+void TestContactMessenger::expectPendingContactsFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingContacts *pending = qobject_cast<PendingContacts *>(op);
+ mContacts = pending->contacts();
+ mLoop->exit(0);
+}
+
+void TestContactMessenger::onSendFinished(Tp::PendingOperation *op)
+{
+ PendingSendMessage *msg = qobject_cast<PendingSendMessage *>(op);
+ QVERIFY(msg != NULL);
+
+ if (msg->isValid()) {
+ qDebug() << "Send succeeded, got token" << msg->sentMessageToken();
+ mSendToken = msg->sentMessageToken();
+ } else {
+ qDebug() << "Send failed, got error" << msg->errorName();
+ mSendError = msg->errorName();
+ }
+
+ mSendFinished = true;
+}
+
+void TestContactMessenger::onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags,
+ const QString &sentMessageToken, const Tp::TextChannelPtr &channel)
+{
+ qDebug() << "Got ContactMessenger::messageSent()";
+
+ mGotMessageSent = true;
+ mMessageSentToken = sentMessageToken;
+ mMessageSentText = message.text();
+}
+
+void TestContactMessenger::onMessageReceived(const Tp::ReceivedMessage &message,
+ const Tp::TextChannelPtr &channel)
+{
+ qDebug() << "Got ContactMessenger::messageReceived()";
+
+ mGotMessageReceived = true;
+ mMessageReceivedText = message.text();
+ mMessageReceivedChan = channel;
+}
+
+void TestContactMessenger::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contact-messenger");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QString channelDispatcherBusName = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER);
+ QString channelDispatcherPath = QLatin1String("/org/freedesktop/Telepathy/ChannelDispatcher");
+ Dispatcher *dispatcher = new Dispatcher(this);
+ mCDMessagesAdaptor = new CDMessagesAdaptor(bus, this, dispatcher);
+ QVERIFY(bus.registerService(channelDispatcherBusName));
+ QVERIFY(bus.registerObject(channelDispatcherPath, dispatcher));
+
+ mAccountBusName = QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_MANAGER);
+ mAccountPath = QLatin1String("/org/freedesktop/Telepathy/Account/simple/simple/account");
+ QObject *acc = new QObject(this);
+
+ mAccountAdaptor = new AccountAdaptor(acc);
+
+ QVERIFY(bus.registerService(mAccountBusName));
+ QVERIFY(bus.registerObject(mAccountPath, acc));
+
+ mAccount = Account::create(mAccountBusName, mAccountPath);
+ QVERIFY(connect(mAccount->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAccount->isReady(), true);
+
+ QCOMPARE(mAccount->supportsRequestHints(), false);
+ QCOMPARE(mAccount->requestsSucceedWithChannel(), false);
+
+ mConnService = TP_TESTS_CONTACTS_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL));
+ QVERIFY(mConnService != 0);
+ mBaseConnService = TP_BASE_CONNECTION(mConnService);
+ QVERIFY(mBaseConnService != 0);
+
+ gchar *name, *connPath;
+ GError *error = NULL;
+
+ QVERIFY(tp_base_connection_register(mBaseConnService,
+ "example", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName = QLatin1String(name);
+ mConnPath = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+
+ mAccountAdaptor->setConnection(mConnPath);
+
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ QVERIFY(connect(mConn->lowlevel()->requestConnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(), true);
+ QCOMPARE(static_cast<uint>(mConn->status()),
+ static_cast<uint>(ConnectionStatusConnected));
+
+ mContactRepo = tp_base_connection_get_handles(mBaseConnService,
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(mContactRepo, "Ann", 0, 0);
+
+ mMessagesChanPath = mConnPath + QLatin1String("/MessagesChannel");
+ QByteArray chanPath = mMessagesChanPath.toAscii();
+ mMessagesChanService = EXAMPLE_ECHO_2_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_2_CHANNEL,
+ "connection", mConnService,
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ QVariantMap immutableProperties;
+ immutableProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetID"),
+ QLatin1String("ann"));
+ mChan = TextChannel::create(mConn, mMessagesChanPath, immutableProperties);
+ QVERIFY(connect(mChan->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ tp_handle_unref(mContactRepo, handle);
+}
+
+void TestContactMessenger::init()
+{
+ initImpl();
+
+ mSendFinished = false;
+ mGotMessageSent = false;
+ mGotMessageReceived = false;
+ mCDMessagesAdaptor->setSimulatedSendError(QString());
+}
+
+void TestContactMessenger::testNoSupport()
+{
+ // We should give a descriptive error message if the CD doesn't actually support sending
+ // messages using the new API. NotImplemented should probably be documented for the
+ // sendMessage() methods as an indication that the CD implementation needs to be upgraded.
+
+ ContactMessengerPtr messenger = ContactMessenger::create(mAccount, QLatin1String("Ann"));
+ QVERIFY(!messenger.isNull());
+
+ mCDMessagesAdaptor->setSimulatedSendError(TP_QT4_DBUS_ERROR_UNKNOWN_METHOD);
+
+ PendingSendMessage *pendingSend = messenger->sendMessage(QLatin1String("Hi!"));
+ QVERIFY(pendingSend != NULL);
+
+ QVERIFY(connect(pendingSend,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pendingSend->isFinished());
+ QVERIFY(!pendingSend->isValid());
+
+ QCOMPARE(pendingSend->errorName(), TP_QT4_ERROR_NOT_IMPLEMENTED);
+
+ // Let's try using the other sendMessage overload similarly as well
+
+ Message m(ChannelTextMessageTypeAction, QLatin1String("is testing!"));
+
+ pendingSend = messenger->sendMessage(m.parts());
+ QVERIFY(pendingSend != NULL);
+
+ QVERIFY(connect(pendingSend,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pendingSend->isFinished());
+ QVERIFY(!pendingSend->isValid());
+
+ QCOMPARE(pendingSend->errorName(), TP_QT4_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestContactMessenger::testObserverRegistration()
+{
+ ContactMessengerPtr messenger = ContactMessenger::create(mAccount, QLatin1String("Ann"));
+
+ // At this point, there should be a registered observer for the relevant channel class on our
+ // unique name
+
+ QList<ClientObserverInterface *> observers = ourObservers();
+ QVERIFY(!observers.empty());
+
+ Q_FOREACH(ClientObserverInterface *observer, observers) {
+ // It shouldn't have recover == true, as it shouldn't be activatable at all, and hence recovery
+ // doesn't make sense for it
+ bool recover;
+ QVERIFY(waitForProperty(observer->requestPropertyRecover(), &recover));
+ QCOMPARE(recover, true);
+ }
+
+ // If we destroy our messenger (which is the last/only one for that ID), the observers should go
+ // away, at least in a few mainloop iterations
+ messenger.reset();
+
+ QVERIFY(ourObservers().empty());
+}
+
+void TestContactMessenger::testSimpleSend()
+{
+ ContactMessengerPtr messenger = ContactMessenger::create(mAccount, QLatin1String("Ann"));
+
+ QVERIFY(connect(messenger->sendMessage(QLatin1String("Hi!")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onSendFinished(Tp::PendingOperation*))));
+
+ while (!mSendFinished) {
+ mLoop->processEvents();
+ }
+
+ QVERIFY(mSendError.isEmpty());
+}
+
+void TestContactMessenger::testReceived()
+{
+ ContactMessengerPtr messenger = ContactMessenger::create(mAccount, QLatin1String("Ann"));
+
+ QVERIFY(connect(messenger.data(),
+ SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)),
+ SLOT(onMessageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr))));
+
+ QList<ClientObserverInterface *> observers = ourObservers();
+ Q_FOREACH(ClientObserverInterface *iface, observers) {
+ ChannelDetails chan = { QDBusObjectPath(mChan->objectPath()), mChan->immutableProperties() };
+ iface->ObserveChannels(
+ QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mChan->connection()->objectPath()),
+ ChannelDetailsList() << chan,
+ QDBusObjectPath(QLatin1String("/")),
+ Tp::ObjectPathList(),
+ QVariantMap());
+ }
+
+ guint handle = tp_handle_ensure(mContactRepo, "Ann", 0, 0);
+ TpMessage *msg = tp_cm_message_new_text(mBaseConnService, handle, TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, "Hi!");
+
+ tp_message_mixin_take_received(G_OBJECT(mMessagesChanService), msg);
+
+ tp_handle_unref(mContactRepo, handle);
+
+ while (!mGotMessageReceived) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mMessageReceivedText, QString::fromLatin1("Hi!"));
+ QCOMPARE(mMessageReceivedChan->objectPath(), mChan->objectPath());
+}
+
+void TestContactMessenger::testReceivedFromContact()
+{
+ QVERIFY(connect(mAccount->connection()->contactManager()->contactsForIdentifiers(
+ QStringList() << QLatin1String("Ann")),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ ContactPtr ann = mContacts.first();
+ ContactMessengerPtr messenger = ContactMessenger::create(mAccount, ann);
+
+ QVERIFY(connect(messenger.data(),
+ SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)),
+ SLOT(onMessageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr))));
+
+ QList<ClientObserverInterface *> observers = ourObservers();
+ Q_FOREACH(ClientObserverInterface *iface, observers) {
+ ChannelDetails chan = { QDBusObjectPath(mChan->objectPath()), mChan->immutableProperties() };
+ iface->ObserveChannels(
+ QDBusObjectPath(mAccount->objectPath()),
+ QDBusObjectPath(mChan->connection()->objectPath()),
+ ChannelDetailsList() << chan,
+ QDBusObjectPath(QLatin1String("/")),
+ Tp::ObjectPathList(),
+ QVariantMap());
+ }
+
+ guint handle = tp_handle_ensure(mContactRepo, "Ann", 0, 0);
+ TpMessage *msg = tp_cm_message_new_text(mBaseConnService, handle, TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, "Hi!");
+
+ tp_message_mixin_take_received(G_OBJECT(mMessagesChanService), msg);
+
+ tp_handle_unref(mContactRepo, handle);
+
+ while (!mGotMessageReceived) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mMessageReceivedText, QString::fromLatin1("Hi!"));
+ QCOMPARE(mMessageReceivedChan->objectPath(), mChan->objectPath());
+}
+
+
+void TestContactMessenger::cleanup()
+{
+ mMessageReceivedChan.reset();
+
+ cleanupImpl();
+}
+
+void TestContactMessenger::cleanupTestCase()
+{
+ if (mConn) {
+ // Disconnect and wait for the readiness change
+ QVERIFY(connect(mConn->lowlevel()->requestDisconnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ if (mConn->isValid()) {
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ mLoop,
+ SLOT(quit())));
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ }
+
+ mChan.reset();
+
+ if (mMessagesChanService != 0) {
+ g_object_unref(mMessagesChanService);
+ mMessagesChanService = 0;
+ }
+
+ if (mConnService != 0) {
+ mBaseConnService = 0;
+ g_object_unref(mConnService);
+ mConnService = 0;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QList<ClientObserverInterface *> TestContactMessenger::ourObservers()
+{
+ QStringList registeredNames =
+ QDBusConnection::sessionBus().interface()->registeredServiceNames();
+ QList<ClientObserverInterface *> observers;
+
+ Q_FOREACH (QString name, registeredNames) {
+ if (!name.startsWith(QLatin1String("org.freedesktop.Telepathy.Client."))) {
+ continue;
+ }
+
+ if (QDBusConnection::sessionBus().interface()->serviceOwner(name).value() !=
+ QDBusConnection::sessionBus().baseService()) {
+ continue;
+ }
+
+ QString path = QLatin1Char('/') + name;
+ path.replace(QLatin1Char('.'), QLatin1Char('/'));
+
+ ClientInterface client(name, path);
+ QStringList ifaces;
+ if (!waitForProperty(client.requestPropertyInterfaces(), &ifaces)) {
+ continue;
+ }
+
+ if (!ifaces.contains(TP_QT4_IFACE_CLIENT_OBSERVER)) {
+ continue;
+ }
+
+ ClientObserverInterface *observer = new ClientObserverInterface(name, path, this);
+
+ ChannelClassList filter;
+ if (!waitForProperty(observer->requestPropertyObserverChannelFilter(), &filter)) {
+ continue;
+ }
+
+ Q_FOREACH (ChannelClassSpec spec, filter) {
+ if (spec.isSubsetOf(ChannelClassSpec::textChat())) {
+ observers.push_back(observer);
+ qDebug() << "Found our observer" << name << '\n';
+ break;
+ }
+ }
+ }
+
+ return observers;
+}
+
+QTEST_MAIN(TestContactMessenger)
+#include "_gen/contact-messenger.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contact-search-chan.cpp b/qt4/tests/dbus/contact-search-chan.cpp
new file mode 100644
index 000000000..6987bce4e
--- /dev/null
+++ b/qt4/tests/dbus/contact-search-chan.cpp
@@ -0,0 +1,310 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/echo/conn.h>
+#include <tests/lib/glib/contact-search-chan.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ContactSearchChannel>
+#include <TelepathyQt4/PendingReady>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestContactSearchChan : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactSearchChan(QObject *parent = 0)
+ : Test(parent),
+ mConn(0),
+ mChan1Service(0), mChan2Service(0), mSearchReturned(false)
+ { }
+
+protected Q_SLOTS:
+ void onSearchStateChanged(Tp::ChannelContactSearchState state, const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details);
+ void onSearchResultReceived(const Tp::ContactSearchChannel::SearchResult &result);
+ void onSearchReturned(Tp::PendingOperation *op);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testContactSearch();
+ void testContactSearchEmptyResult();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ TpHandleRepoIface *mContactRepo;
+
+ ContactSearchChannelPtr mChan;
+ ContactSearchChannelPtr mChan1;
+ ContactSearchChannelPtr mChan2;
+
+ QString mChan1Path;
+ TpTestsContactSearchChannel *mChan1Service;
+ QString mChan2Path;
+ TpTestsContactSearchChannel *mChan2Service;
+
+ ContactSearchChannel::SearchResult mSearchResult;
+ bool mSearchReturned;
+
+ struct SearchStateChangeInfo
+ {
+ SearchStateChangeInfo(ChannelContactSearchState state, const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details)
+ : state(state), errorName(errorName), details(details)
+ {
+ }
+
+ ChannelContactSearchState state;
+ QString errorName;
+ ContactSearchChannel::SearchStateChangeDetails details;
+ };
+ QList<SearchStateChangeInfo> mSearchStateChangeInfoList;
+};
+
+void TestContactSearchChan::onSearchStateChanged(Tp::ChannelContactSearchState state,
+ const QString &errorName,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &details)
+{
+ mSearchStateChangeInfoList.append(SearchStateChangeInfo(state, errorName, details));
+ mLoop->exit(0);
+}
+
+void TestContactSearchChan::onSearchResultReceived(
+ const Tp::ContactSearchChannel::SearchResult &result)
+{
+ QCOMPARE(mChan->searchState(), ChannelContactSearchStateInProgress);
+ mSearchResult = result;
+ mLoop->exit(0);
+}
+
+void TestContactSearchChan::onSearchReturned(Tp::PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ QVERIFY(mChan->searchState() != ChannelContactSearchStateNotStarted);
+ mSearchReturned = true;
+ mLoop->exit(0);
+}
+
+void TestContactSearchChan::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contact-search-chan");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_ECHO_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+
+ QByteArray chan1Path;
+ mChan1Path = mConn->objectPath() + QLatin1String("/ContactSearchChannel/1");
+ chan1Path = mChan1Path.toAscii();
+ mChan1Service = TP_TESTS_CONTACT_SEARCH_CHANNEL(g_object_new(
+ TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chan1Path.data(),
+ NULL));
+
+ QByteArray chan2Path;
+ mChan2Path = mConn->objectPath() + QLatin1String("/ContactSearchChannel/2");
+ chan2Path = mChan2Path.toAscii();
+ mChan2Service = TP_TESTS_CONTACT_SEARCH_CHANNEL(g_object_new(
+ TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chan2Path.data(),
+ NULL));
+}
+
+void TestContactSearchChan::init()
+{
+ initImpl();
+ mSearchResult.clear();
+ mSearchStateChangeInfoList.clear();
+ mSearchReturned = false;
+}
+
+void TestContactSearchChan::testContactSearch()
+{
+ mChan1 = ContactSearchChannel::create(mConn->client(), mChan1Path, QVariantMap());
+ mChan = mChan1;
+ // becomeReady with no args should implicitly enable ContactSearchChannel::FeatureCore
+ QVERIFY(connect(mChan1->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan1->isReady(ContactSearchChannel::FeatureCore), true);
+
+ QCOMPARE(mChan1->searchState(), ChannelContactSearchStateNotStarted);
+ QCOMPARE(mChan1->limit(), static_cast<uint>(0));
+ QCOMPARE(mChan1->availableSearchKeys().isEmpty(), false);
+ QCOMPARE(mChan1->availableSearchKeys().count(), 1);
+ QCOMPARE(mChan1->availableSearchKeys().first(), QLatin1String("employer"));
+ QCOMPARE(mChan1->server(), QLatin1String("characters.shakespeare.lit"));
+
+ QVERIFY(connect(mChan1.data(),
+ SIGNAL(searchStateChanged(Tp::ChannelContactSearchState, const QString &,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &)),
+ SLOT(onSearchStateChanged(Tp::ChannelContactSearchState, const QString &,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &))));
+ QVERIFY(connect(mChan1.data(),
+ SIGNAL(searchResultReceived(const Tp::ContactSearchChannel::SearchResult &)),
+ SLOT(onSearchResultReceived(const Tp::ContactSearchChannel::SearchResult &))));
+
+ QVERIFY(connect(mChan1->search(QLatin1String("employer"), QLatin1String("Collabora")),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onSearchReturned(Tp::PendingOperation *))));
+ while (!mSearchReturned) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ while (mChan1->searchState() != ChannelContactSearchStateCompleted) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mSearchReturned, true);
+
+ QCOMPARE(mSearchStateChangeInfoList.count(), 2);
+ SearchStateChangeInfo info = mSearchStateChangeInfoList.at(0);
+ QCOMPARE(info.state, ChannelContactSearchStateInProgress);
+ QCOMPARE(info.errorName, QLatin1String(""));
+ QCOMPARE(info.details.hasDebugMessage(), true);
+ QCOMPARE(info.details.debugMessage(), QLatin1String("in progress"));
+
+ info = mSearchStateChangeInfoList.at(1);
+ QCOMPARE(info.state, ChannelContactSearchStateCompleted);
+ QCOMPARE(info.errorName, QLatin1String(""));
+ QCOMPARE(info.details.hasDebugMessage(), true);
+ QCOMPARE(info.details.debugMessage(), QLatin1String("completed"));
+
+ QCOMPARE(mSearchResult.isEmpty(), false);
+ QCOMPARE(mSearchResult.size(), 3);
+
+ QStringList expectedIds;
+ expectedIds << QLatin1String("oggis") << QLatin1String("andrunko") <<
+ QLatin1String("wjt");
+ expectedIds.sort();
+ QStringList expectedFns;
+ expectedFns << QLatin1String("Olli Salli") << QLatin1String("Andre Moreira Magalhaes") <<
+ QLatin1String("Will Thompson");
+ expectedFns.sort();
+ QStringList ids;
+ QStringList fns;
+ for (ContactSearchChannel::SearchResult::const_iterator it = mSearchResult.constBegin();
+ it != mSearchResult.constEnd();
+ ++it)
+ {
+ QCOMPARE(it.key().isNull(), false);
+ ids << it.key()->id();
+ QCOMPARE(it.value().isValid(), true);
+ QCOMPARE(it.value().allFields().isEmpty(), false);
+ Q_FOREACH (const ContactInfoField &contactInfo, it.value().allFields()) {
+ QCOMPARE(contactInfo.fieldName, QLatin1String("fn"));
+ fns.append(contactInfo.fieldValue.first());
+ }
+ }
+ ids.sort();
+ QCOMPARE(ids, expectedIds);
+ fns.sort();
+ QCOMPARE(fns, expectedFns);
+
+ mChan1.reset();
+}
+
+void TestContactSearchChan::testContactSearchEmptyResult()
+{
+ mChan2 = ContactSearchChannel::create(mConn->client(), mChan2Path, QVariantMap());
+ mChan = mChan2;
+ QVERIFY(connect(mChan2->becomeReady(ContactSearchChannel::FeatureCore),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan2->isReady(), true);
+
+ QCOMPARE(mChan2->searchState(), ChannelContactSearchStateNotStarted);
+ QCOMPARE(mChan2->limit(), static_cast<uint>(0));
+ QCOMPARE(mChan2->availableSearchKeys().isEmpty(), false);
+ QCOMPARE(mChan2->availableSearchKeys().count(), 1);
+ QCOMPARE(mChan2->availableSearchKeys().first(), QLatin1String("employer"));
+ QCOMPARE(mChan2->server(), QLatin1String("characters.shakespeare.lit"));
+
+ QVERIFY(connect(mChan2.data(),
+ SIGNAL(searchStateChanged(Tp::ChannelContactSearchState, const QString &,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &)),
+ SLOT(onSearchStateChanged(Tp::ChannelContactSearchState, const QString &,
+ const Tp::ContactSearchChannel::SearchStateChangeDetails &))));
+ QVERIFY(connect(mChan2.data(),
+ SIGNAL(searchResultReceived(const Tp::ContactSearchChannel::SearchResult &)),
+ SLOT(onSearchResultReceived(const Tp::ContactSearchChannel::SearchResult &))));
+
+ ContactSearchMap searchTerms;
+ searchTerms.insert(QLatin1String("employer"), QLatin1String("FooBar"));
+ QVERIFY(connect(mChan2->search(searchTerms),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onSearchReturned(Tp::PendingOperation *))));
+ while (!mSearchReturned) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ while (mChan2->searchState() != ChannelContactSearchStateCompleted) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mSearchReturned, true);
+
+ QCOMPARE(mSearchResult.isEmpty(), true);
+
+ QCOMPARE(mSearchStateChangeInfoList.count(), 2);
+ SearchStateChangeInfo info = mSearchStateChangeInfoList.at(0);
+ QCOMPARE(info.state, ChannelContactSearchStateInProgress);
+ QCOMPARE(info.errorName, QLatin1String(""));
+ QCOMPARE(info.details.hasDebugMessage(), true);
+ QCOMPARE(info.details.debugMessage(), QLatin1String("in progress"));
+
+ info = mSearchStateChangeInfoList.at(1);
+ QCOMPARE(info.state, ChannelContactSearchStateCompleted);
+ QCOMPARE(info.errorName, QLatin1String(""));
+ QCOMPARE(info.details.hasDebugMessage(), true);
+ QCOMPARE(info.details.debugMessage(), QLatin1String("completed"));
+
+ mChan2.reset();
+}
+
+void TestContactSearchChan::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContactSearchChan::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ if (mChan1Service != 0) {
+ g_object_unref(mChan1Service);
+ mChan1Service = 0;
+ }
+
+ if (mChan2Service != 0) {
+ g_object_unref(mChan2Service);
+ mChan2Service = 0;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContactSearchChan)
+#include "_gen/contact-search-chan.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contacts-avatar.cpp b/qt4/tests/dbus/contacts-avatar.cpp
new file mode 100644
index 000000000..16a504cd8
--- /dev/null
+++ b/qt4/tests/dbus/contacts-avatar.cpp
@@ -0,0 +1,331 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+
+#include <TelepathyQt4/AvatarData>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class SmartDir : public QDir
+{
+public:
+ SmartDir(const QString &path) : QDir(path) { }
+ bool rmdir() { return QDir().rmdir(path()); }
+ bool removeDirectory();
+};
+
+bool SmartDir::removeDirectory()
+{
+ bool ret = true;
+
+ QFileInfoList list = entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
+ Q_FOREACH (QFileInfo info, list) {
+ if (info.isDir()) {
+ SmartDir subDir(info.filePath());
+ if (!subDir.removeDirectory()) {
+ ret = false;
+ }
+ } else {
+ qDebug() << "deleting" << info.filePath();
+ if (!QFile(info.filePath()).remove()) {
+ ret = false;
+ }
+ }
+ }
+
+ if (ret) {
+ qDebug() << "deleting" << path();
+ ret = rmdir();
+ }
+
+ return ret;
+}
+
+class TestContactsAvatar : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactsAvatar(QObject *parent = 0)
+ : Test(parent), mConn(0),
+ mGotAvatarRetrieved(false), mAvatarDatasChanged(0)
+ { }
+
+protected Q_SLOTS:
+ void onAvatarRetrieved(uint, const QString &, const QByteArray &, const QString &);
+ void onAvatarDataChanged(const Tp::AvatarData &);
+ void createContactWithFakeAvatar(const char *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testAvatar();
+ void testRequestAvatars();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ QList<ContactPtr> mContacts;
+ bool mGotAvatarRetrieved;
+ int mAvatarDatasChanged;
+};
+
+void TestContactsAvatar::onAvatarRetrieved(uint handle, const QString &token,
+ const QByteArray &data, const QString &mimeType)
+{
+ Q_UNUSED(handle);
+ Q_UNUSED(token);
+ Q_UNUSED(data);
+ Q_UNUSED(mimeType);
+
+ mGotAvatarRetrieved = true;
+}
+
+void TestContactsAvatar::onAvatarDataChanged(const AvatarData &avatar)
+{
+ Q_UNUSED(avatar);
+ mAvatarDatasChanged++;
+ mLoop->exit(0);
+}
+
+void TestContactsAvatar::createContactWithFakeAvatar(const char *id)
+{
+ TpHandleRepoIface *serviceRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ const gchar avatarData[] = "fake-avatar-data";
+ const gchar avatarToken[] = "fake-avatar-token";
+ const gchar avatarMimeType[] = "fake-avatar-mime-type";
+ TpHandle handle;
+ GArray *array;
+
+ handle = tp_handle_ensure(serviceRepo, id, NULL, NULL);
+ array = g_array_new(FALSE, FALSE, sizeof(gchar));
+ g_array_append_vals(array, avatarData, strlen(avatarData));
+
+ tp_tests_contacts_connection_change_avatar_data(
+ TP_TESTS_CONTACTS_CONNECTION(mConn->service()), handle,
+ array, avatarMimeType, avatarToken, true);
+ g_array_unref(array);
+
+ Tp::UIntList handles = Tp::UIntList() << handle;
+ Features features = Features()
+ << Contact::FeatureAvatarToken
+ << Contact::FeatureAvatarData;
+
+ mContacts = mConn->contacts(handles, features);
+ QCOMPARE(mContacts.size(), handles.size());
+
+ if (mContacts[0]->avatarData().fileName.isEmpty()) {
+ QVERIFY(connect(mContacts[0].data(),
+ SIGNAL(avatarDataChanged(const Tp::AvatarData &)),
+ SLOT(onAvatarDataChanged(const Tp::AvatarData &))));
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ AvatarData avatar = mContacts[0]->avatarData();
+
+ qDebug() << "Contact created:";
+ qDebug() << "Avatar token:" << mContacts[0]->avatarToken();
+ qDebug() << "Avatar file:" << avatar.fileName;
+ qDebug() << "Avatar MimeType:" << avatar.mimeType;
+
+ QFile file(avatar.fileName);
+ file.open(QIODevice::ReadOnly);
+ QByteArray data(file.readAll());
+ file.close();
+
+ QCOMPARE(mContacts[0]->avatarToken(), QString(QLatin1String(avatarToken)));
+ QCOMPARE(data, QByteArray(avatarData));
+ QCOMPARE(avatar.mimeType, QString(QLatin1String(avatarMimeType)));
+}
+
+void TestContactsAvatar::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contacts-avatar");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "foo",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestContactsAvatar::init()
+{
+ initImpl();
+
+ mGotAvatarRetrieved = false;
+ mAvatarDatasChanged = 0;
+}
+
+void TestContactsAvatar::testAvatar()
+{
+ QVERIFY(mConn->client()->contactManager()->supportedFeatures().contains(
+ Contact::FeatureAvatarData));
+
+ /* Make sure our tests does not mess up user's avatar cache */
+ qsrand(time(0));
+ static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ static const int DirNameLength = 6;
+ QString dirName;
+ for (int i = 0; i < DirNameLength; ++i) {
+ dirName += QLatin1Char(letters[qrand() % qstrlen(letters)]);
+ }
+ QString tmpDir = QString(QLatin1String("%1/%2")).arg(QDir::tempPath()).arg(dirName);
+ QByteArray a = tmpDir.toLatin1();
+ setenv ("XDG_CACHE_HOME", a.constData(), true);
+
+ Client::ConnectionInterfaceAvatarsInterface *connAvatarsInterface =
+ mConn->client()->optionalInterface<Client::ConnectionInterfaceAvatarsInterface>();
+
+ /* Check if AvatarRetrieved gets called */
+ connect(connAvatarsInterface,
+ SIGNAL(AvatarRetrieved(uint, const QString &, const QByteArray &, const QString &)),
+ SLOT(onAvatarRetrieved(uint, const QString &, const QByteArray &, const QString &)));
+
+ /* First time we create a contact, avatar should not be in cache, so
+ * AvatarRetrieved should be called */
+ mGotAvatarRetrieved = false;
+ createContactWithFakeAvatar("foo");
+ QVERIFY(mGotAvatarRetrieved);
+
+ /* Second time we create a contact, avatar should be in cache now, so
+ * AvatarRetrieved should NOT be called */
+ mGotAvatarRetrieved = false;
+ createContactWithFakeAvatar("bar");
+ QVERIFY(!mGotAvatarRetrieved);
+
+ QVERIFY(SmartDir(tmpDir).removeDirectory());
+}
+
+void TestContactsAvatar::testRequestAvatars()
+{
+ TpHandleRepoIface *serviceRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ const gchar avatarData[] = "fake-avatar-data";
+ const gchar avatarToken[] = "fake-avatar-token";
+ const gchar avatarMimeType[] = "fake-avatar-mime-type";
+ TpHandle handle;
+ GArray *array;
+
+ array = g_array_new(FALSE, FALSE, sizeof(gchar));
+ g_array_append_vals(array, avatarData, strlen(avatarData));
+
+ // First let's create the contacts
+ Tp::UIntList handles;
+ for (int i = 0; i < 100; ++i) {
+ QString contactId = QLatin1String("contact") + QString::number(i);
+ handle = tp_handle_ensure(serviceRepo, contactId.toLatin1().constData(), NULL, NULL);
+ handles << handle;
+ }
+ Features features = Features() << Contact::FeatureAvatarToken << Contact::FeatureAvatarData;
+ QList<ContactPtr> contacts = mConn->contacts(handles, features);
+ QCOMPARE(contacts.size(), handles.size());
+
+ // now let's update the avatar for half of them so we can later check that requestContactAvatars
+ // actually worked for all contacts.
+ mAvatarDatasChanged = 0;
+ for (int i = 0; i < contacts.size(); ++i) {
+ ContactPtr contact = contacts[i];
+ QVERIFY(contact->avatarData().fileName.isEmpty());
+
+ QString contactAvatarToken = QLatin1String(avatarToken) + QString::number(i);
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(avatarDataChanged(const Tp::AvatarData &)),
+ SLOT(onAvatarDataChanged(const Tp::AvatarData &))));
+
+ tp_tests_contacts_connection_change_avatar_data(
+ TP_TESTS_CONTACTS_CONNECTION(mConn->service()), contact->handle()[0],
+ array, avatarMimeType, contactAvatarToken.toLatin1().constData(),
+ (i % 2));
+ }
+
+ processDBusQueue(mConn->client().data());
+
+ while (mAvatarDatasChanged < contacts.size() / 2) {
+ mLoop->processEvents();
+ }
+
+ // check the only half got the updates
+ QCOMPARE(mAvatarDatasChanged, contacts.size() / 2);
+
+ for (int i = 0; i < contacts.size(); ++i) {
+ ContactPtr contact = contacts[i];
+ if (i % 2) {
+ QVERIFY(!contact->avatarData().fileName.isEmpty());
+ QCOMPARE(contact->avatarData().mimeType, QLatin1String(avatarMimeType));
+ QString contactAvatarToken = QLatin1String(avatarToken) + QString::number(i);
+ QCOMPARE(contact->avatarToken(), contactAvatarToken);
+ } else {
+ QVERIFY(contact->avatarData().fileName.isEmpty());
+ }
+ }
+
+ // let's call ContactManager::requestContactAvatars now, it should update all contacts
+ mAvatarDatasChanged = 0;
+ mConn->client()->contactManager()->requestContactAvatars(contacts);
+ processDBusQueue(mConn->client().data());
+
+ // the other half will now receive the avatar
+ while (mAvatarDatasChanged < contacts.size() / 2) {
+ mLoop->processEvents();
+ }
+
+ // check the only half got the updates
+ QCOMPARE(mAvatarDatasChanged, contacts.size() / 2);
+
+ for (int i = 0; i < contacts.size(); ++i) {
+ ContactPtr contact = contacts[i];
+ QVERIFY(!contact->avatarData().fileName.isEmpty());
+ QCOMPARE(contact->avatarData().mimeType, QLatin1String(avatarMimeType));
+ QString contactAvatarToken = QLatin1String(avatarToken) + QString::number(i);
+ QCOMPARE(contact->avatarToken(), contactAvatarToken);
+ }
+
+ mAvatarDatasChanged = 0;
+
+ // empty D-DBus queue
+ processDBusQueue(mConn->client().data());
+
+ // it should silently work, no crash
+ mConn->client()->contactManager()->requestContactAvatars(QList<ContactPtr>());
+
+ // let the mainloop run
+ processDBusQueue(mConn->client().data());
+
+ QCOMPARE(mAvatarDatasChanged, 0);
+}
+
+void TestContactsAvatar::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContactsAvatar::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContactsAvatar)
+#include "_gen/contacts-avatar.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contacts-capabilities.cpp b/qt4/tests/dbus/contacts-capabilities.cpp
new file mode 100644
index 000000000..fa2cf8615
--- /dev/null
+++ b/qt4/tests/dbus/contacts-capabilities.cpp
@@ -0,0 +1,161 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactCapabilities>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestContactsCapabilities : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactsCapabilities(QObject *parent = 0)
+ : Test(parent), mConn(0)
+ { }
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testCapabilities();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+};
+
+void TestContactsCapabilities::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contacts-capabilities");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "foo",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestContactsCapabilities::init()
+{
+ initImpl();
+}
+
+static void freeRccList(GPtrArray *rccs)
+{
+ g_boxed_free(TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs);
+}
+
+static void addTextChatClass(GPtrArray *classes, TpHandleType handle_type)
+{
+ GHashTable *fixed = tp_asv_new(
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, handle_type,
+ NULL);
+
+ const gchar * const allowed[] = { NULL };
+ GValueArray *arr = tp_value_array_build(2,
+ TP_HASH_TYPE_STRING_VARIANT_MAP, fixed,
+ G_TYPE_STRV, allowed,
+ G_TYPE_INVALID);
+
+ g_hash_table_unref(fixed);
+
+ g_ptr_array_add(classes, arr);
+}
+
+static GHashTable *createContactCapabilities(TpHandle *handles)
+{
+ GHashTable *capabilities = g_hash_table_new_full(NULL, NULL, NULL,
+ (GDestroyNotify) freeRccList);
+
+ /* Support private text chats */
+ GPtrArray *caps1 = g_ptr_array_sized_new(2);
+ addTextChatClass(caps1, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert(capabilities, GUINT_TO_POINTER(handles[0]), caps1);
+
+ /* Support text chatrooms */
+ GPtrArray *caps2 = g_ptr_array_sized_new(1);
+ g_hash_table_insert(capabilities, GUINT_TO_POINTER(handles[1]), caps2);
+
+ /* Don't support anything */
+ GPtrArray *caps3 = g_ptr_array_sized_new(0);
+ g_hash_table_insert(capabilities, GUINT_TO_POINTER(handles[2]), caps3);
+
+ return capabilities;
+}
+
+void TestContactsCapabilities::testCapabilities()
+{
+ ContactManagerPtr contactManager = mConn->client()->contactManager();
+
+ QVERIFY(contactManager->supportedFeatures().contains(Contact::FeatureCapabilities));
+
+ QStringList ids = QStringList() << QLatin1String("alice")
+ << QLatin1String("bob") << QLatin1String("chris");
+
+ gboolean supportTextChat[] = { TRUE, FALSE, FALSE };
+
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConn->service()),
+ TP_HANDLE_TYPE_CONTACT);
+ TpHandle handles[] = { 0, 0, 0 };
+ for (int i = 0; i < 3; i++) {
+ handles[i] = tp_handle_ensure(serviceRepo, ids[i].toLatin1().constData(),
+ NULL, NULL);
+ }
+
+ GHashTable *capabilities = createContactCapabilities(handles);
+ tp_tests_contacts_connection_change_capabilities(
+ TP_TESTS_CONTACTS_CONNECTION(mConn->service()), capabilities);
+ g_hash_table_destroy(capabilities);
+
+ QList<ContactPtr> contacts = mConn->contacts(ids, Contact::FeatureCapabilities);
+ QCOMPARE(contacts.size(), ids.size());
+ for (int i = 0; i < contacts.size(); i++) {
+ ContactPtr contact = contacts[i];
+
+ QCOMPARE(contact->requestedFeatures().contains(Contact::FeatureCapabilities), true);
+ QCOMPARE(contact->actualFeatures().contains(Contact::FeatureCapabilities), true);
+
+ QCOMPARE(contact->capabilities().textChats(), supportTextChat[i]);
+ QCOMPARE(contact->capabilities().streamedMediaCalls(), false);
+ QCOMPARE(contact->capabilities().streamedMediaAudioCalls(), false);
+ QCOMPARE(contact->capabilities().streamedMediaVideoCalls(), false);
+ QCOMPARE(contact->capabilities().streamedMediaVideoCallsWithAudio(), false);
+ QCOMPARE(contact->capabilities().upgradingStreamedMediaCalls(), false);
+ }
+}
+
+void TestContactsCapabilities::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContactsCapabilities::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContactsCapabilities)
+#include "_gen/contacts-capabilities.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contacts-info.cpp b/qt4/tests/dbus/contacts-info.cpp
new file mode 100644
index 000000000..af5f297fc
--- /dev/null
+++ b/qt4/tests/dbus/contacts-info.cpp
@@ -0,0 +1,247 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingContactInfo>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestContactsInfo : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactsInfo(QObject *parent = 0)
+ : Test(parent), mConn(0),
+ mContactsInfoFieldsUpdated(0),
+ mRefreshInfoFinished(0)
+ { }
+
+protected Q_SLOTS:
+ void onContactInfoFieldsChanged(const Tp::Contact::InfoFields &);
+ void onRefreshInfoFinished(Tp::PendingOperation *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testInfo();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ int mContactsInfoFieldsUpdated;
+ int mRefreshInfoFinished;
+};
+
+void TestContactsInfo::onContactInfoFieldsChanged(const Tp::Contact::InfoFields &info)
+{
+ Q_UNUSED(info);
+ mContactsInfoFieldsUpdated++;
+}
+
+void TestContactsInfo::onRefreshInfoFinished(PendingOperation *op)
+{
+ if (op->isError()) {
+ mLoop->exit(1);
+ return;
+ }
+
+ mRefreshInfoFinished++;
+ mLoop->exit(0);
+}
+
+void TestContactsInfo::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contacts-info");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "foo",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestContactsInfo::init()
+{
+ initImpl();
+ mContactsInfoFieldsUpdated = 0;
+ mRefreshInfoFinished = 0;
+}
+
+void TestContactsInfo::testInfo()
+{
+ ContactManagerPtr contactManager = mConn->client()->contactManager();
+
+ QVERIFY(contactManager->supportedFeatures().contains(Contact::FeatureInfo));
+
+ QStringList validIDs = QStringList() << QLatin1String("foo")
+ << QLatin1String("bar");
+ QList<ContactPtr> contacts = mConn->contacts(validIDs, Contact::FeatureInfo);
+ QCOMPARE(contacts.size(), validIDs.size());
+ for (int i = 0; i < contacts.size(); i++) {
+ ContactPtr contact = contacts[i];
+
+ QCOMPARE(contact->requestedFeatures().contains(Contact::FeatureInfo), true);
+ QCOMPARE(contact->actualFeatures().contains(Contact::FeatureInfo), true);
+
+ QVERIFY(contact->infoFields().allFields().isEmpty());
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(infoFieldsChanged(const Tp::Contact::InfoFields &)),
+ SLOT(onContactInfoFieldsChanged(const Tp::Contact::InfoFields &))));
+ }
+
+ GPtrArray *info_default = (GPtrArray *) dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+ {
+ const gchar * const field_values[2] = {
+ "FooBar", NULL
+ };
+ g_ptr_array_add (info_default, tp_value_array_build (3,
+ G_TYPE_STRING, "n",
+ G_TYPE_STRV, NULL,
+ G_TYPE_STRV, field_values,
+ G_TYPE_INVALID));
+ }
+ tp_tests_contacts_connection_set_default_contact_info(TP_TESTS_CONTACTS_CONNECTION(mConn->service()),
+ info_default);
+
+ GPtrArray *info_1 = (GPtrArray *) dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+ {
+ const gchar * const field_values[2] = {
+ "Foo", NULL
+ };
+ g_ptr_array_add (info_1, tp_value_array_build (3,
+ G_TYPE_STRING, "n",
+ G_TYPE_STRV, NULL,
+ G_TYPE_STRV, field_values,
+ G_TYPE_INVALID));
+ }
+ GPtrArray *info_2 = (GPtrArray *) dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+ {
+ const gchar * const field_values[2] = {
+ "Bar", NULL
+ };
+ g_ptr_array_add (info_2, tp_value_array_build (3,
+ G_TYPE_STRING, "n",
+ G_TYPE_STRV, NULL,
+ G_TYPE_STRV, field_values,
+ G_TYPE_INVALID));
+ }
+
+ TpHandle handles[] = { 0, 0 };
+ TpHandleRepoIface *serviceRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+
+ for (unsigned i = 0; i < 2; i++) {
+ handles[i] = tp_handle_ensure(serviceRepo, qPrintable(validIDs[i]),
+ NULL, NULL);
+ }
+
+ tp_tests_contacts_connection_change_contact_info(TP_TESTS_CONTACTS_CONNECTION(mConn->service()),
+ handles[0], info_1);
+ tp_tests_contacts_connection_change_contact_info(TP_TESTS_CONTACTS_CONNECTION(mConn->service()),
+ handles[1], info_2);
+
+ while (mContactsInfoFieldsUpdated != 2) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mContactsInfoFieldsUpdated, 2);
+
+ mContactsInfoFieldsUpdated = 0;
+ ContactPtr contactFoo = contacts[0];
+ ContactPtr contactBar = contacts[1];
+
+ QCOMPARE(contactFoo->infoFields().isValid(), true);
+ QCOMPARE(contactFoo->infoFields().allFields().size(), 1);
+ QCOMPARE(contactFoo->infoFields().allFields()[0].fieldName, QLatin1String("n"));
+ QCOMPARE(contactFoo->infoFields().allFields()[0].fieldValue[0], QLatin1String("Foo"));
+ QCOMPARE(contactBar->infoFields().isValid(), true);
+ QCOMPARE(contactBar->infoFields().allFields().size(), 1);
+ QCOMPARE(contactBar->infoFields().allFields()[0].fieldName, QLatin1String("n"));
+ QCOMPARE(contactBar->infoFields().allFields()[0].fieldValue[0], QLatin1String("Bar"));
+
+ TpTestsContactsConnection *serviceConn = TP_TESTS_CONTACTS_CONNECTION(mConn->service());
+ QCOMPARE(serviceConn->refresh_contact_info_called, static_cast<uint>(0));
+
+ mContactsInfoFieldsUpdated = 0;
+ mRefreshInfoFinished = 0;
+ Q_FOREACH (const ContactPtr &contact, contacts) {
+ QVERIFY(connect(contact->refreshInfo(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(onRefreshInfoFinished(Tp::PendingOperation*))));
+ }
+ while (mRefreshInfoFinished != contacts.size()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mRefreshInfoFinished, contacts.size());
+
+ while (mContactsInfoFieldsUpdated != contacts.size()) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mContactsInfoFieldsUpdated, contacts.size());
+
+ QCOMPARE(serviceConn->refresh_contact_info_called, static_cast<uint>(1));
+
+ for (int i = 0; i < contacts.size(); i++) {
+ ContactPtr contact = contacts[i];
+ QVERIFY(disconnect(contact.data(),
+ SIGNAL(infoFieldsChanged(const Tp::Contact::InfoFields &)),
+ this,
+ SLOT(onContactInfoFieldsChanged(const Tp::Contact::InfoFields &))));
+ }
+
+ PendingContactInfo *pci = contactFoo->requestInfo();
+ QVERIFY(connect(pci,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ while (!pci->isFinished()) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(pci->infoFields().isValid(), true);
+ QCOMPARE(pci->infoFields().allFields().size(), 1);
+ QCOMPARE(pci->infoFields().allFields()[0].fieldName, QLatin1String("n"));
+ QCOMPARE(pci->infoFields().allFields()[0].fieldValue[0], QLatin1String("FooBar"));
+
+ g_boxed_free(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, info_default);
+ g_boxed_free(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, info_1);
+ g_boxed_free(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, info_2);
+}
+
+void TestContactsInfo::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContactsInfo::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContactsInfo)
+#include "_gen/contacts-info.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contacts-location.cpp b/qt4/tests/dbus/contacts-location.cpp
new file mode 100644
index 000000000..724f982b0
--- /dev/null
+++ b/qt4/tests/dbus/contacts-location.cpp
@@ -0,0 +1,147 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/LocationInfo>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestContactsLocation : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContactsLocation(QObject *parent = 0)
+ : Test(parent), mConn(0), mContactsLocationUpdated(0)
+ { }
+
+protected Q_SLOTS:
+ void onLocationInfoUpdated(const Tp::LocationInfo &location);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testLocation();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ int mContactsLocationUpdated;
+};
+
+void TestContactsLocation::onLocationInfoUpdated(const Tp::LocationInfo &location)
+{
+ Q_UNUSED(location);
+ mContactsLocationUpdated++;
+ mLoop->exit(0);
+}
+
+void TestContactsLocation::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contacts-location");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "foo",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestContactsLocation::init()
+{
+ initImpl();
+ mContactsLocationUpdated = 0;
+}
+
+void TestContactsLocation::testLocation()
+{
+ ContactManagerPtr contactManager = mConn->client()->contactManager();
+
+ QVERIFY(contactManager->supportedFeatures().contains(Contact::FeatureLocation));
+
+ QStringList validIDs = QStringList() << QLatin1String("foo")
+ << QLatin1String("bar");
+ QList<ContactPtr> contacts = mConn->contacts(validIDs, Contact::FeatureLocation);
+ QCOMPARE(contacts.size(), validIDs.size());
+ for (int i = 0; i < contacts.size(); i++) {
+ ContactPtr contact = contacts[i];
+
+ QCOMPARE(contact->requestedFeatures().contains(Contact::FeatureLocation), true);
+ QCOMPARE(contact->actualFeatures().contains(Contact::FeatureLocation), true);
+
+ QVERIFY(connect(contact.data(),
+ SIGNAL(locationUpdated(const Tp::LocationInfo &)),
+ SLOT(onLocationInfoUpdated(const Tp::LocationInfo &))));
+ }
+
+ GHashTable *location_1 = tp_asv_new(
+ "country", G_TYPE_STRING, "United-kingdoms",
+ "lat", G_TYPE_DOUBLE, 20.0,
+ NULL);
+ GHashTable *location_2 = tp_asv_new(
+ "country", G_TYPE_STRING, "Atlantis",
+ "lat", G_TYPE_DOUBLE, 10.0,
+ NULL);
+ GHashTable *locations[] = { location_1, location_2 };
+
+ TpHandle handles[] = { 0, 0 };
+ TpHandleRepoIface *serviceRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+
+ for (unsigned i = 0; i < 2; i++) {
+ handles[i] = tp_handle_ensure(serviceRepo, qPrintable(validIDs[i]),
+ NULL, NULL);
+ }
+
+ tp_tests_contacts_connection_change_locations(TP_TESTS_CONTACTS_CONNECTION(mConn->service()), 2,
+ handles, locations);
+
+ while (mContactsLocationUpdated != 2) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ for (int i = 0; i < contacts.size(); i++) {
+ ContactPtr contact = contacts[i];
+
+ QCOMPARE(contact->location().country(),
+ QLatin1String(tp_asv_get_string(locations[i], "country")));
+ QCOMPARE(contact->location().latitude(),
+ tp_asv_get_double(locations[i], "lat", NULL));
+ }
+
+ g_hash_table_unref(location_1);
+ g_hash_table_unref(location_2);
+}
+
+void TestContactsLocation::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContactsLocation::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContactsLocation)
+#include "_gen/contacts-location.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/contacts.cpp b/qt4/tests/dbus/contacts.cpp
new file mode 100644
index 000000000..583c4704e
--- /dev/null
+++ b/qt4/tests/dbus/contacts.cpp
@@ -0,0 +1,814 @@
+#include <QDebug>
+#include <QList>
+#include <QTimer>
+
+#include <QtDBus>
+#include <QtTest>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/Contact>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Presence>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Types>
+
+#include <telepathy-glib/debug.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/glib/simple-conn.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestContacts : public Test
+{
+ Q_OBJECT
+
+public:
+ TestContacts(QObject *parent = 0)
+ : Test(parent), mConnService(0)
+ {
+ }
+
+protected Q_SLOTS:
+ void expectConnReady(Tp::ConnectionStatus, Tp::ConnectionStatusReason);
+ void expectConnInvalidated();
+ void expectPendingContactsFinished(Tp::PendingOperation *);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testSupport();
+ void testSelfContact();
+ void testForHandles();
+ void testForIdentifiers();
+ void testFeatures();
+ void testFeaturesNotRequested();
+ void testUpgrade();
+ void testSelfContactFallback();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QString mConnName, mConnPath;
+ TpTestsContactsConnection *mConnService;
+ ConnectionPtr mConn;
+ QList<ContactPtr> mContacts;
+ Tp::UIntList mInvalidHandles;
+};
+
+void TestContacts::expectConnReady(Tp::ConnectionStatus newStatus,
+ Tp::ConnectionStatusReason newStatusReason)
+{
+ switch (newStatus) {
+ case ConnectionStatusDisconnected:
+ qWarning() << "Disconnected";
+ mLoop->exit(1);
+ break;
+ case ConnectionStatusConnecting:
+ /* do nothing */
+ break;
+ case ConnectionStatusConnected:
+ qDebug() << "Ready";
+ mLoop->exit(0);
+ break;
+ default:
+ qWarning().nospace() << "What sort of status is "
+ << newStatus << "?!";
+ mLoop->exit(2);
+ break;
+ }
+}
+
+void TestContacts::expectConnInvalidated()
+{
+ mLoop->exit(0);
+}
+
+void TestContacts::expectPendingContactsFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingContacts *pending = qobject_cast<PendingContacts *>(op);
+ mContacts = pending->contacts();
+
+ if (pending->isForHandles()) {
+ mInvalidHandles = pending->invalidHandles();
+ }
+
+ mLoop->exit(0);
+}
+
+void TestContacts::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("contacts");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ mConnService = TP_TESTS_CONTACTS_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(mConnService != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService), "contacts", &name,
+ &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName = QLatin1String(name);
+ mConnPath = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+
+ mConn = Connection::create(mConnName, mConnPath,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ QCOMPARE(mConn->isReady(), false);
+
+ mConn->lowlevel()->requestConnect();
+
+ Features features = Features() << Connection::FeatureSelfContact;
+ QVERIFY(connect(mConn->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mConn->isReady(features), true);
+
+ if (mConn->status() != ConnectionStatusConnected) {
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(statusChanged(Tp::ConnectionStatus, Tp::ConnectionStatusReason)),
+ SLOT(expectConnReady(Tp::ConnectionStatus, Tp::ConnectionStatusReason))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(disconnect(mConn.data(),
+ SIGNAL(statusChanged(Tp::ConnectionStatus, Tp::ConnectionStatusReason)),
+ this,
+ SLOT(expectConnReady(Tp::ConnectionStatus, Tp::ConnectionStatusReason))));
+ QCOMPARE(mConn->status(), ConnectionStatusConnected);
+ }
+}
+
+void TestContacts::init()
+{
+ initImpl();
+}
+
+void TestContacts::testSupport()
+{
+ QCOMPARE(mConn->contactManager()->connection(), mConn);
+
+ QVERIFY(!mConn->lowlevel()->contactAttributeInterfaces().isEmpty());
+
+ QVERIFY(mConn->lowlevel()->contactAttributeInterfaces().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION)));
+ QVERIFY(mConn->lowlevel()->contactAttributeInterfaces().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_ALIASING)));
+ QVERIFY(mConn->lowlevel()->contactAttributeInterfaces().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS)));
+ QVERIFY(mConn->lowlevel()->contactAttributeInterfaces().contains(
+ QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE)));
+
+ Features supportedFeatures = mConn->contactManager()->supportedFeatures();
+ QVERIFY(!supportedFeatures.isEmpty());
+ QVERIFY(supportedFeatures.contains(Contact::FeatureAlias));
+ QVERIFY(supportedFeatures.contains(Contact::FeatureAvatarToken));
+ QVERIFY(supportedFeatures.contains(Contact::FeatureSimplePresence));
+}
+
+void TestContacts::testSelfContact()
+{
+ ContactPtr selfContact = mConn->selfContact();
+ QVERIFY(selfContact != 0);
+
+ QCOMPARE(selfContact->handle()[0], mConn->selfHandle());
+ QCOMPARE(selfContact->id(), QString(QLatin1String("me@example.com")));
+
+ Features features = Features() << Contact::FeatureAlias <<
+ Contact::FeatureAvatarToken <<
+ Contact::FeatureSimplePresence;
+ QVERIFY(connect(selfContact->manager()->upgradeContacts(
+ QList<ContactPtr>() << selfContact, features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(selfContact->alias(), QString(QLatin1String("me@example.com")));
+
+ QVERIFY(!selfContact->isAvatarTokenKnown());
+
+ QCOMPARE(selfContact->presence().status(), QString(QLatin1String("available")));
+ QCOMPARE(selfContact->presence().type(), Tp::ConnectionPresenceTypeAvailable);
+ QCOMPARE(selfContact->presence().statusMessage(), QString(QLatin1String("")));
+
+ features << Contact::FeatureInfo;
+ QVERIFY(connect(selfContact->manager()->upgradeContacts(
+ QList<ContactPtr>() << selfContact, features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(selfContact->alias(), QString(QLatin1String("me@example.com")));
+
+ QVERIFY(!selfContact->isAvatarTokenKnown());
+
+ QCOMPARE(selfContact->presence().status(), QString(QLatin1String("available")));
+ QCOMPARE(selfContact->presence().type(), Tp::ConnectionPresenceTypeAvailable);
+ QCOMPARE(selfContact->presence().statusMessage(), QString(QLatin1String("")));
+}
+
+void TestContacts::testForHandles()
+{
+ Tp::UIntList handles;
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConnService), TP_HANDLE_TYPE_CONTACT);
+
+ // Set up a few valid handles
+ handles << tp_handle_ensure(serviceRepo, "alice", NULL, NULL);
+ QVERIFY(handles[0] != 0);
+ handles << tp_handle_ensure(serviceRepo, "bob", NULL, NULL);
+ QVERIFY(handles[1] != 0);
+ // Put one probably invalid one in between
+ handles << 31337;
+ QVERIFY(!tp_handle_is_valid(serviceRepo, handles[2], NULL));
+ // Then another valid one
+ handles << tp_handle_ensure(serviceRepo, "chris", NULL, NULL);
+ QVERIFY(handles[3] != 0);
+ // And yet another invalid one
+ handles << 12345;
+ QVERIFY(!tp_handle_is_valid(serviceRepo, handles[4], NULL));
+
+ // Get contacts for the mixture of valid and invalid handles
+ PendingContacts *pending = mConn->contactManager()->contactsForHandles(handles);
+
+ // Test the closure accessors
+ QCOMPARE(pending->manager(), mConn->contactManager());
+ QCOMPARE(pending->features(), Features());
+
+ QVERIFY(pending->isForHandles());
+ QVERIFY(!pending->isForIdentifiers());
+ QVERIFY(!pending->isUpgrade());
+
+ QCOMPARE(pending->handles(), handles);
+
+ // Wait for the contacts to be built
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // There should be 3 resulting contacts and 2 handles found to be invalid
+ QCOMPARE(mContacts.size(), 3);
+
+ QCOMPARE(mInvalidHandles.size(), 2);
+ QCOMPARE(mInvalidHandles[0], handles[2]);
+ QCOMPARE(mInvalidHandles[1], handles[4]);
+
+ // Check the contact contents
+ for (int i = 0; i < 3; i++) {
+ QVERIFY(mContacts[i] != NULL);
+ QCOMPARE(mContacts[i]->manager(), mConn->contactManager());
+ QCOMPARE(mContacts[i]->requestedFeatures(), Features());
+ QCOMPARE(mContacts[i]->actualFeatures(), Features());
+ }
+
+ QCOMPARE(mContacts[0]->handle()[0], handles[0]);
+ QCOMPARE(mContacts[1]->handle()[0], handles[1]);
+ QCOMPARE(mContacts[2]->handle()[0], handles[3]);
+
+ QCOMPARE(mContacts[0]->id(), QString(QLatin1String("alice")));
+ QCOMPARE(mContacts[1]->id(), QString(QLatin1String("bob")));
+ QCOMPARE(mContacts[2]->id(), QString(QLatin1String("chris")));
+
+ // Save the contacts, and make a new request, replacing one of the invalid handles with a valid
+ // one
+ QList<ContactPtr> saveContacts = mContacts;
+ handles[2] = tp_handle_ensure(serviceRepo, "dora", NULL, NULL);
+ QVERIFY(handles[2] != 0);
+
+ pending = mConn->contactManager()->contactsForHandles(handles);
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Check that we got the correct number of contacts back
+ QCOMPARE(mContacts.size(), 4);
+ QCOMPARE(mInvalidHandles.size(), 1);
+
+ // Check that the contacts we already had were returned for the initial three
+ QCOMPARE(saveContacts[0], mContacts[0]);
+ QCOMPARE(saveContacts[1], mContacts[1]);
+ QCOMPARE(saveContacts[2], mContacts[3]);
+
+ // Check that the new contact is OK too
+ QCOMPARE(mContacts[2]->handle()[0], handles[2]);
+ QCOMPARE(mContacts[2]->id(), QString(QLatin1String("dora")));
+
+ // Make the contacts go out of scope, starting releasing their handles, and finish that
+ saveContacts.clear();
+ mContacts.clear();
+ mLoop->processEvents();
+ processDBusQueue(mConn.data());
+}
+
+void TestContacts::testForIdentifiers()
+{
+ QStringList validIDs = QStringList() << QLatin1String("Alice")
+ << QLatin1String("Bob") << QLatin1String("Chris");
+ QStringList invalidIDs = QStringList() << QLatin1String("Not valid")
+ << QLatin1String("Not valid either");
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConnService), TP_HANDLE_TYPE_CONTACT);
+
+ QStringList toCheck;
+
+ // Check that a request with just the invalid IDs fails
+ PendingContacts *fails = mConn->contactManager()->contactsForIdentifiers(invalidIDs);
+ QVERIFY(connect(fails,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ toCheck = fails->invalidIdentifiers().keys();
+ toCheck.sort();
+ invalidIDs.sort();
+ QCOMPARE(toCheck, invalidIDs);
+
+ // A request with both valid and invalid IDs should succeed
+ fails = mConn->contactManager()->contactsForIdentifiers(invalidIDs + validIDs + invalidIDs);
+ QVERIFY(connect(fails,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(fails->validIdentifiers(), validIDs);
+ toCheck = fails->invalidIdentifiers().keys();
+ toCheck.sort();
+ invalidIDs.sort();
+ QCOMPARE(toCheck, invalidIDs);
+
+ // Go on to the meat: valid IDs
+ PendingContacts *pending = mConn->contactManager()->contactsForIdentifiers(validIDs);
+
+ // Test the closure accessors
+ QCOMPARE(pending->manager(), mConn->contactManager());
+ QCOMPARE(pending->features(), Features());
+
+ QVERIFY(!pending->isForHandles());
+ QVERIFY(pending->isForIdentifiers());
+ QVERIFY(!pending->isUpgrade());
+
+ QCOMPARE(pending->identifiers(), validIDs);
+
+ // Finish it
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Check that there are 3 contacts consistent with the request
+ QCOMPARE(mContacts.size(), 3);
+
+ for (int i = 0; i < mContacts.size(); i++) {
+ QVERIFY(mContacts[i] != NULL);
+ QCOMPARE(mContacts[i]->manager(), mConn->contactManager());
+ QVERIFY(tp_handle_is_valid(serviceRepo, mContacts[i]->handle()[0], NULL));
+ QCOMPARE(mContacts[i]->requestedFeatures(), Features());
+ QCOMPARE(mContacts[i]->actualFeatures(), Features());
+ }
+
+ QCOMPARE(mContacts[0]->id(), QString(QLatin1String("alice")));
+ QCOMPARE(mContacts[1]->id(), QString(QLatin1String("bob")));
+ QCOMPARE(mContacts[2]->id(), QString(QLatin1String("chris")));
+
+ // Make the contacts go out of scope, starting releasing their handles, and finish that (but
+ // save their handles first)
+ Tp::UIntList saveHandles = Tp::UIntList() << mContacts[0]->handle()[0]
+ << mContacts[1]->handle()[0]
+ << mContacts[2]->handle()[0];
+ mContacts.clear();
+ mLoop->processEvents();
+ processDBusQueue(mConn.data());
+}
+
+void TestContacts::testFeatures()
+{
+ QStringList ids = QStringList() << QLatin1String("alice")
+ << QLatin1String("bob") << QLatin1String("chris");
+ const char *initialAliases[] = {
+ "Alice in Wonderland",
+ "Bob the Builder",
+ "Chris Sawyer"
+ };
+ const char *latterAliases[] = {
+ "Alice Through the Looking Glass",
+ "Bob the Pensioner"
+ };
+ const char *initialTokens[] = {
+ "bbbbb",
+ "ccccc"
+ };
+ const char *latterTokens[] = {
+ "AAAA",
+ "BBBB"
+ };
+ static TpTestsContactsConnectionPresenceStatusIndex initialStatuses[] = {
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_BUSY,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY
+ };
+ static TpTestsContactsConnectionPresenceStatusIndex latterStatuses[] = {
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE,
+ };
+ const char *initialMessages[] = {
+ "",
+ "Fixing it",
+ "GON OUT BACKSON"
+ };
+ const char *latterMessages[] = {
+ "Having some carrots",
+ "Done building for life, yay",
+ };
+ Features features = Features()
+ << Contact::FeatureAlias
+ << Contact::FeatureAvatarToken
+ << Contact::FeatureSimplePresence;
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConnService), TP_HANDLE_TYPE_CONTACT);
+
+ // Get test handles
+ Tp::UIntList handles;
+ for (int i = 0; i < 3; i++) {
+ handles.push_back(tp_handle_ensure(serviceRepo, ids[i].toLatin1().constData(), NULL, NULL));
+ QVERIFY(handles[i] != 0);
+ }
+
+ // Set the initial attributes
+ tp_tests_contacts_connection_change_aliases(mConnService, 3, handles.toVector().constData(),
+ initialAliases);
+ tp_tests_contacts_connection_change_avatar_tokens(mConnService, 2, handles.toVector().constData() + 1,
+ initialTokens);
+ tp_tests_contacts_connection_change_presences(mConnService, 3, handles.toVector().constData(),
+ initialStatuses, initialMessages);
+
+ // Build contacts
+ PendingContacts *pending = mConn->contactManager()->contactsForHandles(handles, features);
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Check the contact contents
+ QCOMPARE(mContacts.size(), 3);
+ for (int i = 0; i < 3; i++) {
+ QCOMPARE(mContacts[i]->handle()[0], handles[i]);
+ QCOMPARE(mContacts[i]->id(), ids[i]);
+ QVERIFY((features - mContacts[i]->requestedFeatures()).isEmpty());
+ QVERIFY((mContacts[i]->actualFeatures() - mContacts[i]->requestedFeatures()).isEmpty());
+
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureAlias));
+ QCOMPARE(mContacts[i]->alias(), QString(QLatin1String(initialAliases[i])));
+
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureAvatarToken));
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureSimplePresence));
+ QCOMPARE(mContacts[i]->presence().statusMessage(), QString(QLatin1String(initialMessages[i])));
+ }
+
+ // Check that there's no known avatar token for the first contact, but that there is for the
+ // two others
+ QVERIFY(!mContacts[0]->isAvatarTokenKnown());
+ QVERIFY(mContacts[1]->isAvatarTokenKnown());
+ QVERIFY(mContacts[2]->isAvatarTokenKnown());
+
+ QCOMPARE(mContacts[0]->avatarToken(), QString(QLatin1String("")));
+ QCOMPARE(mContacts[1]->avatarToken(), QString(QLatin1String(initialTokens[0])));
+ QCOMPARE(mContacts[2]->avatarToken(), QString(QLatin1String(initialTokens[1])));
+
+ QCOMPARE(mContacts[0]->presence().status(), QString(QLatin1String("available")));
+ QCOMPARE(mContacts[1]->presence().status(), QString(QLatin1String("busy")));
+ QCOMPARE(mContacts[2]->presence().status(), QString(QLatin1String("away")));
+
+ QCOMPARE(mContacts[0]->presence().type(), Tp::ConnectionPresenceTypeAvailable);
+ QCOMPARE(mContacts[1]->presence().type(), Tp::ConnectionPresenceTypeBusy);
+ QCOMPARE(mContacts[2]->presence().type(), Tp::ConnectionPresenceTypeAway);
+
+ // Change some of the contacts to a new set of attributes
+ tp_tests_contacts_connection_change_aliases(mConnService, 2, handles.toVector().constData(),
+ latterAliases);
+ tp_tests_contacts_connection_change_avatar_tokens(mConnService, 2, handles.toVector().constData(),
+ latterTokens);
+ tp_tests_contacts_connection_change_presences(mConnService, 2, handles.toVector().constData(),
+ latterStatuses, latterMessages);
+ mLoop->processEvents();
+ processDBusQueue(mConn.data());
+
+ // Check that the attributes were updated in the Contact objects
+ for (int i = 0; i < 3; i++) {
+ QCOMPARE(mContacts[i]->handle()[0], handles[i]);
+ QCOMPARE(mContacts[i]->id(), ids[i]);
+ QVERIFY((features - mContacts[i]->requestedFeatures()).isEmpty());
+ QVERIFY((mContacts[i]->actualFeatures() - mContacts[i]->requestedFeatures()).isEmpty());
+
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureAlias));
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureAvatarToken));
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureSimplePresence));
+
+ QVERIFY(mContacts[i]->isAvatarTokenKnown());
+ }
+
+ QCOMPARE(mContacts[0]->alias(), QString(QLatin1String(latterAliases[0])));
+ QCOMPARE(mContacts[1]->alias(), QString(QLatin1String(latterAliases[1])));
+ QCOMPARE(mContacts[2]->alias(), QString(QLatin1String(initialAliases[2])));
+
+ QCOMPARE(mContacts[0]->avatarToken(), QString(QLatin1String(latterTokens[0])));
+ QCOMPARE(mContacts[1]->avatarToken(), QString(QLatin1String(latterTokens[1])));
+ QCOMPARE(mContacts[2]->avatarToken(), QString(QLatin1String(initialTokens[1])));
+
+ QCOMPARE(mContacts[0]->presence().status(), QString(QLatin1String("away")));
+ QCOMPARE(mContacts[1]->presence().status(), QString(QLatin1String("available")));
+ QCOMPARE(mContacts[2]->presence().status(), QString(QLatin1String("away")));
+
+ QCOMPARE(mContacts[0]->presence().type(), Tp::ConnectionPresenceTypeAway);
+ QCOMPARE(mContacts[1]->presence().type(), Tp::ConnectionPresenceTypeAvailable);
+ QCOMPARE(mContacts[2]->presence().type(), Tp::ConnectionPresenceTypeAway);
+
+ QCOMPARE(mContacts[0]->presence().statusMessage(), QString(QLatin1String(latterMessages[0])));
+ QCOMPARE(mContacts[1]->presence().statusMessage(), QString(QLatin1String(latterMessages[1])));
+ QCOMPARE(mContacts[2]->presence().statusMessage(), QString(QLatin1String(initialMessages[2])));
+
+ // Make the contacts go out of scope, starting releasing their handles, and finish that
+ mContacts.clear();
+ mLoop->processEvents();
+ processDBusQueue(mConn.data());
+}
+
+void TestContacts::testFeaturesNotRequested()
+{
+ // Test ids and corresponding handles
+ QStringList ids = QStringList() << QLatin1String("alice")
+ << QLatin1String("bob") << QLatin1String("chris");
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConnService), TP_HANDLE_TYPE_CONTACT);
+ Tp::UIntList handles;
+ for (int i = 0; i < 3; i++) {
+ handles.push_back(tp_handle_ensure(serviceRepo, ids[i].toLatin1().constData(), NULL, NULL));
+ QVERIFY(handles[i] != 0);
+ }
+
+ // Build contacts (note: no features)
+ PendingContacts *pending = mConn->contactManager()->contactsForHandles(handles);
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Check that the feature accessors return sensible fallback values (note: the warnings are
+ // intentional - however, I'm not quite sure if they should be just debug (like in tp-glib))
+ QCOMPARE(mContacts.size(), 3);
+ for (int i = 0; i < 3; i++) {
+ ContactPtr contact = mContacts[i];
+
+ QVERIFY(contact->requestedFeatures().isEmpty());
+ QVERIFY(contact->actualFeatures().isEmpty());
+
+ QCOMPARE(contact->alias(), contact->id());
+
+ QVERIFY(!contact->isAvatarTokenKnown());
+ QCOMPARE(contact->avatarToken(), QString(QLatin1String("")));
+
+ QCOMPARE(contact->presence().isValid(), false);
+ }
+
+ // Make the contacts go out of scope, starting releasing their handles, and finish that
+ mContacts.clear();
+ mLoop->processEvents();
+ processDBusQueue(mConn.data());
+}
+
+void TestContacts::testUpgrade()
+{
+ QStringList ids = QStringList() << QLatin1String("alice")
+ << QLatin1String("bob") << QLatin1String("chris");
+ const char *aliases[] = {
+ "Alice in Wonderland",
+ "Bob The Builder",
+ "Chris Sawyer"
+ };
+ const char *tokens[] = {
+ "aaaaa",
+ "bbbbb",
+ "ccccc"
+ };
+ static TpTestsContactsConnectionPresenceStatusIndex statuses[] = {
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_BUSY,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY
+ };
+ const char *messages[] = {
+ "",
+ "Fixing it",
+ "GON OUT BACKSON"
+ };
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConnService), TP_HANDLE_TYPE_CONTACT);
+
+ Tp::UIntList handles;
+ for (int i = 0; i < 3; i++) {
+ handles.push_back(tp_handle_ensure(serviceRepo, ids[i].toLatin1().constData(), NULL, NULL));
+ QVERIFY(handles[i] != 0);
+ }
+
+ tp_tests_contacts_connection_change_aliases(mConnService, 3, handles.toVector().constData(), aliases);
+ tp_tests_contacts_connection_change_avatar_tokens(mConnService, 3, handles.toVector().constData(), tokens);
+ tp_tests_contacts_connection_change_presences(mConnService, 3, handles.toVector().constData(), statuses,
+ messages);
+
+ PendingContacts *pending = mConn->contactManager()->contactsForHandles(handles);
+
+ // Wait for the contacts to be built
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // There should be 3 resulting contacts - save them for future reference
+ QCOMPARE(mContacts.size(), 3);
+ QList<ContactPtr> saveContacts = mContacts;
+
+ // Upgrade them
+ Features features = Features()
+ << Contact::FeatureAlias
+ << Contact::FeatureAvatarToken
+ << Contact::FeatureSimplePresence;
+ pending = mConn->contactManager()->upgradeContacts(saveContacts, features);
+
+ // Test the closure accessors
+ QCOMPARE(pending->manager(), mConn->contactManager());
+ QCOMPARE(pending->features(), features);
+
+ QVERIFY(!pending->isForHandles());
+ QVERIFY(!pending->isForIdentifiers());
+ QVERIFY(pending->isUpgrade());
+
+ QCOMPARE(pending->contactsToUpgrade(), saveContacts);
+
+ // Wait for the contacts to be built
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingContactsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Check that we got the correct contacts back
+ QCOMPARE(mContacts, saveContacts);
+
+ // Check the contact contents
+ for (int i = 0; i < 3; i++) {
+ QCOMPARE(mContacts[i]->handle()[0], handles[i]);
+ QCOMPARE(mContacts[i]->id(), ids[i]);
+ QVERIFY((features - mContacts[i]->requestedFeatures()).isEmpty());
+ QVERIFY((mContacts[i]->actualFeatures() - mContacts[i]->requestedFeatures()).isEmpty());
+
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureAlias));
+ QCOMPARE(mContacts[i]->alias(), QString(QLatin1String(aliases[i])));
+
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureAvatarToken));
+ QVERIFY(mContacts[i]->isAvatarTokenKnown());
+ QCOMPARE(mContacts[i]->avatarToken(), QString(QLatin1String(tokens[i])));
+
+ QVERIFY(mContacts[i]->actualFeatures().contains(Contact::FeatureSimplePresence));
+ QCOMPARE(mContacts[i]->presence().statusMessage(), QString(QLatin1String(messages[i])));
+ }
+
+ QCOMPARE(mContacts[0]->presence().status(), QString(QLatin1String("available")));
+ QCOMPARE(mContacts[1]->presence().status(), QString(QLatin1String("busy")));
+ QCOMPARE(mContacts[2]->presence().status(), QString(QLatin1String("away")));
+
+ QCOMPARE(mContacts[0]->presence().type(), Tp::ConnectionPresenceTypeAvailable);
+ QCOMPARE(mContacts[1]->presence().type(), Tp::ConnectionPresenceTypeBusy);
+ QCOMPARE(mContacts[2]->presence().type(), Tp::ConnectionPresenceTypeAway);
+
+ // Make the contacts go out of scope, starting releasing their handles, and finish that
+ saveContacts.clear();
+ mContacts.clear();
+ mLoop->processEvents();
+ processDBusQueue(mConn.data());
+}
+
+void TestContacts::testSelfContactFallback()
+{
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ TpTestsSimpleConnection *connService;
+ connService = TP_TESTS_SIMPLE_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(connService != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(connService), "simple", &name,
+ &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ ConnectionPtr conn = Connection::create(QLatin1String(name), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+ g_free(name);
+ g_free(connPath);
+
+ QCOMPARE(conn->isReady(), false);
+
+ Features features = Features() << Connection::FeatureSelfContact;
+ QVERIFY(connect(conn->lowlevel()->requestConnect(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(conn->isReady(features), true);
+
+ ContactPtr selfContact = conn->selfContact();
+ QVERIFY(selfContact != 0);
+
+ QCOMPARE(selfContact->handle()[0], conn->selfHandle());
+ QCOMPARE(selfContact->id(), QString(QLatin1String("me@example.com")));
+ QCOMPARE(selfContact->alias(), QString(QLatin1String("me@example.com")));
+ QVERIFY(!selfContact->isAvatarTokenKnown());
+ QCOMPARE(selfContact->presence().isValid(), false);
+
+ tp_tests_simple_connection_inject_disconnect(connService);
+
+ if (conn->isValid()) {
+ QVERIFY(connect(conn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ mLoop,
+ SLOT(quit())));
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ g_object_unref(connService);
+}
+
+void TestContacts::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestContacts::cleanupTestCase()
+{
+ if (!mContacts.isEmpty()) {
+ mContacts.clear();
+ }
+
+ if (!mInvalidHandles.isEmpty()) {
+ mInvalidHandles.clear();
+ }
+
+ if (mConn) {
+ // Disconnect and wait for the readiness change
+ QVERIFY(connect(mConn->lowlevel()->requestDisconnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ if (mConn->isValid()) {
+ QVERIFY(connect(mConn.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *, QString, QString)),
+ SLOT(expectConnInvalidated())));
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ }
+
+ if (mConnService != 0) {
+ g_object_unref(mConnService);
+ mConnService = 0;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestContacts)
+#include "_gen/contacts.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/dbus-properties.cpp b/qt4/tests/dbus/dbus-properties.cpp
new file mode 100644
index 000000000..4e012dae5
--- /dev/null
+++ b/qt4/tests/dbus/dbus-properties.cpp
@@ -0,0 +1,165 @@
+#include <QtCore/QEventLoop>
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingVariantMap>
+#include <TelepathyQt4/PendingReady>
+
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestDBusProperties : public Test
+{
+ Q_OBJECT
+
+public:
+ TestDBusProperties(QObject *parent = 0)
+ : Test(parent), mAccountsCount(0)
+ { }
+
+protected Q_SLOTS:
+ void onNewAccount(const Tp::AccountPtr &);
+
+ void expectSuccessfulAllProperties(Tp::PendingOperation *op);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testDBusProperties();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ AccountManagerPtr mAM;
+ int mAccountsCount;
+ QVariantMap mAllProperties;
+};
+
+void TestDBusProperties::onNewAccount(const Tp::AccountPtr &acc)
+{
+ Q_UNUSED(acc);
+
+ mAccountsCount++;
+ mLoop->exit(0);
+}
+
+void TestDBusProperties::expectSuccessfulAllProperties(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning().nospace() << op->errorName()
+ << ": " << op->errorMessage();
+ mAllProperties = QVariantMap();
+ mLoop->exit(1);
+ } else {
+ Tp::PendingVariantMap *pvm = qobject_cast<Tp::PendingVariantMap*>(op);
+ mAllProperties = pvm->result();
+ mLoop->exit(0);
+ }
+}
+
+void TestDBusProperties::initTestCase()
+{
+ initTestCaseImpl();
+
+ mAM = AccountManager::create(AccountFactory::create(QDBusConnection::sessionBus(),
+ Account::FeatureCore | Account::FeatureCapabilities));
+ QCOMPARE(mAM->isReady(), false);
+}
+
+void TestDBusProperties::init()
+{
+ initImpl();
+}
+
+void TestDBusProperties::testDBusProperties()
+{
+ QVERIFY(connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAM->isReady(), true);
+
+ QVERIFY(connect(mAM.data(),
+ SIGNAL(newAccount(const Tp::AccountPtr &)),
+ SLOT(onNewAccount(const Tp::AccountPtr &))));
+
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ PendingAccount *pacc = mAM->createAccount(QLatin1String("foo"),
+ QLatin1String("bar"), QLatin1String("foobar"), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pacc->account());
+
+ while (mAccountsCount != 1) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mAM->interfaces(), QStringList());
+
+ AccountPtr acc = Account::create(mAM->busName(),
+ QLatin1String("/org/freedesktop/Telepathy/Account/foo/bar/Account0"));
+ QVERIFY(connect(acc->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+
+ while (!acc->isReady()) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ const QString oldDisplayName = QLatin1String("foobar (account 0)");
+ QCOMPARE(acc->displayName(), oldDisplayName);
+
+ Client::AccountInterface *cliAccount = acc->interface<Client::AccountInterface>();
+
+ QString currDisplayName;
+ QVERIFY(waitForProperty(cliAccount->requestPropertyDisplayName(), &currDisplayName));
+ QCOMPARE(currDisplayName, oldDisplayName);
+
+ QVERIFY(connect(cliAccount->requestAllProperties(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulAllProperties(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAllProperties[QLatin1String("DisplayName")].value<QString>(), oldDisplayName);
+ QVERIFY(mAllProperties[QLatin1String("Interfaces")].value<QStringList>().contains(
+ QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR)));
+
+ const QString newDisplayName = QLatin1String("Foo bar account");
+ QVERIFY(connect(cliAccount->setPropertyDisplayName(newDisplayName),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(waitForProperty(cliAccount->requestPropertyDisplayName(), &currDisplayName));
+ QCOMPARE(currDisplayName, newDisplayName);
+
+ QVERIFY(connect(cliAccount->requestAllProperties(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulAllProperties(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAllProperties[QLatin1String("DisplayName")].value<QString>(), newDisplayName);
+}
+
+void TestDBusProperties::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestDBusProperties::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestDBusProperties)
+#include "_gen/dbus-properties.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/dbus-proxy-factory.cpp b/qt4/tests/dbus/dbus-proxy-factory.cpp
new file mode 100644
index 000000000..3fc7edb94
--- /dev/null
+++ b/qt4/tests/dbus/dbus-proxy-factory.cpp
@@ -0,0 +1,361 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtDBus/QtDBus>
+#include <QtTest/QtTest>
+
+#include <QDateTime>
+#include <QString>
+#include <QVariantMap>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionFactory>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/Types>
+
+#include <TelepathyQt4/test-backdoors.h>
+
+#include <telepathy-glib/debug.h>
+
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestDBusProxyFactory : public Test
+{
+ Q_OBJECT
+
+public:
+ TestDBusProxyFactory(QObject *parent = 0)
+ : Test(parent),
+ mConnService1(0), mConnService2(0)
+ { }
+
+protected Q_SLOTS:
+ void expectFinished();
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testCaching();
+ void testDropRefs();
+ void testInvalidate();
+ void testBogusService();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TpTestsContactsConnection *mConnService1, *mConnService2;
+ QString mConnPath1, mConnPath2;
+ QString mConnName1, mConnName2;
+ ConnectionFactoryPtr mFactory;
+ uint mNumFinished;
+};
+
+void TestDBusProxyFactory::expectFinished()
+{
+ mNumFinished++;
+}
+
+void TestDBusProxyFactory::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("dbus-proxy-factory");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ gchar *name;
+ gchar *connPath;
+ GError *error = 0;
+
+ mConnService1 = TP_TESTS_CONTACTS_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me1@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(mConnService1 != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService1),
+ "contacts", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName1 = QLatin1String(name);
+ mConnPath1 = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+
+ mConnService2 = TP_TESTS_CONTACTS_CONNECTION(g_object_new(
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me2@example.com",
+ "protocol", "simple",
+ NULL));
+ QVERIFY(mConnService2 != 0);
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mConnService2),
+ "contacts", &name, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(name != 0);
+ QVERIFY(connPath != 0);
+
+ mConnName2 = QLatin1String(name);
+ mConnPath2 = QLatin1String(connPath);
+
+ g_free(name);
+ g_free(connPath);
+}
+
+void TestDBusProxyFactory::init()
+{
+ initImpl();
+
+ mFactory = ConnectionFactory::create(QDBusConnection::sessionBus(),
+ Connection::FeatureCore);
+ mNumFinished = 0;
+}
+
+void TestDBusProxyFactory::testCaching()
+{
+ PendingReady *first = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(first != NULL);
+ QVERIFY(!first->object().isNull());
+ QVERIFY(!first->proxy().isNull());
+
+ PendingReady *same = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(same != NULL);
+ QVERIFY(!same->object().isNull());
+ QVERIFY(!same->proxy().isNull());
+
+ QCOMPARE(same->object().data(), first->object().data());
+ QCOMPARE(same->proxy().data(), first->proxy().data());
+
+ PendingReady *different = mFactory->proxy(mConnName2, mConnPath2,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(different != NULL);
+ QVERIFY(!different->object().isNull());
+ QVERIFY(!different->proxy().isNull());
+
+ QVERIFY(different->object() == first->object());
+ QVERIFY(different->proxy() != first->proxy());
+
+ ConnectionPtr firstProxy = ConnectionPtr::qObjectCast(first->proxy());
+
+ QVERIFY(!first->isFinished() && !same->isFinished() && !different->isFinished());
+ QVERIFY(connect(first, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFinished())));
+ QVERIFY(connect(same, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFinished())));
+ QVERIFY(connect(different, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFinished())));
+ QVERIFY(!first->isFinished() && !same->isFinished() && !different->isFinished());
+
+ while (mNumFinished < 3) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mNumFinished, 3U);
+
+ PendingReady *another = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(another != NULL);
+ QVERIFY(!another->object().isNull());
+ QVERIFY(!another->proxy().isNull());
+
+ // Should still be the same even if all the initial requests already finished
+ QCOMPARE(another->proxy().data(), firstProxy.data());
+
+ QVERIFY(connect(another, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestDBusProxyFactory::testDropRefs()
+{
+ PendingReady *first = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(first != NULL);
+ QVERIFY(!first->object().isNull());
+ QVERIFY(!first->proxy().isNull());
+
+ ConnectionPtr firstProxy = ConnectionPtr::qObjectCast(first->proxy());
+ QVERIFY(firstProxy->isValid());
+
+ QVERIFY(connect(first, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ PendingReady *same = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(same != NULL);
+ QVERIFY(!same->object().isNull());
+ QVERIFY(!same->proxy().isNull());
+
+ // The first one is in scope so we should've got it again
+ QCOMPARE(same->proxy().data(), firstProxy.data());
+
+ QVERIFY(connect(same, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Flush the delete event for the PendingReady, which drops the PendingReady ref to the proxy
+ mLoop->processEvents();
+
+ // Make the Conn go out of scope
+ Connection *firstPtr = firstProxy.data();
+ firstProxy.reset();
+
+ PendingReady *different = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(different != NULL);
+ QVERIFY(!different->object().isNull());
+ QVERIFY(!different->proxy().isNull());
+
+ // The first one has gone out of scope and deleted so we should've got a different one
+ QVERIFY(different->proxy().data() != firstPtr);
+}
+
+void TestDBusProxyFactory::testInvalidate()
+{
+ PendingReady *first = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(first != NULL);
+ QVERIFY(!first->object().isNull());
+ QVERIFY(!first->proxy().isNull());
+
+ ConnectionPtr firstProxy = ConnectionPtr::qObjectCast(first->proxy());
+ QVERIFY(firstProxy->isValid());
+
+ QVERIFY(connect(first, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ PendingReady *same = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(same != NULL);
+ QVERIFY(!same->object().isNull());
+ QVERIFY(!same->proxy().isNull());
+
+ // The first one is in scope and valid so we should've got it again
+ QCOMPARE(same->proxy().data(), firstProxy.data());
+
+ QVERIFY(connect(same, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Flush the delete event for the PendingReady, which drops the PendingReady ref to the proxy
+ mLoop->processEvents();
+
+ // Synthesize an invalidation for the proxy
+ QVERIFY(connect(firstProxy.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ mLoop, SLOT(quit())));
+ TestBackdoors::invalidateProxy(firstProxy.data(),
+ QLatin1String("im.bonghits.Errors.Synthetic"), QLatin1String(""));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!firstProxy->isValid());
+
+ PendingReady *different = mFactory->proxy(mConnName1, mConnPath1,
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(different != NULL);
+ ConnectionPtr differentProxy = ConnectionPtr::qObjectCast(different->proxy());
+ QVERIFY(!differentProxy.isNull());
+
+ // The first one is invalid so we should've got a different one
+ QVERIFY(differentProxy.data() != firstProxy.data());
+ QVERIFY(differentProxy->isValid());
+
+ QVERIFY(!differentProxy->isReady());
+
+ QVERIFY(connect(different,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(differentProxy->isValid());
+ QVERIFY(differentProxy->isReady());
+}
+
+void TestDBusProxyFactory::testBogusService()
+{
+ PendingReady *bogus = mFactory->proxy(QLatin1String("org.bogus.Totally"),
+ QLatin1String("/org/bogus/Totally"),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(bogus != NULL);
+ QVERIFY(!bogus->object().isNull());
+ QVERIFY(!bogus->proxy().isNull());
+
+ QVERIFY(!ConnectionPtr::qObjectCast(bogus->proxy())->isValid());
+
+ PendingReady *another = mFactory->proxy(QLatin1String("org.bogus.Totally"),
+ QLatin1String("/org/bogus/Totally"),
+ ChannelFactory::create(QDBusConnection::sessionBus()),
+ ContactFactory::create());
+
+ QVERIFY(another != NULL);
+ QVERIFY(!another->object().isNull());
+ QVERIFY(!another->proxy().isNull());
+
+ QVERIFY(!ConnectionPtr::qObjectCast(another->proxy())->isValid());
+
+ // We shouldn't get the same proxy twice ie. a proxy should not be cached if not present on bus
+ // and invalidated because of that, otherwise we'll return an invalid instance from the cache
+ // even if after the service appears on the bus
+ QCOMPARE(another->object(), bogus->object());
+ QVERIFY(another->proxy() != bogus->proxy());
+
+ // The PendingReady itself should finish with failure
+ QVERIFY(connect(another, SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectFailure(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestDBusProxyFactory::cleanup()
+{
+ mFactory.reset();
+
+ cleanupImpl();
+}
+
+void TestDBusProxyFactory::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestDBusProxyFactory)
+#include "_gen/dbus-proxy-factory.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/do-nothing.cpp b/qt4/tests/dbus/do-nothing.cpp
new file mode 100644
index 000000000..bd08990cc
--- /dev/null
+++ b/qt4/tests/dbus/do-nothing.cpp
@@ -0,0 +1,64 @@
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <QtDBus/QtDBus>
+
+#include <QtTest/QtTest>
+
+#include <tests/lib/test.h>
+
+class TestDoNothing : public Test
+{
+ Q_OBJECT
+
+public:
+ TestDoNothing(QObject *parent = 0)
+ : Test(parent)
+ { }
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void doNothing();
+ void doNothing2();
+
+ void cleanup();
+ void cleanupTestCase();
+};
+
+void TestDoNothing::initTestCase()
+{
+ initTestCaseImpl();
+}
+
+void TestDoNothing::init()
+{
+ initImpl();
+}
+
+void TestDoNothing::doNothing()
+{
+ QTimer::singleShot(0, mLoop, SLOT(quit()));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestDoNothing::doNothing2()
+{
+ QTimer::singleShot(0, mLoop, SLOT(quit()));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestDoNothing::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestDoNothing::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestDoNothing)
+#include "_gen/do-nothing.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/handles.cpp b/qt4/tests/dbus/handles.cpp
new file mode 100644
index 000000000..afb283563
--- /dev/null
+++ b/qt4/tests/dbus/handles.cpp
@@ -0,0 +1,128 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/simple-conn.h>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/PendingHandles>
+#include <TelepathyQt4/ReferencedHandles>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestHandles : public Test
+{
+ Q_OBJECT
+
+public:
+ TestHandles(QObject *parent = 0)
+ : Test(parent), mConn(0)
+ { }
+
+protected Q_SLOTS:
+ void expectPendingHandlesFinished(Tp::PendingOperation*);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRequestAndRelease();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ ReferencedHandles mHandles;
+};
+
+void TestHandles::expectPendingHandlesFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingHandles *pending = qobject_cast<PendingHandles*>(op);
+ mHandles = pending->handles();
+ mLoop->exit(0);
+}
+
+void TestHandles::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("handles");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "simple",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestHandles::init()
+{
+ initImpl();
+}
+
+void TestHandles::testRequestAndRelease()
+{
+ // Test identifiers
+ QStringList ids = QStringList() << QLatin1String("alice")
+ << QLatin1String("bob") << QLatin1String("chris");
+
+ // Request handles for the identifiers and wait for the request to process
+ PendingHandles *pending = mConn->client()->lowlevel()->requestHandles(Tp::HandleTypeContact, ids);
+ QVERIFY(connect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectPendingHandlesFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(disconnect(pending,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ this,
+ SLOT(expectPendingHandlesFinished(Tp::PendingOperation*))));
+ ReferencedHandles handles = mHandles;
+ mHandles = ReferencedHandles();
+
+ // Verify that the closure indicates correctly which names we requested
+ QCOMPARE(pending->namesRequested(), ids);
+
+ // Verify by directly poking the service that the handles correspond to the requested IDs
+ TpHandleRepoIface *serviceRepo =
+ tp_base_connection_get_handles(TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ for (int i = 0; i < 3; i++) {
+ uint handle = handles[i];
+ QCOMPARE(QString::fromUtf8(tp_handle_inspect(serviceRepo, handle)), ids[i]);
+ }
+
+ // Save the handles to a non-referenced normal container
+ Tp::UIntList saveHandles = handles.toList();
+
+ // Start releasing the handles, RAII style, and complete the asynchronous process doing that
+ handles = ReferencedHandles();
+ mLoop->processEvents();
+ processDBusQueue(mConn->client().data());
+}
+
+void TestHandles::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestHandles::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestHandles)
+#include "_gen/handles.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/profile-manager.cpp b/qt4/tests/dbus/profile-manager.cpp
new file mode 100644
index 000000000..226f5bdb7
--- /dev/null
+++ b/qt4/tests/dbus/profile-manager.cpp
@@ -0,0 +1,86 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ProfileManager>
+
+#include <tests/lib/test.h>
+
+using namespace Tp;
+
+class TestProfileManager : public Test
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testProfileManager();
+};
+
+void TestProfileManager::testProfileManager()
+{
+ ProfileManagerPtr pm = ProfileManager::create(QDBusConnection::sessionBus());
+ QVERIFY(connect(pm->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(pm->isReady(), true);
+
+ QCOMPARE(pm->profiles().isEmpty(), false);
+ QCOMPARE(pm->profiles().count(), 2);
+ QCOMPARE(pm->profileForService(QLatin1String("test-profile")).isNull(), false);
+ QCOMPARE(pm->profileForService(QLatin1String("test-profile-file-not-found")).isNull(), true);
+ QCOMPARE(pm->profileForService(QLatin1String("test-profile-non-im-type")).isNull(), true);
+ QCOMPARE(pm->profilesForCM(QLatin1String("testprofilecm")).isEmpty(), false);
+ QCOMPARE(pm->profilesForCM(QLatin1String("testprofilecm")).count(), 2);
+ QCOMPARE(pm->profilesForProtocol(QLatin1String("testprofileproto")).isEmpty(), false);
+ QCOMPARE(pm->profilesForProtocol(QLatin1String("testprofileproto")).count(), 2);
+
+ QVERIFY(connect(pm->becomeReady(ProfileManager::FeatureFakeProfiles),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(pm->isReady(ProfileManager::FeatureFakeProfiles), true);
+
+ QCOMPARE(pm->profiles().isEmpty(), false);
+ QCOMPARE(pm->profiles().count(), 4);
+ QCOMPARE(pm->profileForService(QLatin1String("spurious-normal")).isNull(), false);
+ QCOMPARE(pm->profileForService(QLatin1String("spurious-weird")).isNull(), false);
+ QCOMPARE(pm->profilesForCM(QLatin1String("spurious")).isEmpty(), false);
+ QCOMPARE(pm->profilesForCM(QLatin1String("spurious")).count(), 2);
+ QCOMPARE(pm->profilesForProtocol(QLatin1String("normal")).isEmpty(), false);
+ QCOMPARE(pm->profilesForProtocol(QLatin1String("weird")).isEmpty(), false);
+
+ ProfilePtr profile = pm->profileForService(QLatin1String("spurious-normal"));
+ QCOMPARE(profile->type(), QLatin1String("IM"));
+
+ QCOMPARE(profile->provider(), QString());
+ QCOMPARE(profile->name(), QLatin1String("normal"));
+ QCOMPARE(profile->cmName(), QLatin1String("spurious"));
+ QCOMPARE(profile->protocolName(), QLatin1String("normal"));
+ QCOMPARE(profile->presences().isEmpty(), true);
+
+ QCOMPARE(profile->parameters().isEmpty(), false);
+ QCOMPARE(profile->parameters().count(), 1);
+
+ QCOMPARE(profile->hasParameter(QLatin1String("not-found")), false);
+
+ /* profile will only return CM default params, account is ignored */
+ QCOMPARE(profile->hasParameter(QLatin1String("account")), false);
+
+ /* profile will only return CM default params, password is ignored */
+ QCOMPARE(profile->hasParameter(QLatin1String("password")), false);
+
+ Profile::Parameter param = profile->parameter(QLatin1String("register"));
+ QCOMPARE(param.name(), QLatin1String("register"));
+ QCOMPARE(param.dbusSignature(), QDBusSignature(QLatin1String("b")));
+ QCOMPARE(param.type(), QVariant::Bool);
+ QCOMPARE(param.value(), QVariant(true));
+ QCOMPARE(param.label(), QString());
+ QCOMPARE(param.isMandatory(), false);
+
+ // Allow the PendingReadys to delete themselves
+ mLoop->processEvents();
+}
+
+QTEST_MAIN(TestProfileManager)
+
+#include "_gen/profile-manager.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/simple-observer.cpp b/qt4/tests/dbus/simple-observer.cpp
new file mode 100644
index 000000000..2bb7e8a51
--- /dev/null
+++ b/qt4/tests/dbus/simple-observer.cpp
@@ -0,0 +1,745 @@
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtDBus/QtDBus>
+#include <QtTest/QtTest>
+
+#include <cstring>
+
+#include <QDateTime>
+#include <QString>
+#include <QVariantMap>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/Client>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/SimpleCallObserver>
+#include <TelepathyQt4/SimpleObserver>
+#include <TelepathyQt4/SimpleTextObserver>
+#include <TelepathyQt4/StreamedMediaChannel>
+#include <TelepathyQt4/TextChannel>
+#include <TelepathyQt4/Types>
+
+#include <telepathy-glib/cm-message.h>
+#include <telepathy-glib/debug.h>
+
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/glib/echo2/chan.h>
+#include <tests/lib/glib/callable/media-channel.h>
+#include <tests/lib/test.h>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+class TestSimpleObserver;
+
+namespace
+{
+
+QList<ChannelPtr> channels(const QList<TextChannelPtr> &channels)
+{
+ QList<ChannelPtr> ret;
+ Q_FOREACH (const TextChannelPtr &channel, channels) {
+ ret << channel;
+ }
+ return ret;
+}
+
+QList<ChannelPtr> channels(const QList<StreamedMediaChannelPtr> &channels)
+{
+ QList<ChannelPtr> ret;
+ Q_FOREACH (const StreamedMediaChannelPtr &channel, channels) {
+ ret << channel;
+ }
+ return ret;
+}
+
+}
+
+class AccountAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Account")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Account\" >\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"Connection\" type=\"o\" access=\"read\" />\n"
+" <signal name=\"AccountPropertyChanged\" >\n"
+" <arg name=\"Properties\" type=\"a{sv}\" />\n"
+" </signal>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Connection READ Connection)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+
+public:
+ AccountAdaptor(QObject *parent)
+ : QDBusAbstractAdaptor(parent), mConnection(QLatin1String("/"))
+ {
+ }
+
+ virtual ~AccountAdaptor()
+ {
+ }
+
+ void setConnection(QString conn)
+ {
+ if (conn.isEmpty()) {
+ conn = QLatin1String("/");
+ }
+
+ mConnection = QDBusObjectPath(conn);
+ QVariantMap props;
+ props.insert(QLatin1String("Connection"), QVariant::fromValue(mConnection));
+ Q_EMIT AccountPropertyChanged(props);
+ }
+
+public: // Properties
+ inline QDBusObjectPath Connection() const
+ {
+ return mConnection;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return QStringList();
+ }
+
+Q_SIGNALS: // Signals
+ void AccountPropertyChanged(const QVariantMap &properties);
+
+private:
+ QDBusObjectPath mConnection;
+};
+
+class Dispatcher : public QObject, public QDBusContext
+{
+ Q_OBJECT;
+
+public:
+ Dispatcher(QObject *parent)
+ : QObject(parent)
+ {
+ }
+
+ ~Dispatcher()
+ {
+ }
+};
+
+class TestSimpleObserver : public Test
+{
+ Q_OBJECT
+
+public:
+ TestSimpleObserver(QObject *parent = 0)
+ : Test(parent),
+ mChannelsCount(0), mSMChannelsCount(0)
+ {
+ std::memset(mMessagesChanServices, 0, sizeof(mMessagesChanServices) / sizeof(ExampleEcho2Channel*));
+ std::memset(mCallableChanServices, 0, sizeof(mCallableChanServices) / sizeof(ExampleCallableMediaChannel*));
+ }
+
+protected Q_SLOTS:
+ void onObserverNewChannels(const QList<Tp::ChannelPtr> &channels);
+ void onObserverChannelInvalidated(const Tp::ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage);
+ void onObserverStreamedMediaCallStarted(
+ const Tp::StreamedMediaChannelPtr &channel);
+ void onObserverStreamedMediaCallEnded(
+ const Tp::StreamedMediaChannelPtr &channel,
+ const QString &errorMessage, const QString &errorName);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testObserverRegistration();
+ void testCrossTalk();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QMap<QString, QString> ourObservers();
+
+ AccountPtr mAccounts[2];
+
+ struct ConnInfo {
+ ConnInfo()
+ : connService(0), baseConnService(0), contactRepo(0)
+ {
+ }
+
+ TpTestsContactsConnection *connService;
+ TpBaseConnection *baseConnService;
+ ConnectionPtr conn;
+ TpHandleRepoIface *contactRepo;
+ };
+ ConnInfo mConns[2];
+
+ QStringList mContacts;
+
+ ExampleEcho2Channel *mMessagesChanServices[2];
+ TextChannelPtr mTextChans[2];
+
+ ExampleCallableMediaChannel *mCallableChanServices[2];
+ StreamedMediaChannelPtr mSMChans[2];
+
+ int mChannelsCount;
+ int mSMChannelsCount;
+};
+
+void TestSimpleObserver::onObserverNewChannels(const QList<Tp::ChannelPtr> &channels)
+{
+ QVERIFY(channels.size() == 1);
+ mChannelsCount += channels.size();
+}
+
+void TestSimpleObserver::onObserverChannelInvalidated(const Tp::ChannelPtr &channel,
+ const QString &errorName, const QString &errorMessage)
+{
+ QVERIFY(!channel.isNull());
+ mChannelsCount -= 1;
+}
+
+void TestSimpleObserver::onObserverStreamedMediaCallStarted(
+ const Tp::StreamedMediaChannelPtr &channel)
+{
+ QVERIFY(!channel.isNull());
+ mSMChannelsCount++;
+}
+
+void TestSimpleObserver::onObserverStreamedMediaCallEnded(const Tp::StreamedMediaChannelPtr &channel,
+ const QString &errorMessage, const QString &errorName)
+{
+ QVERIFY(!channel.isNull());
+ mSMChannelsCount--;
+}
+
+void TestSimpleObserver::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("simple-observer");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QString channelDispatcherBusName = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER);
+ QString channelDispatcherPath = QLatin1String("/org/freedesktop/Telepathy/ChannelDispatcher");
+ Dispatcher *dispatcher = new Dispatcher(this);
+ QVERIFY(bus.registerService(channelDispatcherBusName));
+ QVERIFY(bus.registerObject(channelDispatcherPath, dispatcher));
+
+ mContacts << QLatin1String("alice") << QLatin1String("bob");
+
+ // Create 2 accounts to be used by the tests:
+ // - each account contains a connection, a text channel and a SM channel setup:
+ // - the channels for the first account have alice as target and;
+ // - the channels for the second account have bob as target
+ for (int i = 0; i < 2; ++i) {
+ // setup account
+ QString accountBusName = TP_QT4_IFACE_ACCOUNT_MANAGER;
+ QString accountPath = QLatin1String("/org/freedesktop/Telepathy/Account/simple/account/") +
+ QString::number(i);
+
+ QObject *adaptorObject = new QObject(this);
+ AccountAdaptor *accountAdaptor = new AccountAdaptor(adaptorObject);
+ QVERIFY(bus.registerService(accountBusName));
+ QVERIFY(bus.registerObject(accountPath, adaptorObject));
+
+ AccountPtr acc = Account::create(accountBusName, accountPath);
+ QVERIFY(connect(acc->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(acc->isReady(), true);
+ QCOMPARE(acc->supportsRequestHints(), false);
+ QCOMPARE(acc->requestsSucceedWithChannel(), false);
+ mAccounts[i] = acc;
+
+ // setup conn
+ TpTestsContactsConnection *connService = TP_TESTS_CONTACTS_CONNECTION(
+ g_object_new(TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL));
+ QVERIFY(connService != 0);
+ TpBaseConnection *baseConnService = TP_BASE_CONNECTION(connService);
+ QVERIFY(baseConnService != 0);
+
+ gchar *connName, *connPath;
+ GError *error = NULL;
+
+ QString name(QLatin1String("example") + QString::number(i));
+ QVERIFY(tp_base_connection_register(baseConnService,
+ name.toLatin1().constData(), &connName, &connPath, &error));
+ QVERIFY(error == 0);
+
+ QVERIFY(connName != 0);
+ QVERIFY(connPath != 0);
+
+ ConnectionPtr conn = Connection::create(QLatin1String(connName), QLatin1String(connPath),
+ ChannelFactory::create(QDBusConnection::sessionBus()), ContactFactory::create());
+ QCOMPARE(conn->isReady(), false);
+
+ accountAdaptor->setConnection(QLatin1String(connPath));
+
+ QVERIFY(connect(conn->lowlevel()->requestConnect(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(conn->isReady(), true);
+ QCOMPARE(conn->status(), ConnectionStatusConnected);
+
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(baseConnService,
+ TP_HANDLE_TYPE_CONTACT);
+
+ mConns[i].connService = connService;
+ mConns[i].baseConnService = baseConnService;
+ mConns[i].conn = conn;
+ mConns[i].contactRepo = contactRepo;
+
+ // setup channels
+ guint handle = tp_handle_ensure(contactRepo, mContacts[i].toLatin1().constData(), 0, 0);
+
+ QString messagesChanPath = QLatin1String(connPath) +
+ QLatin1String("/MessagesChannel/") + QString::number(i);
+ mMessagesChanServices[i] = EXAMPLE_ECHO_2_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_2_CHANNEL,
+ "connection", connService,
+ "object-path", messagesChanPath.toLatin1().constData(),
+ "handle", handle,
+ NULL));
+
+ QVariantMap immutableProperties;
+ immutableProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetID"), mContacts[i]);
+ mTextChans[i] = TextChannel::create(conn, messagesChanPath, immutableProperties);
+ QVERIFY(connect(mTextChans[i]->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QString callableChanPath = QLatin1String(connPath) +
+ QLatin1String("/CallableChannel/") + QString::number(i);
+ mCallableChanServices[i] = EXAMPLE_CALLABLE_MEDIA_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ "connection", connService,
+ "object-path", callableChanPath.toLatin1().constData(),
+ "handle", handle,
+ NULL));
+
+ immutableProperties.clear();
+ immutableProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetID"), mContacts[i]);
+ mSMChans[i] = StreamedMediaChannel::create(conn, callableChanPath, immutableProperties);
+ QVERIFY(connect(mSMChans[i]->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ g_free(connName);
+ g_free(connPath);
+
+ tp_handle_unref(contactRepo, handle);
+ }
+}
+
+void TestSimpleObserver::init()
+{
+ initImpl();
+}
+
+void TestSimpleObserver::testObserverRegistration()
+{
+ QVERIFY(ourObservers().isEmpty());
+
+ QList<SimpleObserverPtr> observers;
+ QList<SimpleTextObserverPtr> textObservers;
+ QList<SimpleCallObserverPtr> callObservers;
+ int numRegisteredObservers = 0;
+ // Observers should be shared by bus and channel class, meaning that
+ // the following code should register only 4 observers:
+ // - one for text chat rooms
+ // - one for text chats
+ // - one for incoming/outgoing calls
+ // - one for incoming calls (incoming)
+ //
+ // The Simple*Observer instances are added to the lists observers/textObservers/callObservers,
+ // so they don't get deleted (refcount == 0) when out of scope
+ for (int i = 0; i < 2; ++i) {
+ AccountPtr acc = mAccounts[i];
+
+ for (int j = 0; j < 2; ++j) {
+ QString contact = mContacts[j];
+
+ if (i == 0 && j == 0) {
+ numRegisteredObservers = 1;
+ }
+
+ // on first run the following code should register an observer for text chat rooms,
+ // on consecutive runs it should use the already registered observer for text chat rooms
+ SimpleObserverPtr observer = SimpleObserver::create(acc,
+ ChannelClassSpec::textChatroom(), contact);
+ QCOMPARE(observer->account(), acc);
+ QVERIFY(observer->channelFilter().size() == 1);
+ QVERIFY(observer->channelFilter().contains(ChannelClassSpec::textChatroom()));
+ QCOMPARE(observer->contactIdentifier(), contact);
+ QVERIFY(observer->extraChannelFeatures().isEmpty());
+ QVERIFY(observer->channels().isEmpty());
+ observers.append(observer);
+ QCOMPARE(ourObservers().size(), numRegisteredObservers);
+
+ // the following code should always reuse the observer for text chat rooms already
+ // created.
+ QList<ChannelClassFeatures> extraChannelFeatures;
+ extraChannelFeatures.append(QPair<ChannelClassSpec, Features>(
+ ChannelClassSpec::textChatroom(), Channel::FeatureCore));
+ observer = SimpleObserver::create(acc,
+ ChannelClassSpec::textChatroom(), contact, extraChannelFeatures);
+ QCOMPARE(observer->account(), acc);
+ QVERIFY(observer->channelFilter().size() == 1);
+ QVERIFY(observer->channelFilter().contains(ChannelClassSpec::textChatroom()));
+ QCOMPARE(observer->contactIdentifier(), contact);
+ QCOMPARE(observer->extraChannelFeatures(), extraChannelFeatures);
+ QVERIFY(observer->channels().isEmpty());
+ observers.append(observer);
+ QCOMPARE(ourObservers().size(), numRegisteredObservers);
+
+ if (i == 0 && j == 0) {
+ numRegisteredObservers = 2;
+ }
+
+ // on first run the following code should register an observer for text chats,
+ // on consecutive runs it should use the already registered observer for text chats
+ SimpleTextObserverPtr textObserver = SimpleTextObserver::create(acc, contact);
+ QCOMPARE(textObserver->account(), acc);
+ QCOMPARE(textObserver->contactIdentifier(), contact);
+ QVERIFY(textObserver->textChats().isEmpty());
+ textObservers.append(textObserver);
+ QCOMPARE(ourObservers().size(), numRegisteredObservers);
+
+ // the following code should always reuse the observer for text chats already
+ // created.
+ textObserver = SimpleTextObserver::create(acc, contact);
+ QCOMPARE(textObserver->account(), acc);
+ QCOMPARE(textObserver->contactIdentifier(), contact);
+ QVERIFY(textObserver->textChats().isEmpty());
+ textObservers.append(textObserver);
+ QCOMPARE(ourObservers().size(), numRegisteredObservers);
+
+ if (i == 0 && j == 0) {
+ numRegisteredObservers = 3;
+ }
+
+ // on first run the following code should register an observer for incoming/outgoing
+ // calls, on consecutive runs it should use the already registered observer for
+ // incoming/outgoing calls
+ SimpleCallObserverPtr callObserver = SimpleCallObserver::create(acc, contact);
+ QCOMPARE(callObserver->account(), acc);
+ QCOMPARE(callObserver->contactIdentifier(), contact);
+ QCOMPARE(callObserver->direction(), SimpleCallObserver::CallDirectionBoth);
+ QVERIFY(callObserver->streamedMediaCalls().isEmpty());
+ callObservers.append(callObserver);
+ QCOMPARE(ourObservers().size(), numRegisteredObservers);
+
+ if (i == 0 && j == 0) {
+ numRegisteredObservers = 4;
+ }
+
+ // on first run the following code should register an observer for incoming calls,
+ // on consecutive runs it should use the already registered observer for incoming calls
+ callObserver = SimpleCallObserver::create(acc, contact,
+ SimpleCallObserver::CallDirectionIncoming);
+ QCOMPARE(callObserver->account(), acc);
+ QCOMPARE(callObserver->contactIdentifier(), contact);
+ QCOMPARE(callObserver->direction(), SimpleCallObserver::CallDirectionIncoming);
+ QVERIFY(callObserver->streamedMediaCalls().isEmpty());
+ callObservers.append(callObserver);
+ QCOMPARE(ourObservers().size(), numRegisteredObservers);
+ }
+ }
+
+ // deleting all SimpleObserver instances (text chat room) should unregister 1 observer
+ observers.clear();
+ QCOMPARE(ourObservers().size(), 3);
+ // deleting all SimpleTextObserver instances should unregister 1 observer
+ textObservers.clear();
+ QCOMPARE(ourObservers().size(), 2);
+ // deleting all SimpleCallObserver instances should unregister 2 observers
+ callObservers.clear();
+ QVERIFY(ourObservers().isEmpty());
+}
+
+void TestSimpleObserver::testCrossTalk()
+{
+ SimpleObserverPtr observers[2];
+ SimpleTextObserverPtr textObservers[2];
+ SimpleTextObserverPtr textObserversNoContact[2];
+ SimpleCallObserverPtr callObservers[2];
+ SimpleCallObserverPtr callObserversNoContact[2];
+
+ for (int i = 0; i < 2; ++i) {
+ AccountPtr acc = mAccounts[i];
+ observers[i] = SimpleObserver::create(acc,
+ ChannelClassSpec::textChat(), mContacts[i]);
+ QVERIFY(connect(observers[i].data(), SIGNAL(newChannels(QList<Tp::ChannelPtr>)),
+ SLOT(onObserverNewChannels(QList<Tp::ChannelPtr>))));
+ QVERIFY(connect(observers[i].data(),
+ SIGNAL(channelInvalidated(Tp::ChannelPtr,QString,QString)),
+ SLOT(onObserverChannelInvalidated(Tp::ChannelPtr,QString,QString))));
+
+ // SimpleTextObserver::messageSent/Received is already tested in contact-messenger test
+ textObservers[i] = SimpleTextObserver::create(acc, mContacts[i]);
+ textObserversNoContact[i] = SimpleTextObserver::create(acc);
+
+ callObservers[i] = SimpleCallObserver::create(acc, mContacts[i]);
+ QVERIFY(connect(callObservers[i].data(), SIGNAL(streamedMediaCallStarted(Tp::StreamedMediaChannelPtr)),
+ SLOT(onObserverStreamedMediaCallStarted(Tp::StreamedMediaChannelPtr))));
+ QVERIFY(connect(callObservers[i].data(), SIGNAL(streamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString)),
+ SLOT(onObserverStreamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString))));
+ callObserversNoContact[i] = SimpleCallObserver::create(acc);
+ }
+
+ QMap<QString, QString> ourObserversMap = ourObservers();
+ QMap<QString, QString>::const_iterator it = ourObserversMap.constBegin();
+ QMap<QString, QString>::const_iterator end = ourObserversMap.constEnd();
+ for (; it != end; ++it) {
+ ClientObserverInterface *observerIface = new ClientObserverInterface(
+ it.key(), it.value(), this);
+
+ ChannelClassList observerFilter;
+ if (!waitForProperty(observerIface->requestPropertyObserverChannelFilter(),
+ &observerFilter)) {
+ continue;
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ Q_FOREACH (const ChannelClassSpec &spec, observerFilter) {
+ // only call ObserveChannels for text chat channels on observers that support text
+ // chat
+ if (spec.isSubsetOf(ChannelClassSpec::textChat())) {
+ ChannelDetails textChan = {
+ QDBusObjectPath(mTextChans[i]->objectPath()),
+ mTextChans[i]->immutableProperties()
+ };
+ observerIface->ObserveChannels(
+ QDBusObjectPath(mAccounts[i]->objectPath()),
+ QDBusObjectPath(mTextChans[i]->connection()->objectPath()),
+ ChannelDetailsList() << textChan,
+ QDBusObjectPath(QLatin1String("/")),
+ Tp::ObjectPathList(),
+ QVariantMap());
+ break;
+ }
+ }
+
+ Q_FOREACH (const ChannelClassSpec &spec, observerFilter) {
+ // only call ObserveChannels for SM channels on observers that support SM channels
+ if (spec.isSubsetOf(ChannelClassSpec::streamedMediaCall())) {
+ ChannelDetails smChan = {
+ QDBusObjectPath(mSMChans[i]->objectPath()),
+ mSMChans[i]->immutableProperties()
+ };
+ observerIface->ObserveChannels(
+ QDBusObjectPath(mAccounts[i]->objectPath()),
+ QDBusObjectPath(mSMChans[i]->connection()->objectPath()),
+ ChannelDetailsList() << smChan,
+ QDBusObjectPath(QLatin1String("/")),
+ Tp::ObjectPathList(),
+ QVariantMap());
+ break;
+ }
+ }
+ }
+ }
+
+ // due to QtDBus limitation, we cannot wait for ObserveChannels to finish properly before
+ // proceeding, so we have to wait till all observers receive the channels
+ while (observers[0]->channels().isEmpty() ||
+ observers[1]->channels().isEmpty() ||
+ textObservers[0]->textChats().isEmpty() ||
+ textObservers[1]->textChats().isEmpty() ||
+ textObserversNoContact[0]->textChats().isEmpty() ||
+ textObserversNoContact[1]->textChats().isEmpty() ||
+ callObservers[0]->streamedMediaCalls().isEmpty() ||
+ callObservers[1]->streamedMediaCalls().isEmpty() ||
+ callObserversNoContact[0]->streamedMediaCalls().isEmpty() ||
+ callObserversNoContact[1]->streamedMediaCalls().isEmpty()) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mChannelsCount, 2);
+ QCOMPARE(mSMChannelsCount, 2);
+
+ QCOMPARE(observers[0]->channels().size(), 1);
+ QCOMPARE(textObservers[0]->textChats().size(), 1);
+ QCOMPARE(textObserversNoContact[0]->textChats().size(), 1);
+ QCOMPARE(callObservers[0]->streamedMediaCalls().size(), 1);
+ QCOMPARE(callObserversNoContact[0]->streamedMediaCalls().size(), 1);
+ QCOMPARE(observers[1]->channels().size(), 1);
+ QCOMPARE(textObservers[1]->textChats().size(), 1);
+ QCOMPARE(textObserversNoContact[1]->textChats().size(), 1);
+ QCOMPARE(callObservers[1]->streamedMediaCalls().size(), 1);
+ QCOMPARE(callObserversNoContact[1]->streamedMediaCalls().size(), 1);
+
+ QVERIFY(textObservers[0]->textChats() != textObservers[1]->textChats());
+ QVERIFY(textObserversNoContact[0]->textChats() != textObserversNoContact[1]->textChats());
+ QCOMPARE(textObservers[0]->textChats(), textObserversNoContact[0]->textChats());
+ QCOMPARE(textObservers[1]->textChats(), textObserversNoContact[1]->textChats());
+
+ QVERIFY(callObservers[0]->streamedMediaCalls() != callObservers[1]->streamedMediaCalls());
+ QVERIFY(callObservers[0]->streamedMediaCalls() != callObserversNoContact[1]->streamedMediaCalls());
+ QCOMPARE(callObservers[0]->streamedMediaCalls(), callObserversNoContact[0]->streamedMediaCalls());
+ QCOMPARE(callObservers[1]->streamedMediaCalls(), callObserversNoContact[1]->streamedMediaCalls());
+
+ QCOMPARE(observers[0]->channels(), channels(textObservers[0]->textChats()));
+ QVERIFY(observers[0]->channels() != channels(textObservers[1]->textChats()));
+ QVERIFY(observers[0]->channels() != channels(callObservers[0]->streamedMediaCalls()));
+ QVERIFY(observers[0]->channels() != channels(callObservers[1]->streamedMediaCalls()));
+ QVERIFY(observers[1]->channels() != channels(textObservers[0]->textChats()));
+ QCOMPARE(observers[1]->channels(), channels(textObservers[1]->textChats()));
+ QVERIFY(observers[1]->channels() != channels(callObservers[0]->streamedMediaCalls()));
+ QVERIFY(observers[1]->channels() != channels(callObservers[1]->streamedMediaCalls()));
+
+ QVERIFY(channels(callObservers[0]->streamedMediaCalls()) != channels(textObservers[0]->textChats()));
+ QVERIFY(channels(callObservers[0]->streamedMediaCalls()) != channels(textObservers[1]->textChats()));
+ QVERIFY(channels(callObservers[1]->streamedMediaCalls()) != channels(textObservers[0]->textChats()));
+ QVERIFY(channels(callObservers[1]->streamedMediaCalls()) != channels(textObservers[1]->textChats()));
+
+ QCOMPARE(observers[0]->channels().first()->objectPath(), mTextChans[0]->objectPath());
+ QCOMPARE(observers[1]->channels().first()->objectPath(), mTextChans[1]->objectPath());
+ QCOMPARE(textObservers[0]->textChats().first()->objectPath(), mTextChans[0]->objectPath());
+ QCOMPARE(textObservers[1]->textChats().first()->objectPath(), mTextChans[1]->objectPath());
+ QCOMPARE(textObserversNoContact[0]->textChats().first()->objectPath(), mTextChans[0]->objectPath());
+ QCOMPARE(textObserversNoContact[1]->textChats().first()->objectPath(), mTextChans[1]->objectPath());
+ QCOMPARE(callObservers[0]->streamedMediaCalls().first()->objectPath(), mSMChans[0]->objectPath());
+ QCOMPARE(callObservers[1]->streamedMediaCalls().first()->objectPath(), mSMChans[1]->objectPath());
+
+ // invalidate channels
+ for (int i = 0; i < 2; ++i) {
+ if (mMessagesChanServices[i] != 0) {
+ g_object_unref(mMessagesChanServices[i]);
+ mMessagesChanServices[i] = 0;
+ }
+
+ if (mCallableChanServices[i] != 0) {
+ g_object_unref(mCallableChanServices[i]);
+ mCallableChanServices[i] = 0;
+ }
+ }
+
+ while (!observers[0]->channels().isEmpty() ||
+ !observers[1]->channels().isEmpty() ||
+ !textObservers[0]->textChats().isEmpty() ||
+ !textObservers[1]->textChats().isEmpty() ||
+ !textObserversNoContact[0]->textChats().isEmpty() ||
+ !textObserversNoContact[1]->textChats().isEmpty() ||
+ !callObservers[0]->streamedMediaCalls().isEmpty() ||
+ !callObservers[1]->streamedMediaCalls().isEmpty() ||
+ !callObserversNoContact[0]->streamedMediaCalls().isEmpty() ||
+ !callObserversNoContact[1]->streamedMediaCalls().isEmpty()) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mChannelsCount, 0);
+ QCOMPARE(mSMChannelsCount, 0);
+}
+
+void TestSimpleObserver::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestSimpleObserver::cleanupTestCase()
+{
+ for (int i = 0; i < 2; ++i) {
+ if (!mConns[i].conn) {
+ continue;
+ }
+
+ if (mConns[i].conn->requestedFeatures().contains(Connection::FeatureCore)) {
+ QVERIFY(mConns[i].connService != NULL);
+
+ if (TP_BASE_CONNECTION(mConns[i].connService)->status != TP_CONNECTION_STATUS_DISCONNECTED) {
+ tp_base_connection_change_status(TP_BASE_CONNECTION(mConns[i].connService),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+ }
+
+ while (mConns[i].conn->isValid()) {
+ mLoop->processEvents();
+ }
+
+ }
+ mConns[i].conn.reset();
+
+ mTextChans[i].reset();
+ mSMChans[i].reset();
+
+ if (mMessagesChanServices[i] != 0) {
+ g_object_unref(mMessagesChanServices[i]);
+ mMessagesChanServices[i] = 0;
+ }
+
+ if (mCallableChanServices[i] != 0) {
+ g_object_unref(mCallableChanServices[i]);
+ mCallableChanServices[i] = 0;
+ }
+
+ if (mConns[i].connService != 0) {
+ mConns[i].baseConnService = 0;
+ g_object_unref(mConns[i].connService);
+ mConns[i].connService = 0;
+ }
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QMap<QString, QString> TestSimpleObserver::ourObservers()
+{
+ QStringList registeredNames =
+ QDBusConnection::sessionBus().interface()->registeredServiceNames();
+ QMap<QString, QString> observers;
+
+ Q_FOREACH (QString name, registeredNames) {
+ if (!name.startsWith(QLatin1String("org.freedesktop.Telepathy.Client.TpQt4SO"))) {
+ continue;
+ }
+
+ if (QDBusConnection::sessionBus().interface()->serviceOwner(name).value() !=
+ QDBusConnection::sessionBus().baseService()) {
+ continue;
+ }
+
+ QString path = QLatin1Char('/') + name;
+ path.replace(QLatin1Char('.'), QLatin1Char('/'));
+
+ ClientInterface client(name, path);
+ QStringList ifaces;
+ if (!waitForProperty(client.requestPropertyInterfaces(), &ifaces)) {
+ continue;
+ }
+
+ if (!ifaces.contains(TP_QT4_IFACE_CLIENT_OBSERVER)) {
+ continue;
+ }
+
+ observers.insert(name, path);
+ }
+
+ return observers;
+}
+
+QTEST_MAIN(TestSimpleObserver)
+#include "_gen/simple-observer.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/stateful-proxy.cpp b/qt4/tests/dbus/stateful-proxy.cpp
new file mode 100644
index 000000000..e76ac8676
--- /dev/null
+++ b/qt4/tests/dbus/stateful-proxy.cpp
@@ -0,0 +1,313 @@
+/**
+ * This file is part of TelepathyQt4
+ *
+ * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * @copyright Copyright (C) 2009 Nokia Corporation
+ * @license LGPL 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <QEventLoop>
+#include <QtTest>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/StatefulDBusProxy>
+
+#include "tests/lib/test.h"
+
+using namespace Tp;
+using namespace Tp;
+using Tp::Client::DBus::IntrospectableInterface;
+
+// expose protected functions for testing
+class MyStatefulDBusProxy : public StatefulDBusProxy
+{
+public:
+ MyStatefulDBusProxy(const QDBusConnection &dbusConnection,
+ const QString &busName, const QString &objectPath)
+ : StatefulDBusProxy(dbusConnection, busName, objectPath, Feature())
+ {
+ }
+
+ using StatefulDBusProxy::invalidate;
+};
+
+class ObjectAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "com.example.Foo")
+
+public:
+ ObjectAdaptor(Test *test)
+ : QDBusAbstractAdaptor(test)
+ {
+ }
+};
+
+class TestStatefulProxy : public Test
+{
+ Q_OBJECT
+
+public:
+ TestStatefulProxy(QObject *parent = 0);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testBasics();
+ void testNameOwnerChanged();
+
+ void cleanup();
+ void cleanupTestCase();
+
+protected Q_SLOTS:
+ // these would be public, but then QtTest would think they were tests
+ void expectInvalidated(Tp::DBusProxy *,
+ const QString &, const QString &);
+
+ // anything other than 0 or 1 is OK
+# define EXPECT_INVALIDATED_SUCCESS 111
+
+private:
+ MyStatefulDBusProxy *mProxy;
+ ObjectAdaptor *mAdaptor;
+
+ int mInvalidated;
+ QString mSignalledInvalidationReason;
+ QString mSignalledInvalidationMessage;
+
+ static QString wellKnownName();
+ static QString objectPath();
+ static QString uniqueName();
+};
+
+QString TestStatefulProxy::wellKnownName()
+{
+ return QLatin1String("org.freedesktop.Telepathy.Qt4.TestStatefulProxy");
+}
+
+QString TestStatefulProxy::objectPath()
+{
+ return QLatin1String("/org/freedesktop/Telepathy/Qt4/TestStatefulProxy/Object");
+}
+
+TestStatefulProxy::TestStatefulProxy(QObject *parent)
+ : Test(parent),
+ mProxy(0),
+ mAdaptor(new ObjectAdaptor(this))
+{
+}
+
+QString TestStatefulProxy::uniqueName()
+{
+ return QDBusConnection::sessionBus().baseService();
+}
+
+void TestStatefulProxy::initTestCase()
+{
+ initTestCaseImpl();
+
+ QVERIFY(QDBusConnection::sessionBus().registerService(wellKnownName()));
+ QDBusConnection::sessionBus().registerObject(objectPath(), this);
+}
+
+void TestStatefulProxy::init()
+{
+ initImpl();
+
+ mInvalidated = 0;
+}
+
+void TestStatefulProxy::testBasics()
+{
+ mProxy = new MyStatefulDBusProxy(QDBusConnection::sessionBus(),
+ wellKnownName(), objectPath());
+ IntrospectableInterface ifaceFromProxy(mProxy);
+ IntrospectableInterface ifaceFromWellKnown(wellKnownName(), objectPath());
+ IntrospectableInterface ifaceFromUnique(uniqueName(), objectPath());
+
+ QVERIFY(mProxy);
+ QCOMPARE(mProxy->dbusConnection().baseService(), uniqueName());
+ QCOMPARE(mProxy->busName(), uniqueName());
+ QCOMPARE(mProxy->objectPath(), objectPath());
+
+ QVERIFY(mProxy->isValid());
+ QCOMPARE(mProxy->invalidationReason(), QString());
+ QCOMPARE(mProxy->invalidationMessage(), QString());
+
+ QDBusPendingReply<QString> reply;
+ QDBusPendingCallWatcher *watcher;
+
+ reply = ifaceFromUnique.Introspect();
+ if (!reply.isValid()) {
+ watcher = new QDBusPendingCallWatcher(reply);
+ QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)),
+ this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher *))));
+ QCOMPARE(mLoop->exec(), 0);
+ delete watcher;
+ }
+
+ reply = ifaceFromWellKnown.Introspect();
+ if (!reply.isValid()) {
+ watcher = new QDBusPendingCallWatcher(reply);
+ QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)),
+ this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher *))));
+ QCOMPARE(mLoop->exec(), 0);
+ delete watcher;
+ }
+
+ reply = ifaceFromProxy.Introspect();
+ if (!reply.isValid()) {
+ watcher = new QDBusPendingCallWatcher(reply);
+ QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)),
+ this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher *))));
+ QCOMPARE(mLoop->exec(), 0);
+ delete watcher;
+ }
+
+ QVERIFY(connect(mProxy, SIGNAL(invalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &)),
+ this, SLOT(expectInvalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &))));
+ mProxy->invalidate(QLatin1String("com.example.DomainSpecificError"),
+ QLatin1String("Because I said so"));
+
+ QVERIFY(!mProxy->isValid());
+ QCOMPARE(mProxy->invalidationReason(),
+ QLatin1String("com.example.DomainSpecificError"));
+ QCOMPARE(mProxy->invalidationMessage(),
+ QLatin1String("Because I said so"));
+
+ // FIXME: ideally, the method call would already fail synchronously at
+ // this point - after all, the proxy already knows it's dead
+ reply = ifaceFromProxy.Introspect();
+ if (reply.isValid()) {
+ qWarning() << "reply is valid";
+ } else if (reply.isError()) {
+ qDebug() << "reply is error" << reply.error().name()
+ << reply.error().message();
+ } else {
+ qWarning() << "no reply yet";
+ }
+
+ // the signal doesn't arrive instantly
+ QCOMPARE(mLoop->exec(), EXPECT_INVALIDATED_SUCCESS);
+ QVERIFY(disconnect(mProxy, SIGNAL(invalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &)),
+ this, SLOT(expectInvalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &))));
+
+ QCOMPARE(mInvalidated, 1);
+ QCOMPARE(mSignalledInvalidationReason,
+ QLatin1String("com.example.DomainSpecificError"));
+ QCOMPARE(mSignalledInvalidationMessage,
+ QLatin1String("Because I said so"));
+
+ // low-level proxies with no knowledge of the high-level DBusProxy are
+ // unaffected
+
+ reply = ifaceFromUnique.Introspect();
+ if (!reply.isValid()) {
+ watcher = new QDBusPendingCallWatcher(reply);
+ QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)),
+ this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher *))));
+ QCOMPARE(mLoop->exec(), 0);
+ delete watcher;
+ }
+
+ reply = ifaceFromWellKnown.Introspect();
+ if (!reply.isValid()) {
+ watcher = new QDBusPendingCallWatcher(reply);
+ QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)),
+ this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher *))));
+ QCOMPARE(mLoop->exec(), 0);
+ delete watcher;
+ }
+}
+
+void TestStatefulProxy::expectInvalidated(DBusProxy *proxy,
+ const QString &reason, const QString &message)
+{
+ mInvalidated++;
+ mSignalledInvalidationReason = reason;
+ mSignalledInvalidationMessage = message;
+ mLoop->exit(EXPECT_INVALIDATED_SUCCESS);
+}
+
+void TestStatefulProxy::testNameOwnerChanged()
+{
+ QString otherUniqueName = QDBusConnection::connectToBus(
+ QDBusConnection::SessionBus,
+ QLatin1String("another unique name")).baseService();
+
+ mProxy = new MyStatefulDBusProxy(QDBusConnection::sessionBus(),
+ otherUniqueName, objectPath());
+
+ QVERIFY(mProxy->isValid());
+ QCOMPARE(mProxy->invalidationReason(), QString());
+ QCOMPARE(mProxy->invalidationMessage(), QString());
+
+ QVERIFY(connect(mProxy, SIGNAL(invalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &)),
+ this, SLOT(expectInvalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &))));
+ QDBusConnection::disconnectFromBus(QLatin1String("another unique name"));
+ QCOMPARE(mLoop->exec(), EXPECT_INVALIDATED_SUCCESS);
+ QVERIFY(disconnect(mProxy, SIGNAL(invalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &)),
+ this, SLOT(expectInvalidated(
+ Tp::DBusProxy *,
+ const QString &, const QString &))));
+
+ QCOMPARE(mInvalidated, 1);
+ QVERIFY(!mProxy->isValid());
+ QCOMPARE(mProxy->invalidationReason(),
+ QLatin1String(TELEPATHY_DBUS_ERROR_NAME_HAS_NO_OWNER));
+ QCOMPARE(mSignalledInvalidationReason,
+ QLatin1String(TELEPATHY_DBUS_ERROR_NAME_HAS_NO_OWNER));
+ QVERIFY(!mProxy->invalidationMessage().isEmpty());
+ QCOMPARE(mProxy->invalidationMessage(), mSignalledInvalidationMessage);
+}
+
+void TestStatefulProxy::cleanup()
+{
+ if (mProxy) {
+ delete mProxy;
+ mProxy = 0;
+ }
+
+ cleanupImpl();
+}
+
+void TestStatefulProxy::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestStatefulProxy)
+
+#include "_gen/stateful-proxy.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/stream-tube-chan.cpp b/qt4/tests/dbus/stream-tube-chan.cpp
new file mode 100644
index 000000000..5e159c124
--- /dev/null
+++ b/qt4/tests/dbus/stream-tube-chan.cpp
@@ -0,0 +1,907 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/simple-conn.h>
+#include <tests/lib/glib/stream-tube-chan.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/PendingStreamTubeConnection>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/StreamTubeChannel>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <QHostAddress>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QTcpServer>
+#include <QTcpSocket>
+
+#include <stdio.h>
+
+using namespace Tp;
+
+namespace
+{
+
+struct TestContext
+{
+ bool withContact;
+ TpSocketAddressType addressType;
+ TpSocketAccessControl accessControl;
+};
+
+// FIXME: Enable IPv6 and Port access control tests
+TestContext contexts[] = {
+ { FALSE, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST },
+ { FALSE, TP_SOCKET_ADDRESS_TYPE_IPV4, TP_SOCKET_ACCESS_CONTROL_LOCALHOST },
+ //{ FALSE, TP_SOCKET_ADDRESS_TYPE_IPV6, TP_SOCKET_ACCESS_CONTROL_LOCALHOST },
+ { FALSE, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_CREDENTIALS },
+ { FALSE, TP_SOCKET_ADDRESS_TYPE_IPV4, TP_SOCKET_ACCESS_CONTROL_PORT },
+
+ { TRUE, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_LOCALHOST },
+ { TRUE, TP_SOCKET_ADDRESS_TYPE_IPV4, TP_SOCKET_ACCESS_CONTROL_LOCALHOST },
+ //{ TRUE, TP_SOCKET_ADDRESS_TYPE_IPV6, TP_SOCKET_ACCESS_CONTROL_LOCALHOST },
+ { TRUE, TP_SOCKET_ADDRESS_TYPE_UNIX, TP_SOCKET_ACCESS_CONTROL_CREDENTIALS },
+ { TRUE, TP_SOCKET_ADDRESS_TYPE_IPV4, TP_SOCKET_ACCESS_CONTROL_PORT },
+
+ { FALSE, (TpSocketAddressType) NUM_TP_SOCKET_ADDRESS_TYPES, (TpSocketAccessControl) NUM_TP_SOCKET_ACCESS_CONTROLS }
+};
+
+void destroySocketControlList(gpointer data)
+{
+ g_array_free((GArray *) data, TRUE);
+}
+
+GHashTable *createSupportedSocketTypesHash(TpSocketAddressType addressType,
+ TpSocketAccessControl accessControl)
+{
+ GHashTable *ret;
+ GArray *tab;
+
+ ret = g_hash_table_new_full(NULL, NULL, NULL, destroySocketControlList);
+
+ tab = g_array_sized_new(FALSE, FALSE, sizeof(TpSocketAccessControl), 1);
+ g_array_append_val(tab, accessControl);
+
+ g_hash_table_insert(ret, GUINT_TO_POINTER(addressType), tab);
+
+ return ret;
+}
+
+GSocket *createTcpClientGSocket(TpSocketAddressType socketType)
+{
+ Q_ASSERT(socketType != TP_SOCKET_ADDRESS_TYPE_UNIX);
+
+ GSocketFamily family = (GSocketFamily) 0;
+ switch (socketType) {
+ case TP_SOCKET_ADDRESS_TYPE_UNIX:
+ family = G_SOCKET_FAMILY_UNIX;
+ break;
+
+ case TP_SOCKET_ADDRESS_TYPE_IPV4:
+ family = G_SOCKET_FAMILY_IPV4;
+ break;
+
+ case TP_SOCKET_ADDRESS_TYPE_IPV6:
+ family = G_SOCKET_FAMILY_IPV6;
+ break;
+
+ default:
+ Q_ASSERT(false);
+ }
+
+ /* Create socket to connect to the CM */
+ GError *error = NULL;
+ GSocket *clientSocket = g_socket_new(family, G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT, &error);
+ Q_ASSERT(clientSocket != NULL);
+
+ if (socketType == TP_SOCKET_ADDRESS_TYPE_IPV4 ||
+ socketType == TP_SOCKET_ADDRESS_TYPE_IPV6) {
+ /* Bind local address */
+ GSocketAddress *localAddress;
+ GInetAddress *tmp;
+ gboolean success;
+
+ tmp = g_inet_address_new_loopback(family);
+ localAddress = g_inet_socket_address_new(tmp, 0);
+
+ success = g_socket_bind(clientSocket, localAddress,
+ TRUE, &error);
+
+ g_object_unref(tmp);
+ g_object_unref(localAddress);
+
+ Q_ASSERT(success);
+ }
+
+ return clientSocket;
+}
+
+}
+
+class TestStreamTubeChan : public Test
+{
+ Q_OBJECT
+
+public:
+ TestStreamTubeChan(QObject *parent = 0)
+ : Test(parent),
+ mConn(0), mChanService(0), mLocalConnectionId(-1), mRemoteConnectionId(-1),
+ mGotLocalConnection(false), mGotRemoteConnection(false),
+ mGotSocketConnection(false), mGotConnectionClosed(false),
+ mOfferFinished(false), mRequiresCredentials(false), mCredentialByte(0)
+ { }
+
+protected Q_SLOTS:
+ void onNewLocalConnection(uint connectionId);
+ void onNewRemoteConnection(uint connectionId);
+ void onNewSocketConnection();
+ void onConnectionClosed(uint connectionId, const QString &errorName,
+ const QString &errorMesssage);
+ void onOfferFinished(Tp::PendingOperation *op);
+ void expectPendingTubeConnectionFinished(Tp::PendingOperation *op);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testCreation();
+ void testAcceptTwice();
+ void testAcceptSuccess();
+ void testAcceptFail();
+ void testOfferSuccess();
+ void testOutgoingConnectionMonitoring();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ void testCheckRemoteConnectionsCommon();
+
+ void createTubeChannel(bool requested, TpSocketAddressType addressType,
+ TpSocketAccessControl accessControl, bool withContact);
+
+ TestConnHelper *mConn;
+ TpTestsStreamTubeChannel *mChanService;
+ StreamTubeChannelPtr mChan;
+
+ uint mCurrentContext;
+
+ uint mLocalConnectionId;
+ uint mRemoteConnectionId;
+ bool mGotLocalConnection;
+ bool mGotRemoteConnection;
+ bool mGotSocketConnection;
+ bool mGotConnectionClosed;
+ bool mOfferFinished;
+ bool mRequiresCredentials;
+ uchar mCredentialByte;
+
+ QHostAddress mExpectedAddress;
+ uint mExpectedPort;
+ uint mExpectedHandle;
+ QString mExpectedId;
+};
+
+void TestStreamTubeChan::onNewLocalConnection(uint connectionId)
+{
+ qDebug() << "Got local connection with id:" << connectionId;
+ mLocalConnectionId = connectionId;
+ mGotLocalConnection = true;
+ QVERIFY(mChan->connections().contains(connectionId));
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeChan::onNewRemoteConnection(uint connectionId)
+{
+ qDebug() << "Got remote connection with id:" << connectionId;
+ mRemoteConnectionId = connectionId;
+ mGotRemoteConnection = true;
+ QVERIFY(mChan->connections().contains(connectionId));
+
+ testCheckRemoteConnectionsCommon();
+}
+
+void TestStreamTubeChan::onNewSocketConnection()
+{
+ qDebug() << "Got new socket connection";
+ mGotSocketConnection = true;
+ mLoop->exit(0);
+}
+
+void TestStreamTubeChan::onConnectionClosed(uint connectionId,
+ const QString &errorName, const QString &errorMesssage)
+{
+ qDebug() << "Got connetion closed for connection" << connectionId;
+ mGotConnectionClosed = true;
+ QVERIFY(!mChan->connections().contains(connectionId));
+
+ if (mChan->isRequested()) {
+ testCheckRemoteConnectionsCommon();
+ }
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeChan::onOfferFinished(Tp::PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ mOfferFinished = true;
+ mLoop->exit(0);
+}
+
+void TestStreamTubeChan::expectPendingTubeConnectionFinished(PendingOperation *op)
+{
+ TEST_VERIFY_OP(op);
+
+ PendingStreamTubeConnection *pstc = qobject_cast<PendingStreamTubeConnection*>(op);
+ mRequiresCredentials = pstc->requiresCredentials();
+ mCredentialByte = pstc->credentialByte();
+ mLoop->exit(0);
+}
+
+void TestStreamTubeChan::createTubeChannel(bool requested,
+ TpSocketAddressType addressType,
+ TpSocketAccessControl accessControl,
+ bool withContact)
+{
+ mChan.reset();
+ mLoop->processEvents();
+ tp_clear_object(&mChanService);
+
+ /* Create service-side tube channel object */
+ QString chanPath = QString(QLatin1String("%1/Channel")).arg(mConn->objectPath());
+
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *roomRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_ROOM);
+ TpHandle handle;
+ GType type;
+ if (withContact) {
+ handle = tp_handle_ensure(contactRepo, "bob", NULL, NULL);
+ type = TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL;
+ } else {
+ handle = tp_handle_ensure(roomRepo, "#test", NULL, NULL);
+ type = TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL;
+ }
+
+ TpHandle alfHandle = tp_handle_ensure(contactRepo, "alf", NULL, NULL);
+
+ GHashTable *sockets = createSupportedSocketTypesHash(addressType, accessControl);
+
+ mChanService = TP_TESTS_STREAM_TUBE_CHANNEL(g_object_new(
+ type,
+ "connection", mConn->service(),
+ "handle", handle,
+ "requested", requested,
+ "object-path", chanPath.toLatin1().constData(),
+ "supported-socket-types", sockets,
+ "initiator-handle", alfHandle,
+ NULL));
+
+ /* Create client-side tube channel object */
+ GHashTable *props;
+ g_object_get(mChanService, "channel-properties", &props, NULL);
+
+ if (requested) {
+ mChan = OutgoingStreamTubeChannel::create(mConn->client(), chanPath, QVariantMap());
+ } else {
+ mChan = IncomingStreamTubeChannel::create(mConn->client(), chanPath, QVariantMap());
+ }
+
+ g_hash_table_unref(props);
+ g_hash_table_unref(sockets);
+
+ if (withContact)
+ tp_handle_unref(contactRepo, handle);
+ else
+ tp_handle_unref(roomRepo, handle);
+}
+
+void TestStreamTubeChan::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("stream-tube-chan");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestStreamTubeChan::init()
+{
+ initImpl();
+
+ mCurrentContext = -1;
+
+ mLocalConnectionId = -1;
+ mRemoteConnectionId = -1;
+ mGotLocalConnection = false;
+ mGotRemoteConnection = false;
+ mGotConnectionClosed = false;
+ mGotSocketConnection = false;
+ mOfferFinished = false;
+ mRequiresCredentials = false;
+ mCredentialByte = 0;
+
+ mExpectedAddress = QHostAddress();
+ mExpectedPort = -1;
+ mExpectedHandle = -1;
+ mExpectedId = QString();
+}
+
+void TestStreamTubeChan::testCheckRemoteConnectionsCommon()
+{
+ OutgoingStreamTubeChannelPtr chan = OutgoingStreamTubeChannelPtr::qObjectCast(mChan);
+ QVERIFY(chan);
+
+ QCOMPARE(chan->contactsForConnections().isEmpty(), false);
+ QCOMPARE(chan->contactsForConnections().contains(mRemoteConnectionId), true);
+ QCOMPARE(chan->contactsForConnections().value(mRemoteConnectionId)->handle()[0],
+ mExpectedHandle);
+ QCOMPARE(chan->contactsForConnections().value(mRemoteConnectionId)->id(),
+ mExpectedId);
+
+ if (contexts[mCurrentContext].accessControl == TP_SOCKET_ACCESS_CONTROL_PORT) {
+ // qDebug() << "+++ conn for source addresses" << chan->connectionsForSourceAddresses();
+ QCOMPARE(chan->connectionsForSourceAddresses().isEmpty(), false);
+ QCOMPARE(chan->connectionsForCredentials().isEmpty(), true);
+ QPair<QHostAddress, quint16> srcAddr(mExpectedAddress, mExpectedPort);
+ QCOMPARE(chan->connectionsForSourceAddresses().contains(srcAddr), true);
+ QCOMPARE(chan->connectionsForSourceAddresses().value(srcAddr), mRemoteConnectionId);
+ } else if (contexts[mCurrentContext].accessControl == TP_SOCKET_ACCESS_CONTROL_CREDENTIALS) {
+ // qDebug() << "+++ conn for credentials" << chan->connectionsForCredentials();
+ QCOMPARE(chan->connectionsForCredentials().isEmpty(), false);
+ QCOMPARE(chan->connectionsForSourceAddresses().isEmpty(), true);
+ QCOMPARE(chan->connectionsForCredentials().contains(mCredentialByte), true);
+ QCOMPARE(chan->connectionsForCredentials().value(mCredentialByte), mRemoteConnectionId);
+ }
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeChan::testCreation()
+{
+ /* Outgoing tube */
+ createTubeChannel(true, TP_SOCKET_ADDRESS_TYPE_UNIX,
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST, true);
+ QVERIFY(connect(mChan->becomeReady(OutgoingStreamTubeChannel::FeatureCore),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(OutgoingStreamTubeChannel::FeatureCore), true);
+ QCOMPARE(mChan->isReady(StreamTubeChannel::FeatureConnectionMonitoring), false);
+ QCOMPARE(mChan->state(), TubeChannelStateNotOffered);
+ QCOMPARE(mChan->parameters().isEmpty(), true);
+ QCOMPARE(mChan->service(), QLatin1String("test-service"));
+ QCOMPARE(mChan->supportsIPv4SocketsOnLocalhost(), false);
+ QCOMPARE(mChan->supportsIPv4SocketsWithSpecifiedAddress(), false);
+ QCOMPARE(mChan->supportsIPv6SocketsOnLocalhost(), false);
+ QCOMPARE(mChan->supportsIPv6SocketsWithSpecifiedAddress(), false);
+ QCOMPARE(mChan->supportsUnixSocketsOnLocalhost(), true);
+ QCOMPARE(mChan->supportsUnixSocketsWithCredentials(), false);
+ QCOMPARE(mChan->supportsAbstractUnixSocketsOnLocalhost(), false);
+ QCOMPARE(mChan->supportsAbstractUnixSocketsWithCredentials(), false);
+ QCOMPARE(mChan->connections().isEmpty(), true);
+ QCOMPARE(mChan->addressType(), SocketAddressTypeUnix);
+ QCOMPARE(mChan->ipAddress().first.isNull(), true);
+ QCOMPARE(mChan->localAddress(), QString());
+
+ /* incoming tube */
+ createTubeChannel(false, TP_SOCKET_ADDRESS_TYPE_UNIX,
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST, false);
+ QVERIFY(connect(mChan->becomeReady(IncomingStreamTubeChannel::FeatureCore),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(IncomingStreamTubeChannel::FeatureCore), true);
+ QCOMPARE(mChan->isReady(StreamTubeChannel::FeatureConnectionMonitoring), false);
+ QCOMPARE(mChan->state(), TubeChannelStateLocalPending);
+ QCOMPARE(mChan->parameters().isEmpty(), false);
+ QCOMPARE(mChan->parameters().size(), 1);
+ QCOMPARE(mChan->parameters().contains(QLatin1String("badger")), true);
+ QCOMPARE(mChan->parameters().value(QLatin1String("badger")), QVariant(42));
+ QCOMPARE(mChan->service(), QLatin1String("test-service"));
+ QCOMPARE(mChan->supportsIPv4SocketsOnLocalhost(), false);
+ QCOMPARE(mChan->supportsIPv4SocketsWithSpecifiedAddress(), false);
+ QCOMPARE(mChan->supportsIPv6SocketsOnLocalhost(), false);
+ QCOMPARE(mChan->supportsIPv6SocketsWithSpecifiedAddress(), false);
+ QCOMPARE(mChan->supportsUnixSocketsOnLocalhost(), true);
+ QCOMPARE(mChan->supportsUnixSocketsWithCredentials(), false);
+ QCOMPARE(mChan->supportsAbstractUnixSocketsOnLocalhost(), false);
+ QCOMPARE(mChan->supportsAbstractUnixSocketsWithCredentials(), false);
+ QCOMPARE(mChan->connections().isEmpty(), true);
+ QCOMPARE(mChan->addressType(), SocketAddressTypeUnix);
+ QCOMPARE(mChan->ipAddress().first.isNull(), true);
+ QCOMPARE(mChan->localAddress(), QString());
+}
+
+void TestStreamTubeChan::testAcceptTwice()
+{
+ /* incoming tube */
+ createTubeChannel(false, TP_SOCKET_ADDRESS_TYPE_UNIX,
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST, false);
+ QVERIFY(connect(mChan->becomeReady(IncomingStreamTubeChannel::FeatureCore |
+ StreamTubeChannel::FeatureConnectionMonitoring),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(IncomingStreamTubeChannel::FeatureCore), true);
+ QCOMPARE(mChan->isReady(StreamTubeChannel::FeatureConnectionMonitoring), true);
+ QCOMPARE(mChan->state(), TubeChannelStateLocalPending);
+
+ IncomingStreamTubeChannelPtr chan = IncomingStreamTubeChannelPtr::qObjectCast(mChan);
+ QVERIFY(connect(chan->acceptTubeAsUnixSocket(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->state(), TubeChannelStateOpen);
+
+ /* try to re-accept the tube */
+ QVERIFY(connect(chan->acceptTubeAsUnixSocket(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectFailure(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->state(), TubeChannelStateOpen);
+}
+
+void TestStreamTubeChan::testAcceptSuccess()
+{
+ /* incoming tube */
+ for (int i = 0; contexts[i].addressType != NUM_TP_SOCKET_ADDRESS_TYPES; i++) {
+ /* as we run several tests here, let's init/cleanup properly */
+ init();
+
+ qDebug() << "Testing context:" << i;
+ mCurrentContext = i;
+
+ createTubeChannel(false, contexts[i].addressType,
+ contexts[i].accessControl, contexts[i].withContact);
+ QVERIFY(connect(mChan->becomeReady(IncomingStreamTubeChannel::FeatureCore |
+ StreamTubeChannel::FeatureConnectionMonitoring),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(IncomingStreamTubeChannel::FeatureCore), true);
+ QCOMPARE(mChan->isReady(StreamTubeChannel::FeatureConnectionMonitoring), true);
+ QCOMPARE(mChan->state(), TubeChannelStateLocalPending);
+
+ mLocalConnectionId = -1;
+ mGotLocalConnection = false;
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(newConnection(uint)),
+ SLOT(onNewLocalConnection(uint))));
+
+ bool requiresCredentials = ((contexts[i].accessControl == TP_SOCKET_ACCESS_CONTROL_CREDENTIALS) ?
+ true : false);
+
+ GSocket *gSocket = 0;
+ QHostAddress addr;
+ quint16 port = 0;
+ IncomingStreamTubeChannelPtr chan = IncomingStreamTubeChannelPtr::qObjectCast(mChan);
+ if (contexts[i].addressType == TP_SOCKET_ADDRESS_TYPE_UNIX) {
+ QVERIFY(connect(chan->acceptTubeAsUnixSocket(requiresCredentials),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectPendingTubeConnectionFinished(Tp::PendingOperation *))));
+ } else {
+ if (contexts[i].accessControl == TP_SOCKET_ACCESS_CONTROL_PORT) {
+ gSocket = createTcpClientGSocket(contexts[i].addressType);
+
+ // There is no way to bind a QTcpSocket and using
+ // QAbstractSocket::setSocketDescriptor does not work either, so using glib sockets
+ // for this test. See http://bugreports.qt.nokia.com/browse/QTBUG-121
+ GSocketAddress *localAddr;
+
+ localAddr = g_socket_get_local_address(gSocket, NULL);
+ QVERIFY(localAddr != NULL);
+ addr = QHostAddress(QLatin1String(g_inet_address_to_string(
+ g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(localAddr)))));
+ port = g_inet_socket_address_get_port(G_INET_SOCKET_ADDRESS(localAddr));
+ g_object_unref(localAddr);
+ } else {
+ addr = QHostAddress::Any;
+ port = 0;
+ }
+
+ QVERIFY(connect(chan->acceptTubeAsTcpSocket(addr, port),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectPendingTubeConnectionFinished(Tp::PendingOperation *))));
+ }
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->state(), TubeChannelStateOpen);
+ QCOMPARE(mRequiresCredentials, requiresCredentials);
+
+ if (contexts[i].addressType == TP_SOCKET_ADDRESS_TYPE_UNIX) {
+ qDebug() << "Connecting to host" << mChan->localAddress();
+
+ QLocalSocket *socket = new QLocalSocket(this);
+ socket->connectToServer(mChan->localAddress());
+
+ if (requiresCredentials) {
+ qDebug() << "Sending credential byte" << mCredentialByte;
+ socket->write(reinterpret_cast<const char*>(&mCredentialByte), 1);
+ }
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mGotLocalConnection, true);
+ qDebug() << "Connected to host";
+
+ delete socket;
+ } else {
+ qDebug().nospace() << "Connecting to host " << mChan->ipAddress().first << ":" <<
+ mChan->ipAddress().second;
+
+ QTcpSocket *socket = 0;
+
+ if (contexts[i].accessControl == TP_SOCKET_ACCESS_CONTROL_PORT) {
+ GSocketAddress *remoteAddr = (GSocketAddress*) g_inet_socket_address_new(
+ g_inet_address_new_from_string(
+ mChan->ipAddress().first.toString().toLatin1().constData()),
+ mChan->ipAddress().second);
+ g_socket_connect(gSocket, remoteAddr, NULL, NULL);
+ } else {
+ socket = new QTcpSocket();
+ socket->connectToHost(mChan->ipAddress().first, mChan->ipAddress().second);
+ }
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mGotLocalConnection, true);
+ qDebug() << "Connected to host";
+
+ if (gSocket) {
+ tp_clear_object(&gSocket);
+ }
+
+ delete socket;
+ }
+
+ mGotConnectionClosed = false;
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(connectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString))));
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanService,
+ TP_ERROR_STR_DISCONNECTED);
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mGotConnectionClosed, true);
+
+ /* as we run several tests here, let's init/cleanup properly */
+ cleanup();
+ }
+}
+
+void TestStreamTubeChan::testAcceptFail()
+{
+ /* incoming tube */
+ createTubeChannel(false, TP_SOCKET_ADDRESS_TYPE_UNIX,
+ TP_SOCKET_ACCESS_CONTROL_LOCALHOST, false);
+ QVERIFY(connect(mChan->becomeReady(IncomingStreamTubeChannel::FeatureCore |
+ StreamTubeChannel::FeatureConnectionMonitoring),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(IncomingStreamTubeChannel::FeatureCore), true);
+ QCOMPARE(mChan->isReady(StreamTubeChannel::FeatureConnectionMonitoring), true);
+ QCOMPARE(mChan->state(), TubeChannelStateLocalPending);
+
+ /* when accept is called the channel will be closed service side */
+ tp_tests_stream_tube_channel_set_close_on_accept (mChanService, TRUE);
+
+ /* calling accept should fail */
+ IncomingStreamTubeChannelPtr chan = IncomingStreamTubeChannelPtr::qObjectCast(mChan);
+ QVERIFY(connect(chan->acceptTubeAsUnixSocket(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectFailure(Tp::PendingOperation *))));
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mChan->isValid(), false);
+
+ /* trying to accept again should fail immediately */
+ QVERIFY(connect(chan->acceptTubeAsUnixSocket(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectFailure(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+}
+
+void TestStreamTubeChan::testOfferSuccess()
+{
+ /* incoming tube */
+ for (int i = 0; contexts[i].addressType != NUM_TP_SOCKET_ADDRESS_TYPES; i++) {
+ /* as we run several tests here, let's init/cleanup properly */
+ init();
+
+ qDebug() << "Testing context:" << i;
+ mCurrentContext = i;
+
+ createTubeChannel(true, contexts[i].addressType,
+ contexts[i].accessControl, contexts[i].withContact);
+ QVERIFY(connect(mChan->becomeReady(OutgoingStreamTubeChannel::FeatureCore |
+ StreamTubeChannel::FeatureConnectionMonitoring),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->isReady(OutgoingStreamTubeChannel::FeatureCore), true);
+ QCOMPARE(mChan->isReady(StreamTubeChannel::FeatureConnectionMonitoring), true);
+ QCOMPARE(mChan->state(), TubeChannelStateNotOffered);
+ QCOMPARE(mChan->parameters().isEmpty(), true);
+
+ mRemoteConnectionId = -1;
+ mGotRemoteConnection = false;
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(newConnection(uint)),
+ SLOT(onNewRemoteConnection(uint))));
+
+ bool requiresCredentials = ((contexts[i].accessControl == TP_SOCKET_ACCESS_CONTROL_CREDENTIALS) ?
+ true : false);
+
+ mExpectedAddress = QHostAddress();
+ mExpectedPort = -1;
+ mExpectedHandle = -1;
+ mExpectedId = QString();
+
+ mOfferFinished = false;
+ mGotSocketConnection = false;
+ QLocalServer *localServer = 0;
+ QTcpServer *tcpServer = 0;
+ OutgoingStreamTubeChannelPtr chan = OutgoingStreamTubeChannelPtr::qObjectCast(mChan);
+ QVariantMap offerParameters;
+ offerParameters.insert(QLatin1String("mushroom"), 44);
+ if (contexts[i].addressType == TP_SOCKET_ADDRESS_TYPE_UNIX) {
+ localServer = new QLocalServer(this);
+ localServer->listen(QLatin1String(tmpnam(NULL)));
+ connect(localServer, SIGNAL(newConnection()), SLOT(onNewSocketConnection()));
+
+ QVERIFY(connect(chan->offerUnixSocket(localServer, offerParameters, requiresCredentials),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onOfferFinished(Tp::PendingOperation *))));
+ } else {
+ tcpServer = new QTcpServer(this);
+ tcpServer->listen();
+ connect(tcpServer, SIGNAL(newConnection()), SLOT(onNewSocketConnection()));
+
+ QVERIFY(connect(chan->offerTcpSocket(tcpServer, offerParameters),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onOfferFinished(Tp::PendingOperation *))));
+ }
+
+ while (mChan->state() != TubeChannelStateRemotePending) {
+ mLoop->processEvents();
+ }
+
+ QCOMPARE(mGotSocketConnection, false);
+
+ // A client now connects to the tube
+ QLocalSocket *localSocket = 0;
+ QTcpSocket *tcpSocket = 0;
+ if (contexts[i].addressType == TP_SOCKET_ADDRESS_TYPE_UNIX) {
+ qDebug() << "Connecting to host" << localServer->fullServerName();
+ localSocket = new QLocalSocket(this);
+ localSocket->connectToServer(localServer->fullServerName());
+ } else {
+ qDebug().nospace() << "Connecting to host" << tcpServer->serverAddress() <<
+ ":" << tcpServer->serverPort();
+ tcpSocket = new QTcpSocket(this);
+ tcpSocket->connectToHost(tcpServer->serverAddress(), tcpServer->serverPort());
+ }
+
+ QCOMPARE(mGotSocketConnection, false);
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mGotSocketConnection, true);
+
+ if (tcpSocket) {
+ mExpectedAddress = tcpSocket->localAddress();
+ mExpectedPort = tcpSocket->localPort();
+ }
+
+ /* simulate CM when peer connects */
+ GValue *connParam = 0;
+ mCredentialByte = 0;
+ switch (contexts[i].accessControl) {
+ case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
+ connParam = tp_g_value_slice_new_static_string("");
+ break;
+
+ case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
+ {
+ mCredentialByte = g_random_int_range(0, G_MAXUINT8);
+
+ localSocket->write(reinterpret_cast<const char*>(&mCredentialByte), 1);
+ connParam = tp_g_value_slice_new_byte(mCredentialByte);
+ }
+ break;
+
+ case TP_SOCKET_ACCESS_CONTROL_PORT:
+ {
+ connParam = tp_g_value_slice_new_take_boxed(
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4,
+ dbus_g_type_specialized_construct(TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
+ dbus_g_type_struct_set(connParam,
+ 0, tcpSocket->localAddress().toString().toLatin1().constData(),
+ 1, tcpSocket->localPort(),
+ G_MAXUINT);
+ }
+ break;
+
+ default:
+ Q_ASSERT(false);
+ }
+
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ TpHandle bobHandle = tp_handle_ensure(contactRepo, "bob", NULL, NULL);
+ tp_tests_stream_tube_channel_peer_connected_no_stream(mChanService,
+ connParam, bobHandle);
+
+ tp_g_value_slice_free(connParam);
+
+ mExpectedHandle = bobHandle;
+ mExpectedId = QLatin1String("bob");
+
+ QCOMPARE(mChan->state(), TubeChannelStateRemotePending);
+
+ while (!mOfferFinished) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mChan->state(), TubeChannelStateOpen);
+ QCOMPARE(mChan->parameters().isEmpty(), false);
+ QCOMPARE(mChan->parameters().size(), 1);
+ QCOMPARE(mChan->parameters().contains(QLatin1String("mushroom")), true);
+ QCOMPARE(mChan->parameters().value(QLatin1String("mushroom")), QVariant(44));
+
+ if (!mGotRemoteConnection) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mGotRemoteConnection, true);
+
+ qDebug() << "Connected to host";
+
+ mGotConnectionClosed = false;
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(connectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString))));
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanService,
+ TP_ERROR_STR_DISCONNECTED);
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mGotConnectionClosed, true);
+
+ /* let the internal OutgoingStreamTubeChannel::onConnectionClosed slot be called before
+ * checking the data for that connection */
+ mLoop->processEvents();
+
+ QCOMPARE(chan->contactsForConnections().isEmpty(), true);
+ QCOMPARE(chan->connectionsForSourceAddresses().isEmpty(), true);
+ QCOMPARE(chan->connectionsForCredentials().isEmpty(), true);
+
+ delete localServer;
+ delete localSocket;
+ delete tcpServer;
+ delete tcpSocket;
+
+ /* as we run several tests here, let's init/cleanup properly */
+ cleanup();
+ }
+}
+
+void TestStreamTubeChan::testOutgoingConnectionMonitoring()
+{
+ mCurrentContext = 3; // should point to the room, IPv4, AC port one
+ createTubeChannel(true, TP_SOCKET_ADDRESS_TYPE_IPV4, TP_SOCKET_ACCESS_CONTROL_PORT, false);
+ QVERIFY(connect(mChan->becomeReady(OutgoingStreamTubeChannel::FeatureCore |
+ StreamTubeChannel::FeatureConnectionMonitoring),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(newConnection(uint)),
+ SLOT(onNewRemoteConnection(uint))));
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(connectionClosed(uint,QString,QString)),
+ SLOT(onConnectionClosed(uint,QString,QString))));
+
+ OutgoingStreamTubeChannelPtr chan = OutgoingStreamTubeChannelPtr::qObjectCast(mChan);
+ QVERIFY(connect(chan->offerTcpSocket(QHostAddress(QHostAddress::LocalHost), 9), // DISCARD
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(onOfferFinished(Tp::PendingOperation *))));
+
+ while (mChan->state() != TubeChannelStateRemotePending) {
+ mLoop->processEvents();
+ }
+
+ /* simulate CM when peer connects */
+ GValue *connParam = tp_g_value_slice_new_take_boxed(
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4,
+ dbus_g_type_specialized_construct(TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
+
+ mExpectedAddress.setAddress(QLatin1String("127.0.0.1"));
+ mExpectedPort = 12345;
+
+ dbus_g_type_struct_set(connParam,
+ 0, mExpectedAddress.toString().toLatin1().constData(),
+ 1, static_cast<quint16>(mExpectedPort),
+ G_MAXUINT);
+
+ // Simulate a peer connection from someone we don't have a prebuilt contact for yet, and
+ // immediately drop it
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle = tp_handle_ensure(contactRepo, "YouHaventSeenMeYet", NULL, NULL);
+
+ mExpectedHandle = handle;
+ mExpectedId = QLatin1String("youhaventseenmeyet");
+
+ tp_tests_stream_tube_channel_peer_connected_no_stream(mChanService,
+ connParam, handle);
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanService,
+ TP_ERROR_STR_DISCONNECTED);
+ tp_g_value_slice_free(connParam);
+
+ // Test that we get newConnection first and only then connectionClosed, unlike how the code has
+ // been for a long time, queueing newConnection events and emitting connectionClosed directly
+ while (!mOfferFinished || !mGotRemoteConnection) {
+ QVERIFY(!mGotConnectionClosed || !mOfferFinished);
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QCOMPARE(mChan->connections().size(), 1);
+
+ // The connectionClosed emission should finally exit the main loop
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mGotConnectionClosed);
+
+ QCOMPARE(mChan->connections().size(), 0);
+}
+
+void TestStreamTubeChan::cleanup()
+{
+ cleanupImpl();
+
+ if (mChan && mChan->isValid()) {
+ qDebug() << "waiting for the channel to become invalidated";
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ mLoop,
+ SLOT(quit())));
+ tp_base_channel_close(TP_BASE_CHANNEL(mChanService));
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ mChan.reset();
+
+ if (mChanService != 0) {
+ g_object_unref(mChanService);
+ mChanService = 0;
+ }
+
+ mLoop->processEvents();
+}
+
+void TestStreamTubeChan::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestStreamTubeChan)
+#include "_gen/stream-tube-chan.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/stream-tube-handlers.cpp b/qt4/tests/dbus/stream-tube-handlers.cpp
new file mode 100644
index 000000000..8c4e9488a
--- /dev/null
+++ b/qt4/tests/dbus/stream-tube-handlers.cpp
@@ -0,0 +1,1939 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/simple-conn.h>
+#include <tests/lib/glib/stream-tube-chan.h>
+#include <tests/lib/glib/echo/chan.h>
+
+#include <TelepathyQt4/AbstractClient>
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+#include <TelepathyQt4/ChannelClassSpec>
+#include <TelepathyQt4/ClientHandlerInterface>
+#include <TelepathyQt4/ClientRegistrar>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/IncomingStreamTubeChannel>
+#include <TelepathyQt4/OutgoingStreamTubeChannel>
+#include <TelepathyQt4/PendingAccount>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReferencedHandles>
+#include <TelepathyQt4/StreamTubeChannel>
+#include <TelepathyQt4/StreamTubeClient>
+#include <TelepathyQt4/StreamTubeServer>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <cstring>
+
+#include <QTcpServer>
+#include <QTcpSocket>
+
+using namespace Tp;
+using namespace Tp::Client;
+
+namespace
+{
+
+class ChannelRequestAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.ChannelRequest")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.ChannelRequest\" >\n"
+" <property name=\"Account\" type=\"o\" access=\"read\" />\n"
+" <property name=\"UserActionTime\" type=\"x\" access=\"read\" />\n"
+" <property name=\"PreferredHandler\" type=\"s\" access=\"read\" />\n"
+" <property name=\"Requests\" type=\"aa{sv}\" access=\"read\" />\n"
+" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n"
+" <property name=\"Hints\" type=\"a{sv}\" access=\"read\" />\n"
+" <method name=\"Proceed\" />\n"
+" <method name=\"Cancel\" />\n"
+" <signal name=\"Failed\" >\n"
+" <arg name=\"Error\" type=\"s\" />\n"
+" <arg name=\"Message\" type=\"s\" />\n"
+" </signal>\n"
+" <signal name=\"Succeeded\" />\n"
+" <signal name=\"SucceededWithChannel\" >\n"
+" <arg name=\"Connection\" type=\"o\" />\n"
+" <arg name=\"ConnectionProperties\" type=\"a{sv}\" />\n"
+" <arg name=\"Channel\" type=\"o\" />\n"
+" <arg name=\"ChannelProperties\" type=\"a{sv}\" />\n"
+" </signal>\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QDBusObjectPath Account READ Account)
+ Q_PROPERTY(qulonglong UserActionTime READ UserActionTime)
+ Q_PROPERTY(QString PreferredHandler READ PreferredHandler)
+ Q_PROPERTY(QualifiedPropertyValueMapList Requests READ Requests)
+ Q_PROPERTY(QStringList Interfaces READ Interfaces)
+ Q_PROPERTY(QVariantMap Hints READ Hints)
+
+public:
+ ChannelRequestAdaptor(QDBusObjectPath account,
+ qulonglong userActionTime,
+ QString preferredHandler,
+ QualifiedPropertyValueMapList requests,
+ QStringList interfaces,
+ bool shouldFail,
+ bool proceedNoop,
+ QVariantMap hints,
+ QObject *parent)
+ : QDBusAbstractAdaptor(parent),
+ mAccount(account), mUserActionTime(userActionTime),
+ mPreferredHandler(preferredHandler), mRequests(requests),
+ mInterfaces(interfaces), mShouldFail(shouldFail),
+ mProceedNoop(proceedNoop), mHints(hints)
+ {
+ }
+
+ virtual ~ChannelRequestAdaptor()
+ {
+ }
+
+ void setChan(const QString &connPath, const QVariantMap &connProps,
+ const QString &chanPath, const QVariantMap &chanProps)
+ {
+ mConnPath = connPath;
+ mConnProps = connProps;
+ mChanPath = chanPath;
+ mChanProps = chanProps;
+ }
+
+public: // Properties
+ inline QDBusObjectPath Account() const
+ {
+ return mAccount;
+ }
+
+ inline qulonglong UserActionTime() const
+ {
+ return mUserActionTime;
+ }
+
+ inline QString PreferredHandler() const
+ {
+ return mPreferredHandler;
+ }
+
+ inline QualifiedPropertyValueMapList Requests() const
+ {
+ return mRequests;
+ }
+
+ inline QStringList Interfaces() const
+ {
+ return mInterfaces;
+ }
+
+ inline QVariantMap Hints() const
+ {
+ return mHints;
+ }
+
+public Q_SLOTS: // Methods
+ void Proceed()
+ {
+ if (mProceedNoop) {
+ return;
+ }
+
+ if (mShouldFail) {
+ QTimer::singleShot(0, this, SLOT(fail()));
+ } else {
+ QTimer::singleShot(0, this, SLOT(succeed()));
+ }
+ }
+
+ void Cancel()
+ {
+ Q_EMIT Failed(QLatin1String(TELEPATHY_ERROR_CANCELLED), QLatin1String("Cancelled"));
+ }
+
+Q_SIGNALS: // Signals
+ void Failed(const QString &error, const QString &message);
+ void Succeeded();
+ void SucceededWithChannel(const QDBusObjectPath &connPath, const QVariantMap &connProps,
+ const QDBusObjectPath &chanPath, const QVariantMap &chanProps);
+
+private Q_SLOTS:
+ void fail()
+ {
+ Q_EMIT Failed(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("Not available"));
+ }
+
+ void succeed()
+ {
+ if (!mConnPath.isEmpty() && !mChanPath.isEmpty()) {
+ Q_EMIT SucceededWithChannel(QDBusObjectPath(mConnPath), mConnProps,
+ QDBusObjectPath(mChanPath), mChanProps);
+ }
+
+ Q_EMIT Succeeded();
+ }
+
+private:
+ QDBusObjectPath mAccount;
+ qulonglong mUserActionTime;
+ QString mPreferredHandler;
+ QualifiedPropertyValueMapList mRequests;
+ QStringList mInterfaces;
+ bool mShouldFail;
+ bool mProceedNoop;
+ QVariantMap mHints;
+ QString mConnPath, mChanPath;
+ QVariantMap mConnProps, mChanProps;
+};
+
+void destroySocketControlList(gpointer data)
+{
+ g_array_free(reinterpret_cast<GArray *>(data), TRUE);
+}
+
+GHashTable *createSupportedSocketTypesHash(bool supportMonitoring, bool unixOnly)
+{
+ GHashTable *ret;
+ GArray *tab;
+ TpSocketAccessControl ac;
+
+ ret = g_hash_table_new_full(NULL, NULL, NULL, destroySocketControlList);
+
+ // Named UNIX
+ tab = g_array_sized_new(FALSE, FALSE, sizeof(TpSocketAccessControl), 1);
+ ac = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val(tab, ac);
+
+ if (supportMonitoring) {
+ ac = TP_SOCKET_ACCESS_CONTROL_CREDENTIALS;
+ g_array_append_val(tab, ac);
+ }
+
+ g_hash_table_insert(ret, GUINT_TO_POINTER(TP_SOCKET_ADDRESS_TYPE_UNIX), tab);
+
+ // Abstract UNIX
+ tab = g_array_sized_new(FALSE, FALSE, sizeof(TpSocketAccessControl), 1);
+ ac = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val(tab, ac);
+
+ if (supportMonitoring) {
+ ac = TP_SOCKET_ACCESS_CONTROL_CREDENTIALS;
+ g_array_append_val(tab, ac);
+ }
+
+ g_hash_table_insert(ret, GUINT_TO_POINTER(TP_SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX), tab);
+
+ if (unixOnly) {
+ return ret;
+ }
+
+ // IPv4
+ tab = g_array_sized_new(FALSE, FALSE, sizeof(TpSocketAccessControl), 1);
+ ac = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val(tab, ac);
+
+ if (supportMonitoring) {
+ ac = TP_SOCKET_ACCESS_CONTROL_PORT;
+ g_array_append_val(tab, ac);
+ }
+
+ g_hash_table_insert(ret, GUINT_TO_POINTER(TP_SOCKET_ADDRESS_TYPE_IPV4), tab);
+
+ // IPv6
+ tab = g_array_sized_new(FALSE, FALSE, sizeof(TpSocketAccessControl), 1);
+ ac = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val(tab, ac);
+
+ if (supportMonitoring) {
+ ac = TP_SOCKET_ACCESS_CONTROL_PORT;
+ g_array_append_val(tab, ac);
+ }
+
+ g_hash_table_insert(ret, GUINT_TO_POINTER(TP_SOCKET_ADDRESS_TYPE_IPV6), tab);
+
+ return ret;
+}
+
+}
+
+class TestStreamTubeHandlers : public Test
+{
+ Q_OBJECT
+
+public:
+ TestStreamTubeHandlers(QObject *parent = 0)
+ : Test(parent)
+ { }
+
+protected Q_SLOTS:
+ void onTubeRequested(const Tp::AccountPtr &, const Tp::OutgoingStreamTubeChannelPtr &,
+ const QDateTime &, const Tp::ChannelRequestHints &);
+ void onServerTubeClosed(const Tp::AccountPtr &, const Tp::OutgoingStreamTubeChannelPtr &,
+ const QString &, const QString &);
+ void onNewServerConnection(const QHostAddress &, quint16, const Tp::AccountPtr &,
+ const Tp::ContactPtr &, const Tp::OutgoingStreamTubeChannelPtr &);
+ void onServerConnectionClosed(const QHostAddress &, quint16, const Tp::AccountPtr &,
+ const Tp::ContactPtr &, const QString &, const QString &,
+ const Tp::OutgoingStreamTubeChannelPtr &);
+
+ void onTubeOffered(const Tp::AccountPtr &, const Tp::IncomingStreamTubeChannelPtr &);
+ void onClientTubeClosed(const Tp::AccountPtr &, const Tp::IncomingStreamTubeChannelPtr &,
+ const QString &, const QString &);
+ void onClientAcceptedAsTcp(const QHostAddress &, quint16, const QHostAddress &, quint16,
+ const Tp::AccountPtr &, const Tp::IncomingStreamTubeChannelPtr &);
+ void onClientAcceptedAsUnix(const QString &, bool reqsCreds, uchar credByte,
+ const Tp::AccountPtr &, const Tp::IncomingStreamTubeChannelPtr &);
+ void onNewClientConnection(const Tp::AccountPtr &, const Tp::IncomingStreamTubeChannelPtr &,
+ uint connectionId);
+ void onClientConnectionClosed(const Tp::AccountPtr &, const Tp::IncomingStreamTubeChannelPtr &,
+ uint, const QString &, const QString &);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testRegistration();
+ void testBasicTcpExport();
+ void testFailedExport();
+ void testServerConnMonitoring();
+ void testSSTHErrorPaths();
+
+ void testClientBasicTcp();
+ void testClientTcpGeneratorIgnore();
+ void testClientTcpUnsupported();
+
+ void testClientBasicUnix();
+ void testClientUnixCredsIgnore();
+ // the unix AF unsupported codepaths are the same, so no need to test separately
+ void testClientConnMonitoring();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QMap<QString, ClientHandlerInterface *> ourHandlers();
+
+ QPair<QString, QVariantMap> createTubeChannel(bool requested, HandleType type,
+ bool supportMonitoring, bool unixOnly = false);
+
+ AccountManagerPtr mAM;
+ AccountPtr mAcc;
+ TestConnHelper *mConn;
+ QList<TpTestsStreamTubeChannel *> mChanServices;
+
+ OutgoingStreamTubeChannelPtr mRequestedTube;
+ QDateTime mRequestTime;
+ ChannelRequestHints mRequestHints;
+
+ OutgoingStreamTubeChannelPtr mServerClosedTube;
+ QString mServerCloseError, mServerCloseMessage;
+
+ QHostAddress mNewServerConnectionAddress, mClosedServerConnectionAddress;
+ quint16 mNewServerConnectionPort, mClosedServerConnectionPort;
+ ContactPtr mNewServerConnectionContact, mClosedServerConnectionContact;
+ OutgoingStreamTubeChannelPtr mNewServerConnectionTube, mServerConnectionCloseTube;
+ QString mServerConnectionCloseError, mServerConnectionCloseMessage;
+
+ IncomingStreamTubeChannelPtr mOfferedTube;
+
+ IncomingStreamTubeChannelPtr mClientClosedTube;
+ QString mClientCloseError, mClientCloseMessage;
+
+ QHostAddress mClientTcpAcceptAddr, mClientTcpAcceptSrcAddr;
+ quint16 mClientTcpAcceptPort, mClientTcpAcceptSrcPort;
+ IncomingStreamTubeChannelPtr mClientTcpAcceptTube;
+
+ QString mClientUnixAcceptAddr;
+ bool mClientUnixReqsCreds;
+ IncomingStreamTubeChannelPtr mClientUnixAcceptTube;
+
+ IncomingStreamTubeChannelPtr mNewClientConnectionTube, mClosedClientConnectionTube;
+ uint mNewClientConnectionId, mClosedClientConnectionId;
+ QString mClientConnectionCloseError, mClientConnectionCloseMessage;
+};
+
+QPair<QString, QVariantMap> TestStreamTubeHandlers::createTubeChannel(bool requested,
+ HandleType handleType,
+ bool supportMonitoring,
+ bool unixOnly)
+{
+ mLoop->processEvents();
+
+ /* Create service-side tube channel object */
+ QString chanPath = QString(QLatin1String("%1/Channel%2%3%4"))
+ .arg(mConn->objectPath())
+ .arg(requested)
+ .arg(static_cast<uint>(handleType))
+ .arg(supportMonitoring);
+ QVariantMap chanProps;
+
+ chanProps.insert(TP_QT4_IFACE_CHANNEL + QString::fromLatin1(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_STREAM_TUBE);
+ chanProps.insert(TP_QT4_IFACE_CHANNEL + QString::fromLatin1(".Requested"), requested);
+ chanProps.insert(TP_QT4_IFACE_CHANNEL + QString::fromLatin1(".TargetHandleType"),
+ static_cast<uint>(handleType));
+
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *roomRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_ROOM);
+ TpHandle handle;
+ GType type;
+ if (handleType == HandleTypeContact) {
+ handle = tp_handle_ensure(contactRepo, "bob", NULL, NULL);
+ type = TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL;
+ chanProps.insert(TP_QT4_IFACE_CHANNEL + QString::fromLatin1(".TargetID"),
+ QString::fromLatin1("bob"));
+ } else {
+ handle = tp_handle_ensure(roomRepo, "#test", NULL, NULL);
+ type = TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL;
+ chanProps.insert(TP_QT4_IFACE_CHANNEL + QString::fromLatin1(".TargetID"),
+ QString::fromLatin1("#test"));
+ }
+
+ chanProps.insert(TP_QT4_IFACE_CHANNEL + QString::fromLatin1(".TargetHandle"), handle);
+
+ TpHandle alfHandle = tp_handle_ensure(contactRepo, "alf", NULL, NULL);
+
+ GHashTable *sockets = createSupportedSocketTypesHash(supportMonitoring, unixOnly);
+
+ mChanServices.push_back(
+ TP_TESTS_STREAM_TUBE_CHANNEL(g_object_new(
+ type,
+ "connection", mConn->service(),
+ "handle", handle,
+ "requested", requested,
+ "object-path", chanPath.toLatin1().constData(),
+ "supported-socket-types", sockets,
+ "initiator-handle", alfHandle,
+ NULL)));
+
+ if (handleType == HandleTypeContact)
+ tp_handle_unref(contactRepo, handle);
+ else
+ tp_handle_unref(roomRepo, handle);
+
+ return qMakePair(chanPath, chanProps);
+}
+
+QMap<QString, ClientHandlerInterface *> TestStreamTubeHandlers::ourHandlers()
+{
+ QStringList registeredNames =
+ QDBusConnection::sessionBus().interface()->registeredServiceNames();
+ QMap<QString, ClientHandlerInterface *> handlers;
+
+ Q_FOREACH (QString name, registeredNames) {
+ if (!name.startsWith(QLatin1String("org.freedesktop.Telepathy.Client."))) {
+ continue;
+ }
+
+ if (QDBusConnection::sessionBus().interface()->serviceOwner(name).value() !=
+ QDBusConnection::sessionBus().baseService()) {
+ continue;
+ }
+
+ QString path = QLatin1Char('/') + name;
+ path.replace(QLatin1Char('.'), QLatin1Char('/'));
+
+ ClientInterface client(name, path);
+ QStringList ifaces;
+ if (!waitForProperty(client.requestPropertyInterfaces(), &ifaces)) {
+ continue;
+ }
+
+ if (!ifaces.contains(TP_QT4_IFACE_CLIENT_HANDLER)) {
+ continue;
+ }
+
+ handlers.insert(name.mid(std::strlen("org.freedesktop.Telepathy.Client.")),
+ new ClientHandlerInterface(name, path, this));
+ }
+
+ return handlers;
+}
+
+void TestStreamTubeHandlers::onTubeRequested(
+ const Tp::AccountPtr &acc,
+ const Tp::OutgoingStreamTubeChannelPtr &tube,
+ const QDateTime &userActionTime,
+ const Tp::ChannelRequestHints &hints)
+{
+ qDebug() << "tube" << tube->objectPath() << "requested on account" << acc->objectPath();
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ // We always set the user action time in the past, so if that's carried over correctly, it won't
+ // be any more recent than the current time
+
+ if (mRequestTime >= QDateTime::currentDateTime()) {
+ qWarning() << "user action time later than expected";
+ mLoop->exit(2);
+ return;
+ }
+
+ mRequestedTube = tube;
+ mRequestTime = userActionTime;
+ mRequestHints = hints;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onServerTubeClosed(
+ const Tp::AccountPtr &acc,
+ const Tp::OutgoingStreamTubeChannelPtr &tube,
+ const QString &error,
+ const QString &message)
+{
+ qDebug() << "tube" << tube->objectPath() << "closed on account" << acc->objectPath();
+ qDebug() << "with error" << error << ':' << message;
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mServerClosedTube = tube;
+ mServerCloseError = error;
+ mServerCloseMessage = message;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onClientTubeClosed(
+ const Tp::AccountPtr &acc,
+ const Tp::IncomingStreamTubeChannelPtr &tube,
+ const QString &error,
+ const QString &message)
+{
+ qDebug() << "tube" << tube->objectPath() << "closed on account" << acc->objectPath();
+ qDebug() << "with error" << error << ':' << message;
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mClientClosedTube = tube;
+ mClientCloseError = error;
+ mClientCloseMessage = message;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onNewServerConnection(
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ const Tp::AccountPtr &acc,
+ const Tp::ContactPtr &contact,
+ const Tp::OutgoingStreamTubeChannelPtr &tube)
+{
+ qDebug() << "new conn" << qMakePair(sourceAddress, sourcePort) << "on tube" << tube->objectPath();
+ qDebug() << "from contact" << contact->id();
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ if (!tube->connectionsForSourceAddresses().contains(qMakePair(sourceAddress, sourcePort))) {
+ qWarning() << "the signaled tube doesn't report having that particular connection";
+ mLoop->exit(2);
+ return;
+ }
+
+ mNewServerConnectionAddress = sourceAddress;
+ mNewServerConnectionPort = sourcePort;
+ mNewServerConnectionContact = contact;
+ mNewServerConnectionTube = tube;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onServerConnectionClosed(
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ const Tp::AccountPtr &acc,
+ const Tp::ContactPtr &contact,
+ const QString &error,
+ const QString &message,
+ const Tp::OutgoingStreamTubeChannelPtr &tube)
+{
+ qDebug() << "conn" << qMakePair(sourceAddress, sourcePort) << "closed on tube" << tube->objectPath();
+ qDebug() << "with error" << error << ':' << message;
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mClosedServerConnectionAddress = sourceAddress;
+ mClosedServerConnectionPort = sourcePort;
+ mClosedServerConnectionContact = contact;
+ mServerConnectionCloseError = error;
+ mServerConnectionCloseMessage = message;
+ mServerConnectionCloseTube = tube;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onTubeOffered(
+ const Tp::AccountPtr &acc,
+ const Tp::IncomingStreamTubeChannelPtr &tube)
+{
+ qDebug() << "tube" << tube->objectPath() << "offered to account" << acc->objectPath();
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mOfferedTube = tube;
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onClientAcceptedAsTcp(
+ const QHostAddress &listenAddress,
+ quint16 listenPort,
+ const QHostAddress &sourceAddress,
+ quint16 sourcePort,
+ const Tp::AccountPtr &acc,
+ const Tp::IncomingStreamTubeChannelPtr &tube)
+{
+ qDebug() << "tube" << tube->objectPath() << "accepted at" << qMakePair(listenAddress, listenPort);
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mClientTcpAcceptAddr = listenAddress;
+ mClientTcpAcceptPort = listenPort;
+ mClientTcpAcceptSrcAddr = sourceAddress;
+ mClientTcpAcceptSrcPort = sourcePort;
+ mClientTcpAcceptTube = tube;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onClientAcceptedAsUnix(
+ const QString &listenAddress,
+ bool reqsCreds,
+ uchar credByte,
+ const Tp::AccountPtr &acc,
+ const Tp::IncomingStreamTubeChannelPtr &tube)
+{
+ qDebug() << "tube" << tube->objectPath() << "accepted at" << listenAddress;
+ qDebug() << "reqs creds:" << reqsCreds << "cred byte:" << credByte;
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mClientUnixAcceptAddr = listenAddress;
+ mClientUnixReqsCreds = reqsCreds;
+ mClientUnixAcceptTube = tube;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onNewClientConnection(
+ const Tp::AccountPtr &acc,
+ const Tp::IncomingStreamTubeChannelPtr &tube,
+ uint id)
+{
+ qDebug() << "new conn" << id << "on tube" << tube->objectPath();
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mNewClientConnectionTube = tube;
+ mNewClientConnectionId = id;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::onClientConnectionClosed(
+ const Tp::AccountPtr &acc,
+ const Tp::IncomingStreamTubeChannelPtr &tube,
+ uint id,
+ const QString &error,
+ const QString &message)
+{
+ qDebug() << "conn" << id << "closed on tube" << tube->objectPath();
+ qDebug() << "with error" << error << ':' << message;
+
+ // We don't use a shared factory here so the proxies will be different, but the object path
+ // should be the same
+ if (acc->objectPath() != mAcc->objectPath()) {
+ qWarning() << "account" << acc->objectPath() << "is not the expected" << mAcc->objectPath();
+ mLoop->exit(1);
+ return;
+ }
+
+ mClosedClientConnectionTube = tube;
+ mClosedClientConnectionId = id;
+ mClientConnectionCloseError = error;
+ mClientConnectionCloseMessage = message;
+
+ mLoop->exit(0);
+}
+
+void TestStreamTubeHandlers::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("stream-tube-handlers");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mAM = AccountManager::create();
+ QVERIFY(connect(mAM->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mAM->isReady(), true);
+
+ QVariantMap parameters;
+ parameters[QLatin1String("account")] = QLatin1String("foobar");
+ PendingAccount *pacc = mAM->createAccount(QLatin1String("foo"),
+ QLatin1String("bar"), QLatin1String("foobar"), parameters);
+ QVERIFY(connect(pacc,
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(pacc->account());
+ mAcc= pacc->account();
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+}
+
+void TestStreamTubeHandlers::init()
+{
+ initImpl();
+}
+
+void TestStreamTubeHandlers::testRegistration()
+{
+ StreamTubeServerPtr httpServer =
+ StreamTubeServer::create(QStringList() << QLatin1String("http"), QStringList());
+ StreamTubeServerPtr whiteboardServer =
+ StreamTubeServer::create(QStringList() << QLatin1String("sketch"),
+ QStringList() << QLatin1String("sketch"), QString(), true);
+ StreamTubeServerPtr activatedServer =
+ StreamTubeServer::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("vsftpd"));
+ StreamTubeServerPtr preferredHandlerServer = StreamTubeServer::create(QStringList());
+
+ StreamTubeClientPtr browser =
+ StreamTubeClient::create(QStringList() << QLatin1String("http"), QStringList(),
+ QLatin1String("Debian.Iceweasel"));
+ StreamTubeClientPtr collaborationTool =
+ StreamTubeClient::create(QStringList() << QLatin1String("sketch") << QLatin1String("ftp"),
+ QStringList() << QLatin1String("sketch"), QString(), false, true);
+ StreamTubeClientPtr invalidBecauseNoServicesClient =
+ StreamTubeClient::create(QStringList());
+
+ QVERIFY(!httpServer.isNull());
+ QVERIFY(!whiteboardServer.isNull());
+ QVERIFY(!activatedServer.isNull());
+ QVERIFY(!preferredHandlerServer.isNull());
+ QVERIFY(!browser.isNull());
+ QVERIFY(!collaborationTool.isNull());
+ QVERIFY(invalidBecauseNoServicesClient.isNull());
+
+ QCOMPARE(activatedServer->clientName(), QLatin1String("vsftpd"));
+ QCOMPARE(browser->clientName(), QLatin1String("Debian.Iceweasel"));
+
+ class CookieGenerator : public StreamTubeServer::ParametersGenerator
+ {
+ public:
+ CookieGenerator() : serial(0) {}
+
+ QVariantMap nextParameters(const AccountPtr &account, const OutgoingStreamTubeChannelPtr &tube,
+ const ChannelRequestHints &hints)
+ {
+ QVariantMap params;
+ params.insert(QLatin1String("cookie-y"),
+ QString(QLatin1String("e982mrh2mr2h+%1")).arg(serial++));
+ return params;
+ }
+
+ private:
+ uint serial;
+ } httpGenerator;
+
+ QVariantMap whiteboardParams;
+ whiteboardParams.insert(QLatin1String("password"),
+ QString::fromLatin1("s3kr1t"));
+
+ QTcpServer server;
+ server.listen();
+
+ httpServer->exportTcpSocket(QHostAddress::LocalHost, 80, &httpGenerator);
+ whiteboardServer->exportTcpSocket(QHostAddress::LocalHost, 31552, whiteboardParams);
+ activatedServer->exportTcpSocket(&server);
+ QCOMPARE(activatedServer->exportedParameters(), QVariantMap());
+ preferredHandlerServer->exportTcpSocket(QHostAddress::LocalHost, 6681);
+
+ browser->setToAcceptAsTcp();
+ collaborationTool->setToAcceptAsUnix(true);
+
+ QVERIFY(httpServer->isRegistered());
+ QVERIFY(whiteboardServer->isRegistered());
+ QVERIFY(activatedServer->isRegistered());
+ QVERIFY(preferredHandlerServer->isRegistered());
+ QVERIFY(browser->isRegistered());
+ QVERIFY(collaborationTool->isRegistered());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+
+ QVERIFY(handlers.contains(httpServer->clientName()));
+ QVERIFY(handlers.contains(whiteboardServer->clientName()));
+ QVERIFY(handlers.contains(QLatin1String("vsftpd")));
+ QVERIFY(handlers.contains(preferredHandlerServer->clientName()));
+ QVERIFY(handlers.contains(QLatin1String("Debian.Iceweasel")));
+ QVERIFY(handlers.contains(collaborationTool->clientName()));
+
+ QCOMPARE(handlers.size(), 6);
+
+ // The only-to-be-used-through-preferredHandler server should have an empty filter, but still be
+ // registered and introspectable
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handlers.value(preferredHandlerServer->clientName())->
+ requestPropertyHandlerChannelFilter(), &filter));
+ QVERIFY(filter.isEmpty());
+
+ // We didn't specify bypassApproval = true, so it should be false. for all we know we could be
+ // sent some fairly NSFW stuff on a HTTP tube :>
+ bool bypass;
+ QVERIFY(waitForProperty(handlers.value(browser->clientName())->requestPropertyBypassApproval(),
+ &bypass));
+ QVERIFY(!bypass);
+
+ // here we did, though, because we want our coworkers to be able to save our ass by launching a
+ // brainstorming UI on top of our again rather NSFW browsing habits while we're having a cup
+ QVERIFY(waitForProperty(handlers.value(collaborationTool->clientName())->
+ requestPropertyBypassApproval(), &bypass));
+ QVERIFY(bypass);
+}
+
+void TestStreamTubeHandlers::testBasicTcpExport()
+{
+ StreamTubeServerPtr server =
+ StreamTubeServer::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("vsftpd"));
+
+ QVariantMap params;
+ params.insert(QLatin1String("username"), QString::fromLatin1("user"));
+ params.insert(QLatin1String("password"), QString::fromLatin1("pass"));
+
+ server->exportTcpSocket(QHostAddress::LocalHost, 22, params);
+
+ QVERIFY(server->isRegistered());
+ QCOMPARE(server->exportedTcpSocketAddress(),
+ qMakePair(QHostAddress(QHostAddress::LocalHost), quint16(22)));
+ QCOMPARE(server->exportedParameters(), params);
+ QVERIFY(!server->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(server->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::outgoingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(true, HandleTypeContact, false);
+
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onTubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints))));
+
+ QDateTime userActionTime = QDateTime::currentDateTime().addDays(-1);
+ userActionTime = userActionTime.addMSecs(-userActionTime.time().msec());
+
+ QVariantMap hints;
+ hints.insert(QLatin1String("tp-qt4-test-request-hint-herring-color-rgba"), uint(0xff000000));
+
+ QObject *request = new QObject(this);
+ QString requestPath =
+ QLatin1String("/org/freedesktop/Telepathy/ChannelRequest/RequestForSimpleTcpExport");
+
+ QDBusConnection bus = server->registrar()->dbusConnection();
+ new ChannelRequestAdaptor(QDBusObjectPath(mAcc->objectPath()),
+ userActionTime.toTime_t(),
+ QString(),
+ QualifiedPropertyValueMapList(),
+ QStringList(),
+ false,
+ false,
+ hints,
+ request);
+ QVERIFY(bus.registerService(TP_QT4_CHANNEL_DISPATCHER_BUS_NAME));
+ QVERIFY(bus.registerObject(requestPath, request));
+
+ // Invoke the handler, verifying that we're notified when that happens with the correct tube
+ // details
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList() << QDBusObjectPath(requestPath),
+ userActionTime.toTime_t(),
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mRequestedTube.isNull());
+ QCOMPARE(mRequestedTube->objectPath(), chan.first);
+ QCOMPARE(mRequestTime, userActionTime);
+ QCOMPARE(mRequestHints.allHints(), hints);
+
+ // Verify that the state recovery accessors return sensible values at this point
+ QList<StreamTubeServer::Tube> serverTubes = server->tubes();
+ QCOMPARE(serverTubes.size(), 1);
+ QCOMPARE(serverTubes.first().account()->objectPath(), mAcc->objectPath());
+ QCOMPARE(serverTubes.first().channel(), mRequestedTube);
+
+ QVERIFY(server->tcpConnections().isEmpty());
+
+ // Let's run until the tube has been offered
+ while (mRequestedTube->isValid() && mRequestedTube->state() != TubeChannelStateRemotePending) {
+ mLoop->processEvents();
+ }
+
+ QVERIFY(mRequestedTube->isValid());
+
+ // Simulate a peer connecting (makes the tube Open up)
+ GValue *connParam = tp_g_value_slice_new_static_string("ignored");
+ tp_tests_stream_tube_channel_peer_connected_no_stream(mChanServices.back(),
+ connParam, tp_base_channel_get_target_handle(TP_BASE_CHANNEL(mChanServices.back())));
+ tp_g_value_slice_free(connParam);
+
+ // The params are set once the tube is open (we've picked up the first peer connection)
+ while (mRequestedTube->isValid() && mRequestedTube->state() != TubeChannelStateOpen) {
+ mLoop->processEvents();
+ }
+
+ // Verify the params
+ QVERIFY(mRequestedTube->isValid());
+ QCOMPARE(mRequestedTube->parameters(), params);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onServerTubeClosed(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QString,QString))));
+ mRequestedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mServerClosedTube, mRequestedTube);
+ QCOMPARE(mServerCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::testFailedExport()
+{
+ StreamTubeServerPtr server =
+ StreamTubeServer::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("vsftpd"));
+ server->exportTcpSocket(QHostAddress::LocalHost, 22);
+ QVERIFY(server->isRegistered());
+
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onTubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints))));
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onServerTubeClosed(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QString,QString))));
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(server->clientName());
+ QVERIFY(handler != 0);
+
+ // To trigger the Offer error codepath, give it a channel which only supports Unix sockets
+ // although we're exporting a TCP one - which is always supported in real CMs
+ QPair<QString, QVariantMap> chan = createTubeChannel(true, HandleTypeContact, false, true);
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+
+ // We should initially get tubeRequested just fine
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mRequestedTube.isNull());
+ QCOMPARE(mRequestedTube->objectPath(), chan.first);
+
+ // THEN we should get a tube close because the offer fails
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mServerClosedTube, mRequestedTube);
+ QCOMPARE(mServerCloseError, QString(TP_QT4_ERROR_NOT_IMPLEMENTED)); // == AF unsupported by "CM"
+}
+
+void TestStreamTubeHandlers::testServerConnMonitoring()
+{
+ StreamTubeServerPtr server =
+ StreamTubeServer::create(QStringList(), QStringList() << QLatin1String("multiftp"),
+ QLatin1String("warezd"), true);
+
+ server->exportTcpSocket(QHostAddress::LocalHost, 22);
+
+ QVERIFY(server->isRegistered());
+ QVERIFY(server->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(server->clientName());
+ QVERIFY(handler != 0);
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(true, HandleTypeRoom, true);
+
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onTubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints))));
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onServerTubeClosed(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QString,QString))));
+
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mRequestedTube.isNull());
+ QCOMPARE(mRequestedTube->objectPath(), chan.first);
+
+ // There are no connections at this point
+ QVERIFY(server->tcpConnections().isEmpty());
+
+ // Let's run until the tube has been offered
+ while (mRequestedTube->isValid() && mRequestedTube->state() != TubeChannelStateRemotePending) {
+ mLoop->processEvents();
+ }
+ QVERIFY(mRequestedTube->isValid());
+
+ // Still no connections
+ QVERIFY(server->tcpConnections().isEmpty());
+
+ // Now, some connections actually start popping up
+ QVERIFY(connect(server.data(),
+ SIGNAL(newTcpConnection(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,Tp::OutgoingStreamTubeChannelPtr)),
+ SLOT(onNewServerConnection(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,Tp::OutgoingStreamTubeChannelPtr))));
+ QVERIFY(connect(server.data(),
+ SIGNAL(tcpConnectionClosed(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,QString,QString,Tp::OutgoingStreamTubeChannelPtr)),
+ SLOT(onServerConnectionClosed(QHostAddress,quint16,Tp::AccountPtr,Tp::ContactPtr,QString,QString,Tp::OutgoingStreamTubeChannelPtr))));
+
+ // Simulate the first peer connecting (makes the tube Open up)
+ GValue *connParam = tp_g_value_slice_new_take_boxed(
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4,
+ dbus_g_type_specialized_construct(TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
+
+ QHostAddress expectedAddress = QHostAddress::LocalHost;
+ quint16 expectedPort = 1;
+
+ dbus_g_type_struct_set(connParam,
+ 0, expectedAddress.toString().toLatin1().constData(),
+ 1, expectedPort,
+ G_MAXUINT);
+
+ TpHandleRepoIface *contactRepo = tp_base_connection_get_handles(
+ TP_BASE_CONNECTION(mConn->service()), TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle = tp_handle_ensure(contactRepo, "first", NULL, NULL);
+
+ tp_tests_stream_tube_channel_peer_connected_no_stream(mChanServices.back(), connParam, handle);
+
+ // We should get a newTcpConnection signal now and tcpConnections() should include this new conn
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mNewServerConnectionAddress, expectedAddress);
+ QCOMPARE(mNewServerConnectionPort, expectedPort);
+ QCOMPARE(mNewServerConnectionContact->handle()[0], handle);
+ QCOMPARE(mNewServerConnectionContact->id(), QLatin1String("first"));
+ QCOMPARE(mNewServerConnectionTube, mRequestedTube);
+
+ QHash<QPair<QHostAddress, quint16>, StreamTubeServer::RemoteContact > conns =
+ server->tcpConnections();
+ QCOMPARE(conns.size(), 1);
+ QVERIFY(conns.contains(qMakePair(expectedAddress, expectedPort)));
+ QCOMPARE(conns.value(qMakePair(expectedAddress, expectedPort)).account()->objectPath(), mAcc->objectPath());
+ QCOMPARE(conns.value(qMakePair(expectedAddress, expectedPort)).contact(), mNewServerConnectionContact);
+
+ // Now, close the first connection
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanServices.back(),
+ TP_ERROR_STR_DISCONNECTED);
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClosedServerConnectionAddress, expectedAddress);
+ QCOMPARE(mClosedServerConnectionPort, expectedPort);
+ QCOMPARE(mClosedServerConnectionContact, mNewServerConnectionContact);
+ QCOMPARE(mServerConnectionCloseError, QString(TP_QT4_ERROR_DISCONNECTED));
+ QVERIFY(server->tcpConnections().isEmpty());
+
+ // Fire up two new connections
+ handle = tp_handle_ensure(contactRepo, "second", NULL, NULL);
+ expectedPort = 2;
+ dbus_g_type_struct_set(connParam, 1, expectedPort, G_MAXUINT);
+ tp_tests_stream_tube_channel_peer_connected_no_stream(mChanServices.back(), connParam, handle);
+
+ handle = tp_handle_ensure(contactRepo, "third", NULL, NULL);
+ expectedPort = 3;
+ dbus_g_type_struct_set(connParam, 1, expectedPort, G_MAXUINT);
+ tp_tests_stream_tube_channel_peer_connected_no_stream(mChanServices.back(), connParam, handle);
+
+ // We should get two newTcpConnection signals now and tcpConnections() should include these
+ // connections
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mNewServerConnectionAddress, expectedAddress);
+ QCOMPARE(mNewServerConnectionPort, quint16(2));
+ QCOMPARE(mNewServerConnectionContact->id(), QLatin1String("second"));
+ QCOMPARE(mNewServerConnectionTube, mRequestedTube);
+ QCOMPARE(server->tcpConnections().size(), 1);
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mNewServerConnectionAddress, expectedAddress);
+ QCOMPARE(mNewServerConnectionPort, quint16(3));
+ QCOMPARE(mNewServerConnectionContact->id(), QLatin1String("third"));
+ QCOMPARE(mNewServerConnectionTube, mRequestedTube);
+ QCOMPARE(server->tcpConnections().size(), 2);
+
+ // Close one of them, and check that we receive the signal for it
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanServices.back(),
+ TP_ERROR_STR_DISCONNECTED);
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClosedServerConnectionAddress, expectedAddress);
+ QCOMPARE(mClosedServerConnectionPort, quint16(3));
+ QCOMPARE(mClosedServerConnectionContact, mNewServerConnectionContact);
+ QCOMPARE(mServerConnectionCloseError, QString(TP_QT4_ERROR_DISCONNECTED));
+ QCOMPARE(server->tcpConnections().size(), 1);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(mServerClosedTube.isNull());
+ QCOMPARE(server->tubes().size(), 1);
+ mClosedServerConnectionContact.reset();
+ mRequestedTube->requestClose();
+
+ while (mClosedServerConnectionContact.isNull() || mServerClosedTube.isNull()) {
+ QVERIFY(mServerClosedTube.isNull()); // we should get the conn close first, only then tube close
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QVERIFY(server->tubes().isEmpty());
+
+ QVERIFY(server->tcpConnections().isEmpty());
+ QCOMPARE(mClosedServerConnectionAddress, expectedAddress);
+ QCOMPARE(mClosedServerConnectionPort, quint16(2));
+ QCOMPARE(mClosedServerConnectionContact->id(), QLatin1String("second"));
+ QCOMPARE(mServerConnectionCloseError, QString(TP_QT4_ERROR_ORPHANED)); // parent tube died
+
+ QCOMPARE(mServerClosedTube, mRequestedTube);
+ QCOMPARE(mServerCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::testSSTHErrorPaths()
+{
+ // Create and look up a handler with an incorrectly set up channel factory
+ ChannelFactoryPtr chanFactory = ChannelFactory::create(QDBusConnection::sessionBus());
+ chanFactory->setSubclassForIncomingStreamTubes<Tp::Channel>();
+ StreamTubeServerPtr server =
+ StreamTubeServer::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("vsftpd"),
+ false,
+ AccountFactory::create(QDBusConnection::sessionBus()),
+ ConnectionFactory::create(QDBusConnection::sessionBus()),
+ chanFactory);
+ server->exportTcpSocket(QHostAddress::LocalHost, 22);
+ QVERIFY(server->isRegistered());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(server->clientName());
+ QVERIFY(handler != 0);
+
+ // Pass it a text channel, and with no satisfied requests
+ QString textChanPath = mConn->objectPath() + QLatin1String("/TextChannel");
+ QByteArray chanPath(textChanPath.toAscii());
+ ExampleEchoChannel *textChanService = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", TpHandle(1),
+ NULL));
+
+ ChannelDetails details = { QDBusObjectPath(textChanPath),
+ ChannelClassSpec::textChat().allProperties() };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+ processDBusQueue(mConn->client().data());
+
+ // Now pass it an incoming stream tube chan, which will trigger the error paths for constructing
+ // wrong subclasses for tubes
+ QPair<QString, QVariantMap> tubeChan = createTubeChannel(false, HandleTypeContact, false);
+
+ details.channel = QDBusObjectPath(tubeChan.first);
+ details.properties = tubeChan.second;
+
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+ processDBusQueue(mConn->client().data());
+
+ // Now pass it an outgoing stream tube chan (which we didn't set an incorrect subclass for), but
+ // which doesn't actually exist so introspection fails
+
+ details.channel = QDBusObjectPath(QString::fromLatin1("/does/not/exist"));
+ details.properties = ChannelClassSpec::outgoingStreamTube(QLatin1String("ftp")).allProperties();
+
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+ processDBusQueue(mConn->client().data());
+
+ // Now pass an actual outgoing tube chan and verify it's still signaled correctly after all
+ // these incorrect invocations of the handler
+ QVERIFY(connect(server.data(),
+ SIGNAL(tubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints)),
+ SLOT(onTubeRequested(Tp::AccountPtr,Tp::OutgoingStreamTubeChannelPtr,QDateTime,Tp::ChannelRequestHints))));
+
+ tubeChan = createTubeChannel(true, HandleTypeContact, false);
+
+ details.channel = QDBusObjectPath(tubeChan.first);
+ details.properties = tubeChan.second;
+
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+ processDBusQueue(mConn->client().data());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mRequestedTube->objectPath(), tubeChan.first);
+
+ // TODO: if/when the QDBus bug about not being able to wait for local loop replies even with a
+ // main loop is fixed, wait for the HandleChannels invocations to return properly. For now just
+ // run 100 pings to the service, during which the codepaths get run almost certainly.
+ for (int i = 0; i < 100; i++) {
+ processDBusQueue(mConn->client().data());
+ }
+
+ g_object_unref(textChanService);
+}
+
+void TestStreamTubeHandlers::testClientBasicTcp()
+{
+ StreamTubeClientPtr client =
+ StreamTubeClient::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("ncftp"));
+
+ class FakeGenerator : public StreamTubeClient::TcpSourceAddressGenerator
+ {
+ public:
+ FakeGenerator() : port(0) {}
+
+ QPair<QHostAddress, quint16> nextSourceAddress(const AccountPtr &account,
+ const IncomingStreamTubeChannelPtr &tube) {
+ return qMakePair(QHostAddress(QHostAddress::LocalHost), ++port);
+ }
+
+ quint16 port;
+ } gen;
+
+ client->setToAcceptAsTcp(&gen);
+ QVERIFY(client->isRegistered());
+ QCOMPARE(client->registrar()->registeredClients().size(), 1);
+ QVERIFY(client->acceptsAsTcp());
+ QVERIFY(!client->acceptsAsUnix());
+ QCOMPARE(client->tcpGenerator(), static_cast<StreamTubeClient::TcpSourceAddressGenerator *>(&gen));
+ QVERIFY(!client->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(client->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::incomingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(false, HandleTypeContact, true);
+
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeAcceptedAsTcp(QHostAddress,quint16,QHostAddress,quint16,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onClientAcceptedAsTcp(QHostAddress,quint16,QHostAddress,quint16,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+
+ // Invoke the handler, verifying that we're notified when that happens with the correct tube
+ // details
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ 0, // not an user action
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mOfferedTube.isNull());
+ QCOMPARE(mOfferedTube->objectPath(), chan.first);
+
+ // Verify that the state recovery accessors return sensible values at this point
+ QList<StreamTubeClient::Tube> clientTubes = client->tubes();
+ QCOMPARE(clientTubes.size(), 1);
+ QCOMPARE(clientTubes.first().account()->objectPath(), mAcc->objectPath());
+ QCOMPARE(clientTubes.first().channel(), mOfferedTube);
+
+ QVERIFY(client->connections().isEmpty());
+
+ // Let's run until we've accepted the tube
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mOfferedTube->isValid());
+ QCOMPARE(mClientTcpAcceptAddr, QHostAddress(QHostAddress::LocalHost));
+ QVERIFY(mClientTcpAcceptPort != 0);
+ QCOMPARE(mClientTcpAcceptSrcAddr, QHostAddress(QHostAddress::LocalHost));
+ QCOMPARE(mClientTcpAcceptSrcPort, gen.port);
+ QCOMPARE(mClientTcpAcceptTube, mOfferedTube);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onClientTubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString))));
+ mOfferedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClientClosedTube, mOfferedTube);
+ QCOMPARE(mClientCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::testClientTcpGeneratorIgnore()
+{
+ StreamTubeClientPtr client =
+ StreamTubeClient::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("ncftp"));
+
+ class FakeGenerator : public StreamTubeClient::TcpSourceAddressGenerator
+ {
+ public:
+ QPair<QHostAddress, quint16> nextSourceAddress(const AccountPtr &account,
+ const IncomingStreamTubeChannelPtr &tube) {
+ return qMakePair(QHostAddress(QHostAddress::LocalHost), quint16(1111));
+ }
+ } gen;
+
+ client->setToAcceptAsTcp(&gen);
+ QVERIFY(client->isRegistered());
+ QVERIFY(client->acceptsAsTcp());
+ QVERIFY(!client->acceptsAsUnix());
+ QCOMPARE(client->tcpGenerator(), static_cast<StreamTubeClient::TcpSourceAddressGenerator *>(&gen));
+ QVERIFY(!client->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(client->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::incomingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(false, HandleTypeContact, false);
+
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeAcceptedAsTcp(QHostAddress,quint16,QHostAddress,quint16,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onClientAcceptedAsTcp(QHostAddress,quint16,QHostAddress,quint16,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+
+ // Invoke the handler, verifying that we're notified when that happens with the correct tube
+ // details
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ 0, // not an user action
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mOfferedTube.isNull());
+ QCOMPARE(mOfferedTube->objectPath(), chan.first);
+
+ // Verify that the state recovery accessors return sensible values at this point
+ QList<StreamTubeClient::Tube> clientTubes = client->tubes();
+ QCOMPARE(clientTubes.size(), 1);
+ QCOMPARE(clientTubes.first().account()->objectPath(), mAcc->objectPath());
+ QCOMPARE(clientTubes.first().channel(), mOfferedTube);
+
+ QVERIFY(client->connections().isEmpty());
+
+ // Let's run until we've accepted the tube
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mOfferedTube->isValid());
+ QCOMPARE(mClientTcpAcceptAddr, QHostAddress(QHostAddress::LocalHost));
+ QVERIFY(mClientTcpAcceptPort != 0);
+ QCOMPARE(mClientTcpAcceptSrcAddr, QHostAddress(QHostAddress::Any));
+ QCOMPARE(mClientTcpAcceptSrcPort, quint16(0));
+ QCOMPARE(mClientTcpAcceptTube, mOfferedTube);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onClientTubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString))));
+ mOfferedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClientClosedTube, mOfferedTube);
+ QCOMPARE(mClientCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::testClientTcpUnsupported()
+{
+ StreamTubeClientPtr client =
+ StreamTubeClient::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("ncftp"));
+
+ client->setToAcceptAsTcp();
+ QVERIFY(client->isRegistered());
+ QVERIFY(client->acceptsAsTcp());
+ QVERIFY(!client->acceptsAsUnix());
+ QVERIFY(!client->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(client->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::incomingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(false, HandleTypeContact, false, true);
+
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onClientTubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString))));
+
+ // Invoke the handler, verifying that we're notified when that happens with the correct tube
+ // details
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ 0, // not an user action
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mOfferedTube.isNull());
+ QCOMPARE(mOfferedTube->objectPath(), chan.first);
+
+ // Now, run until Accept fails (because TCP is not supported by the fake service here) and
+ // consequently the tube is closed
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClientClosedTube, mOfferedTube);
+ QCOMPARE(mClientCloseError, QString(TP_QT4_ERROR_NOT_IMPLEMENTED)); // == AF unsupported
+}
+
+void TestStreamTubeHandlers::testClientBasicUnix()
+{
+ StreamTubeClientPtr client =
+ StreamTubeClient::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("ncftp"));
+
+ client->setToAcceptAsUnix(true);
+ QVERIFY(client->isRegistered());
+ QCOMPARE(client->registrar()->registeredClients().size(), 1);
+ QVERIFY(!client->acceptsAsTcp());
+ QVERIFY(client->acceptsAsUnix());
+ QCOMPARE(client->tcpGenerator(), static_cast<StreamTubeClient::TcpSourceAddressGenerator *>(0));
+ QVERIFY(!client->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(client->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::incomingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(false, HandleTypeContact, true);
+
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeAcceptedAsUnix(QString,bool,uchar,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onClientAcceptedAsUnix(QString,bool,uchar,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+
+ // Invoke the handler, verifying that we're notified when that happens with the correct tube
+ // details
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ 0, // not an user action
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mOfferedTube.isNull());
+ QCOMPARE(mOfferedTube->objectPath(), chan.first);
+
+ // Verify that the state recovery accessors return sensible values at this point
+ QList<StreamTubeClient::Tube> clientTubes = client->tubes();
+ QCOMPARE(clientTubes.size(), 1);
+ QCOMPARE(clientTubes.first().account()->objectPath(), mAcc->objectPath());
+ QCOMPARE(clientTubes.first().channel(), mOfferedTube);
+
+ QVERIFY(client->connections().isEmpty());
+
+ // Let's run until we've accepted the tube
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mOfferedTube->isValid());
+ QVERIFY(!mClientUnixAcceptAddr.isNull());
+ QVERIFY(mClientUnixReqsCreds);
+ QCOMPARE(mClientUnixAcceptTube, mOfferedTube);
+ QCOMPARE(mOfferedTube->addressType(), SocketAddressTypeUnix);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onClientTubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString))));
+ mOfferedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClientClosedTube, mOfferedTube);
+ QCOMPARE(mClientCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::testClientUnixCredsIgnore()
+{
+ StreamTubeClientPtr client =
+ StreamTubeClient::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("ncftp"));
+
+ client->setToAcceptAsUnix(true);
+ QVERIFY(client->isRegistered());
+ QVERIFY(!client->acceptsAsTcp());
+ QVERIFY(client->acceptsAsUnix());
+ QCOMPARE(client->tcpGenerator(), static_cast<StreamTubeClient::TcpSourceAddressGenerator *>(0));
+ QVERIFY(!client->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(client->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::incomingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(false, HandleTypeContact, false);
+
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeAcceptedAsUnix(QString,bool,uchar,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onClientAcceptedAsUnix(QString,bool,uchar,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+
+ // Invoke the handler, verifying that we're notified when that happens with the correct tube
+ // details
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ 0, // not an user action
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mOfferedTube.isNull());
+ QCOMPARE(mOfferedTube->objectPath(), chan.first);
+
+ // Verify that the state recovery accessors return sensible values at this point
+ QList<StreamTubeClient::Tube> clientTubes = client->tubes();
+ QCOMPARE(clientTubes.size(), 1);
+ QCOMPARE(clientTubes.first().account()->objectPath(), mAcc->objectPath());
+ QCOMPARE(clientTubes.first().channel(), mOfferedTube);
+
+ QVERIFY(client->connections().isEmpty());
+
+ // Let's run until we've accepted the tube
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mOfferedTube->isValid());
+ QVERIFY(!mClientUnixAcceptAddr.isNull());
+ QVERIFY(!mClientUnixReqsCreds);
+ QCOMPARE(mClientUnixAcceptTube, mOfferedTube);
+ QCOMPARE(mOfferedTube->addressType(), SocketAddressTypeUnix);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onClientTubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString))));
+ mOfferedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClientClosedTube, mOfferedTube);
+ QCOMPARE(mClientCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::testClientConnMonitoring()
+{
+ StreamTubeClientPtr client =
+ StreamTubeClient::create(QStringList() << QLatin1String("ftp"), QStringList(),
+ QLatin1String("ncftp"), true);
+
+ client->setToAcceptAsTcp();
+ QVERIFY(client->isRegistered());
+ QVERIFY(client->acceptsAsTcp());
+ QVERIFY(client->monitorsConnections());
+
+ QMap<QString, ClientHandlerInterface *> handlers = ourHandlers();
+
+ QVERIFY(!handlers.isEmpty());
+ ClientHandlerInterface *handler = handlers.value(client->clientName());
+ QVERIFY(handler != 0);
+
+ ChannelClassList filter;
+ QVERIFY(waitForProperty(handler->requestPropertyHandlerChannelFilter(), &filter));
+
+ QCOMPARE(filter.size(), 1);
+ QVERIFY(ChannelClassSpec(filter.first())
+ == ChannelClassSpec::incomingStreamTube(QLatin1String("ftp")));
+
+ QPair<QString, QVariantMap> chan = createTubeChannel(false, HandleTypeContact, true);
+
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onTubeOffered(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeAcceptedAsTcp(QHostAddress,quint16,QHostAddress,quint16,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr)),
+ SLOT(onClientAcceptedAsTcp(QHostAddress,quint16,QHostAddress,quint16,
+ Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr))));
+
+ ChannelDetails details = { QDBusObjectPath(chan.first), chan.second };
+ handler->HandleChannels(
+ QDBusObjectPath(mAcc->objectPath()),
+ QDBusObjectPath(mConn->objectPath()),
+ ChannelDetailsList() << details,
+ ObjectPathList(),
+ QDateTime::currentDateTime().toTime_t(),
+ QVariantMap());
+
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(!mOfferedTube.isNull());
+ QCOMPARE(mOfferedTube->objectPath(), chan.first);
+
+ // There are no connections at this point
+ QVERIFY(client->connections().isEmpty());
+
+ // Let's run until we've accepted the tube
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mOfferedTube->isValid());
+ QCOMPARE(mClientTcpAcceptAddr, QHostAddress(QHostAddress::LocalHost));
+ QVERIFY(mClientTcpAcceptPort != 0);
+ QCOMPARE(mClientTcpAcceptSrcAddr, QHostAddress(QHostAddress::Any));
+ QCOMPARE(mClientTcpAcceptSrcPort, quint16(0));
+ QCOMPARE(mClientTcpAcceptTube, mOfferedTube);
+
+ // Still no connections
+ QVERIFY(client->connections().isEmpty());
+
+ // Now, some connections actually start popping up
+ QVERIFY(connect(client.data(),
+ SIGNAL(newConnection(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,uint)),
+ SLOT(onNewClientConnection(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,uint))));
+ QVERIFY(connect(client.data(),
+ SIGNAL(connectionClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,uint,QString,QString)),
+ SLOT(onClientConnectionClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,uint,QString,QString))));
+
+ QTcpSocket first;
+ first.connectToHost(mClientTcpAcceptAddr, mClientTcpAcceptPort);
+ first.waitForConnected();
+ QVERIFY(first.isValid());
+ QCOMPARE(first.state(), QAbstractSocket::ConnectedState);
+
+ // We should get a newConnection signal now and connections() should include this new conn
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mNewClientConnectionTube, mOfferedTube);
+ QHash<StreamTubeClient::Tube, QSet<uint> > conns = client->connections();
+ QCOMPARE(conns.size(), 1);
+ QCOMPARE(conns.values().first().size(), 1);
+ QVERIFY(conns.values().first().contains(mNewClientConnectionId));
+ uint firstId = mNewClientConnectionId;
+
+ // Now, close the first connection
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanServices.back(),
+ TP_ERROR_STR_DISCONNECTED);
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClosedClientConnectionId, firstId);
+ QCOMPARE(mClientConnectionCloseError, QString(TP_QT4_ERROR_DISCONNECTED));
+ QVERIFY(client->connections().isEmpty());
+
+ // Fire up two new connections
+ QTcpSocket second;
+ second.connectToHost(mClientTcpAcceptAddr, mClientTcpAcceptPort);
+ second.waitForConnected();
+ QVERIFY(second.isValid());
+ QCOMPARE(second.state(), QAbstractSocket::ConnectedState);
+
+ QTcpSocket third;
+ third.connectToHost(mClientTcpAcceptAddr, mClientTcpAcceptPort);
+ third.waitForConnected();
+ QVERIFY(third.isValid());
+ QCOMPARE(third.state(), QAbstractSocket::ConnectedState);
+
+ // We should get two newConnection signals now and connections() should include these
+ // connections
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mNewClientConnectionTube, mOfferedTube);
+ QCOMPARE(client->connections().size(), 1);
+ uint secondId = mNewClientConnectionId;
+
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mNewClientConnectionTube, mOfferedTube);
+ QCOMPARE(client->connections().size(), 1);
+ uint thirdId = mNewClientConnectionId;
+
+ conns = client->connections();
+ QCOMPARE(conns.size(), 1);
+ QCOMPARE(conns.values().first().size(), 2);
+ QVERIFY(conns.values().first().contains(secondId));
+ QVERIFY(conns.values().first().contains(thirdId));
+
+ // Close one of them, and check that we receive the signal for it
+ tp_tests_stream_tube_channel_last_connection_disconnected(mChanServices.back(),
+ TP_ERROR_STR_DISCONNECTED);
+ QCOMPARE(mLoop->exec(), 0);
+
+ QCOMPARE(mClosedClientConnectionId, thirdId);
+ QCOMPARE(mClientConnectionCloseError, QString(TP_QT4_ERROR_DISCONNECTED));
+ QCOMPARE(mClosedServerConnectionContact, mNewServerConnectionContact);
+
+ conns = client->connections();
+ QCOMPARE(conns.size(), 1);
+ QVERIFY(!conns.values().first().contains(thirdId));
+ QVERIFY(conns.values().first().contains(secondId));
+ QCOMPARE(conns.values().first().size(), 1);
+
+ // Now, close the tube and verify we're signaled about that
+ QVERIFY(connect(client.data(),
+ SIGNAL(tubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString)),
+ SLOT(onClientTubeClosed(Tp::AccountPtr,Tp::IncomingStreamTubeChannelPtr,QString,QString))));
+ mClosedClientConnectionId = 0xdeadbeefU;
+ QVERIFY(mClientClosedTube.isNull());
+ QCOMPARE(client->tubes().size(), 1);
+ mOfferedTube->requestClose();
+
+ while (mClosedClientConnectionId == 0xdeadbeefU || mClientClosedTube.isNull()) {
+ QVERIFY(mClientClosedTube.isNull()); // we should get first conn close, then tube close
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ QVERIFY(client->tubes().isEmpty());
+
+ QVERIFY(client->connections().isEmpty());
+ QCOMPARE(mClosedClientConnectionId, secondId);
+ QCOMPARE(mClientConnectionCloseError, QString(TP_QT4_ERROR_ORPHANED)); // parent tube died
+
+ QCOMPARE(mClientClosedTube, mOfferedTube);
+ QCOMPARE(mClientCloseError, QString(TP_QT4_ERROR_CANCELLED)); // == local close request
+}
+
+void TestStreamTubeHandlers::cleanup()
+{
+ cleanupImpl();
+
+ mRequestHints = ChannelRequestHints();
+
+ if (mRequestedTube && mRequestedTube->isValid()) {
+ qDebug() << "waiting for the reqd tube to become invalidated";
+
+ QVERIFY(connect(mRequestedTube.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ mLoop,
+ SLOT(quit())));
+ mRequestedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ mRequestedTube.reset();
+ mServerClosedTube.reset();
+ mNewServerConnectionContact.reset();
+ mClosedServerConnectionContact.reset();
+ mNewServerConnectionTube.reset();
+ mServerConnectionCloseTube.reset();
+
+ if (mOfferedTube && mOfferedTube->isValid()) {
+ qDebug() << "waiting for the ofrd tube to become invalidated";
+
+ QVERIFY(connect(mOfferedTube.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ mLoop,
+ SLOT(quit())));
+ mOfferedTube->requestClose();
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ mOfferedTube.reset();
+ mClientClosedTube.reset();
+ mClientTcpAcceptTube.reset();
+ mClientUnixAcceptTube.reset();
+ mNewClientConnectionTube.reset();
+ mClosedClientConnectionTube.reset();
+
+ while (!mChanServices.empty()) {
+ g_object_unref(mChanServices.back());
+ mChanServices.pop_back();
+ }
+
+ mLoop->processEvents();
+}
+
+void TestStreamTubeHandlers::cleanupTestCase()
+{
+ mAM.reset();
+ mAcc.reset();
+
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestStreamTubeHandlers)
+#include "_gen/stream-tube-handlers.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/streamed-media-chan.cpp b/qt4/tests/dbus/streamed-media-chan.cpp
new file mode 100644
index 000000000..48a49297b
--- /dev/null
+++ b/qt4/tests/dbus/streamed-media-chan.cpp
@@ -0,0 +1,1374 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/callable/conn.h>
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/StreamedMediaChannel>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+class TestStreamedMediaChan : public Test
+{
+ Q_OBJECT
+
+public:
+ TestStreamedMediaChan(QObject *parent = 0)
+ : Test(parent), mConn(0)
+ { }
+
+protected Q_SLOTS:
+ void expectRequestStreamsFinished(Tp::PendingOperation *);
+ void expectBusyRequestStreamsFinished(Tp::PendingOperation *);
+
+ // Special event handlers for the OutgoingCall state-machine
+ void expectOutgoingRequestStreamsFinished(Tp::PendingOperation *);
+
+ void onOutgoingGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &);
+
+ void onGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &);
+
+ void onStreamRemoved(const Tp::StreamedMediaStreamPtr &);
+ void onStreamDirectionChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamDirection,
+ Tp::MediaStreamPendingSend);
+ void onLSSChanged(Tp::StreamedMediaStream::SendingState);
+ void onRSSChanged(Tp::StreamedMediaStream::SendingState);
+ void onStreamStateChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamState);
+ void onChanInvalidated(Tp::DBusProxy *,
+ const QString &, const QString &);
+
+ // Special event handlers for the OutgoingCallTerminate state-machine
+ void expectTerminateRequestStreamsFinished(Tp::PendingOperation *);
+
+ void onTerminateGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &);
+
+ void onTerminateChanInvalidated(Tp::DBusProxy *,
+ const QString &, const QString &);
+
+ void onNewChannels(const Tp::ChannelDetailsList &);
+ void onLocalHoldStateChanged(Tp::LocalHoldState,
+ Tp::LocalHoldStateReason);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testOutgoingCall();
+ void testOutgoingCallBusy();
+ void testOutgoingCallNoAnswer();
+ void testOutgoingCallTerminate();
+ void testIncomingCall();
+ void testHold();
+ void testHoldNoUnhold();
+ void testHoldInabilityUnhold();
+ void testDTMF();
+ void testDTMFNoContinuousTone();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ TestConnHelper *mConn;
+ StreamedMediaChannelPtr mChan;
+ QList<ContactPtr> mContacts;
+ StreamedMediaStreams mRequestStreamsReturn;
+ Contacts mChangedCurrent;
+ Contacts mChangedLP;
+ Contacts mChangedRP;
+ Contacts mChangedRemoved;
+ Channel::GroupMemberChangeDetails mDetails;
+ StreamedMediaStreamPtr mStreamRemovedReturn;
+ StreamedMediaStreamPtr mSDCStreamReturn;
+ Tp::MediaStreamDirection mSDCDirectionReturn;
+ Tp::MediaStreamPendingSend mSDCPendingReturn;
+ StreamedMediaStreamPtr mSSCStreamReturn;
+ Tp::StreamedMediaStream::SendingState mChangedLSS;
+ Tp::StreamedMediaStream::SendingState mChangedRSS;
+ Tp::MediaStreamState mSSCStateReturn;
+ QQueue<uint> mLocalHoldStates;
+ QQueue<uint> mLocalHoldStateReasons;
+
+ // state machine for the OutgoingCall test-case
+ enum {
+ OutgoingStateInitial,
+ OutgoingStateRequested,
+ OutgoingStateRinging,
+ OutgoingStateDone
+ } mOutgoingState;
+ bool mOutgoingGotRequestStreamsFinished;
+ bool mOutgoingAudioDone;
+
+ // state machine for the OutgoingCallTerminate test-case
+ enum {
+ TerminateStateInitial,
+ TerminateStateRequested,
+ TerminateStateRinging,
+ TerminateStateAnswered,
+ TerminateStateTerminated
+ } mTerminateState;
+};
+
+void TestStreamedMediaChan::expectRequestStreamsFinished(PendingOperation *op)
+{
+ mRequestStreamsReturn.clear();
+
+ TEST_VERIFY_OP(op);
+
+ qDebug() << "request streams finished successfully";
+
+ PendingStreamedMediaStreams *pms = qobject_cast<PendingStreamedMediaStreams*>(op);
+ mRequestStreamsReturn = pms->streams();
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::expectBusyRequestStreamsFinished(PendingOperation *op)
+{
+ if (!op->isFinished()) {
+ qWarning() << "unfinished";
+ mLoop->exit(1);
+ return;
+ }
+
+ if (op->isError()) {
+ // The service signaled busy even before tp-qt4 finished introspection.
+ // FIXME: should the error be something else, actually? Such as, perchance,
+ // org.freedesktop.Telepathy.Error.Busy? (fd.o #29757).
+ QCOMPARE(op->errorName(), QLatin1String("org.freedesktop.Telepathy.Error.Cancelled"));
+ qDebug() << "request streams finished already busy";
+ mLoop->exit(0);
+ return;
+ }
+
+ qDebug() << "request streams finished successfully";
+
+ PendingStreamedMediaStreams *pms = qobject_cast<PendingStreamedMediaStreams*>(op);
+ mRequestStreamsReturn = pms->streams();
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::expectOutgoingRequestStreamsFinished(PendingOperation *op)
+{
+ QVERIFY(op->isFinished());
+ QVERIFY(!op->isError());
+ QVERIFY(op->isValid());
+
+ PendingStreamedMediaStreams *pms = qobject_cast<PendingStreamedMediaStreams*>(op);
+ mRequestStreamsReturn = pms->streams();
+
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ QCOMPARE(mContacts.size(), 1);
+ StreamedMediaStreamPtr stream = mRequestStreamsReturn.first();
+ QCOMPARE(stream->contact(), otherContact);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeAudio);
+
+ // These checks can't work reliably, unless we add some complex backdoors to the test service,
+ // to only start changing state / direction when we explicitly tell it so (not automatically
+ // when we have requested the stream)
+ // QCOMPARE(stream->state(), Tp::MediaStreamStateDisconnected);
+ // QCOMPARE(stream->direction(), Tp::MediaStreamDirectionBidirectional);
+
+ QCOMPARE(mChan->streams().size(), 1);
+ QVERIFY(mChan->streams().contains(stream));
+
+ qDebug() << "stream requested successfully";
+
+ // Only advance to Requested if the remote moving to RP hasn't already advanced it to Ringing or
+ // even Answered - tbf it seems the StreamedMediaChannel semantics are quite hard for
+ // application code to get right because the events can happen in whichever order. Should this
+ // be considered a bug by itself? It'd probably be pretty hard to fix so I hope not :D
+ if (mOutgoingState == OutgoingStateInitial) {
+ mOutgoingState = OutgoingStateRequested;
+ }
+
+ if (mOutgoingState == OutgoingStateDone) {
+ // finished later than the membersChanged() - exit mainloop now
+ mLoop->exit(0);
+ } else {
+ // finished earlier than membersChanged() - it will exit
+ mOutgoingGotRequestStreamsFinished = true;
+ }
+}
+
+void TestStreamedMediaChan::onOutgoingGroupMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ // At this point, mContacts should still contain the contact we requested the
+ // stream for
+ ContactPtr otherContact = mContacts.first();
+
+ if (mOutgoingState == OutgoingStateInitial || mOutgoingState == OutgoingStateRequested) {
+ // The target should have become remote pending now
+ QVERIFY(groupMembersAdded.isEmpty());
+ QVERIFY(groupLocalPendingMembersAdded.isEmpty());
+ QCOMPARE(groupRemotePendingMembersAdded.size(), 1);
+ QVERIFY(groupMembersRemoved.isEmpty());
+
+ QVERIFY(mChan->groupRemotePendingContacts().contains(otherContact));
+ QCOMPARE(mChan->awaitingRemoteAnswer(), true);
+
+ qDebug() << "call now ringing";
+
+ mOutgoingState = OutgoingStateRinging;
+ } else if (mOutgoingState == OutgoingStateRinging) {
+ QCOMPARE(groupMembersAdded.size(), 1);
+ QVERIFY(groupLocalPendingMembersAdded.isEmpty());
+ QVERIFY(groupRemotePendingMembersAdded.isEmpty());
+ QVERIFY(groupMembersRemoved.isEmpty());
+
+ QCOMPARE(mChan->groupContacts().size(), 2);
+ QVERIFY(mChan->groupContacts().contains(otherContact));
+ QCOMPARE(mChan->awaitingRemoteAnswer(), false);
+
+ qDebug() << "call now answered";
+
+ mOutgoingState = OutgoingStateDone;
+ mOutgoingAudioDone = true;
+
+ // Exit if we already got finished() from requestStreams() - otherwise the finish callback
+ // will exit
+ if (mOutgoingGotRequestStreamsFinished) {
+ mLoop->exit(0);
+ }
+ }
+
+ qDebug() << "group members changed";
+ mChangedCurrent = groupMembersAdded;
+ mChangedLP = groupLocalPendingMembersAdded;
+ mChangedRP = groupRemotePendingMembersAdded;
+ mChangedRemoved = groupMembersRemoved;
+ mDetails = details;
+}
+
+void TestStreamedMediaChan::onGroupMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ qDebug() << "group members changed";
+ mChangedCurrent = groupMembersAdded;
+ mChangedLP = groupLocalPendingMembersAdded;
+ mChangedRP = groupRemotePendingMembersAdded;
+ mChangedRemoved = groupMembersRemoved;
+ mDetails = details;
+}
+
+void TestStreamedMediaChan::onStreamRemoved(const StreamedMediaStreamPtr &stream)
+{
+ qDebug() << "stream" << stream.data() << "removed";
+ mStreamRemovedReturn = stream;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::onStreamDirectionChanged(const StreamedMediaStreamPtr &stream,
+ Tp::MediaStreamDirection direction,
+ Tp::MediaStreamPendingSend pendingSend)
+{
+ qDebug() << "stream" << stream.data() << "direction changed to" << direction;
+ mSDCStreamReturn = stream;
+ mSDCDirectionReturn = direction;
+ mSDCPendingReturn = pendingSend;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::onLSSChanged(Tp::StreamedMediaStream::SendingState state)
+{
+ qDebug() << "onLSSChanged: " << static_cast<uint>(state);
+ mChangedLSS = state;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::onRSSChanged(Tp::StreamedMediaStream::SendingState state)
+{
+ qDebug() << "onRSSChanged: " << static_cast<uint>(state);
+ mChangedRSS = state;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::onStreamStateChanged(const StreamedMediaStreamPtr &stream,
+ Tp::MediaStreamState state)
+{
+ qDebug() << "stream" << stream.data() << "state changed to" << state;
+ mSSCStreamReturn = stream;
+ mSSCStateReturn = state;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::onChanInvalidated(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ qDebug() << "chan invalidated:" << errorName << "-" << errorMessage;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::expectTerminateRequestStreamsFinished(PendingOperation *op)
+{
+ QVERIFY(op->isFinished());
+
+ if (op->isError()) {
+ // FIXME: should the error be something else, actually? Such as, perchance,
+ // org.freedesktop.Telepathy.Error.Terminated? (fd.o #29757).
+ QCOMPARE(op->errorName(), QLatin1String("org.freedesktop.Telepathy.Error.Cancelled"));
+ qDebug() << "The remote hung up before we even got to take a look at the stream!";
+ mTerminateState = TerminateStateTerminated;
+ mLoop->exit(0);
+ return;
+ }
+
+ QVERIFY(op->isValid());
+
+ PendingStreamedMediaStreams *pms = qobject_cast<PendingStreamedMediaStreams*>(op);
+ mRequestStreamsReturn = pms->streams();
+
+ qDebug() << "stream requested successfully";
+
+ // Only advance to Requested if the remote moving to RP hasn't already advanced it to Ringing or
+ // even Answered - tbf it seems the StreamedMediaChannel semantics are quite hard for
+ // application code to get right because the events can happen in whichever order. Should this
+ // be considered a bug by itself? It'd probably be pretty hard to fix so I hope not :D
+ if (mTerminateState == TerminateStateInitial) {
+ mTerminateState = TerminateStateRequested;
+ }
+}
+
+void TestStreamedMediaChan::onTerminateGroupMembersChanged(
+ const Contacts &groupMembersAdded,
+ const Contacts &groupLocalPendingMembersAdded,
+ const Contacts &groupRemotePendingMembersAdded,
+ const Contacts &groupMembersRemoved,
+ const Channel::GroupMemberChangeDetails &details)
+{
+ // At this point, mContacts should still contain the contact we requested the
+ // stream for
+ ContactPtr otherContact = mContacts.first();
+
+ if (mTerminateState == TerminateStateInitial || mTerminateState == TerminateStateRequested) {
+ // The target should have become remote pending now
+ QVERIFY(groupMembersAdded.isEmpty());
+ QVERIFY(groupLocalPendingMembersAdded.isEmpty());
+ QCOMPARE(groupRemotePendingMembersAdded.size(), 1);
+ QVERIFY(groupMembersRemoved.isEmpty());
+
+ QVERIFY(mChan->groupRemotePendingContacts().contains(otherContact));
+ QCOMPARE(mChan->awaitingRemoteAnswer(), true);
+
+ qDebug() << "call now ringing";
+
+ mTerminateState = TerminateStateRinging;
+ } else if (mTerminateState == TerminateStateRinging) {
+ QCOMPARE(groupMembersAdded.size(), 1);
+ QVERIFY(groupLocalPendingMembersAdded.isEmpty());
+ QVERIFY(groupRemotePendingMembersAdded.isEmpty());
+ QVERIFY(groupMembersRemoved.isEmpty());
+
+ QCOMPARE(mChan->groupContacts().size(), 2);
+ QVERIFY(mChan->groupContacts().contains(otherContact));
+ QCOMPARE(mChan->awaitingRemoteAnswer(), false);
+
+ qDebug() << "call now answered";
+
+ mTerminateState = TerminateStateAnswered;
+ } else if (mTerminateState == TerminateStateAnswered) {
+ // It might be actually that currently this won't happen before invalidated() is emitted, so
+ // we'll never reach this due to having exited the mainloop already - but it's entirely
+ // valid for the library to signal either the invalidation or removing the members first, so
+ // let's verify the member change in case it does that first.
+
+ qDebug() << "membersChanged() after the call was answered - the remote probably hung up";
+
+ QVERIFY(groupMembersAdded.isEmpty());
+ QVERIFY(groupLocalPendingMembersAdded.isEmpty());
+ QVERIFY(groupRemotePendingMembersAdded.isEmpty());
+ QVERIFY(groupMembersRemoved.contains(otherContact) ||
+ groupMembersRemoved.contains(mChan->groupSelfContact())); // can be either, or both
+
+ // the invalidated handler will change state due to the fact that we might get 0-2 of these,
+ // but always exactly one invalidated()
+ }
+
+ qDebug() << "group members changed";
+ mChangedCurrent = groupMembersAdded;
+ mChangedLP = groupLocalPendingMembersAdded;
+ mChangedRP = groupRemotePendingMembersAdded;
+ mChangedRemoved = groupMembersRemoved;
+ mDetails = details;
+}
+
+void TestStreamedMediaChan::onTerminateChanInvalidated(Tp::DBusProxy *proxy,
+ const QString &errorName, const QString &errorMessage)
+{
+ qDebug() << "chan invalidated:" << errorName << "-" << errorMessage;
+ mTerminateState = TerminateStateTerminated;
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::onNewChannels(const Tp::ChannelDetailsList &channels)
+{
+ qDebug() << "new channels";
+ Q_FOREACH (const Tp::ChannelDetails &details, channels) {
+ QString channelType = details.properties.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
+ bool requested = details.properties.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")).toBool();
+ qDebug() << " channelType:" << channelType;
+ qDebug() << " requested :" << requested;
+
+ if (channelType == QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA) &&
+ !requested) {
+ mChan = StreamedMediaChannel::create(mConn->client(),
+ details.channel.path(), details.properties);
+ mLoop->exit(0);
+ }
+ }
+}
+
+void TestStreamedMediaChan::onLocalHoldStateChanged(Tp::LocalHoldState localHoldState,
+ Tp::LocalHoldStateReason localHoldStateReason)
+{
+ qDebug() << "local hold state changed:" << localHoldState << localHoldStateReason;
+ mLocalHoldStates.append(localHoldState);
+ mLocalHoldStateReasons.append(localHoldStateReason);
+ mLoop->exit(0);
+}
+
+void TestStreamedMediaChan::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("streamed-media-chan");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ EXAMPLE_TYPE_CALLABLE_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ "simulation-delay", 1,
+ NULL);
+ QCOMPARE(mConn->connect(Connection::FeatureSelfContact), true);
+}
+
+void TestStreamedMediaChan::init()
+{
+ initImpl();
+
+ mContacts.clear();
+ mRequestStreamsReturn.clear();
+ mChangedCurrent.clear();
+ mChangedLP.clear();
+ mChangedRP.clear();
+ mChangedRemoved.clear();
+ mStreamRemovedReturn.reset();
+ mSDCStreamReturn.reset();
+ mSDCDirectionReturn = (Tp::MediaStreamDirection) -1;
+ mSDCPendingReturn = (Tp::MediaStreamPendingSend) -1;
+ mSSCStateReturn = (Tp::MediaStreamState) -1;
+ mChangedLSS = (Tp::StreamedMediaStream::SendingState) -1;
+ mChangedRSS = (Tp::StreamedMediaStream::SendingState) -1;
+ mSSCStreamReturn.reset();
+ mLocalHoldStates.clear();
+ mLocalHoldStateReasons.clear();
+}
+
+void TestStreamedMediaChan::testOutgoingCall()
+{
+ mContacts = mConn->contacts(QStringList() << QLatin1String("alice"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ QCOMPARE(mChan->streams().size(), 0);
+ QCOMPARE(mChan->groupContacts().size(), 1);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->awaitingLocalAnswer(), false);
+ QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(groupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+
+ // RequestStreams with bad type must fail
+ QVERIFY(connect(mChan->requestStream(otherContact, (Tp::MediaStreamType) -1),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 2);
+ QCOMPARE(mRequestStreamsReturn.size(), 0);
+
+ // Request audio stream, and wait for:
+ // - the request to finish
+ // - the contact to appear on RP
+ // - the contact to accept the call
+ mOutgoingState = OutgoingStateInitial;
+ mOutgoingAudioDone = false;
+ mOutgoingGotRequestStreamsFinished = false;
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(groupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onOutgoingGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+
+ qDebug() << "requesting audio stream";
+
+ QVERIFY(connect(mChan->requestStreams(otherContact,
+ QList<Tp::MediaStreamType>() << Tp::MediaStreamTypeAudio),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectOutgoingRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(static_cast<int>(mOutgoingState), static_cast<int>(OutgoingStateDone));
+ QCOMPARE(mOutgoingAudioDone, true);
+
+ qDebug() << "requesting video stream";
+
+ // Request video stream
+ QVERIFY(connect(mChan->requestStream(otherContact, Tp::MediaStreamTypeVideo),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mRequestStreamsReturn.size(), 1);
+ StreamedMediaStreamPtr stream = mRequestStreamsReturn.first();
+ QCOMPARE(stream->contact(), otherContact);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeVideo);
+
+ // These checks can't work reliably, unless we add some complex backdoors to the test service,
+ // to only start changing state / direction when we explicitly tell it so (not automatically
+ // when we have requested the stream)
+ // QCOMPARE(stream->state(), Tp::MediaStreamStateDisconnected);
+ // QCOMPARE(stream->direction(), Tp::MediaStreamDirectionBidirectional);
+
+ QCOMPARE(mChan->streams().size(), 2);
+ QVERIFY(mChan->streams().contains(stream));
+
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeAudio).size(), 1);
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeVideo).size(), 1);
+
+ // test stream removal
+ stream = mChan->streamsForType(Tp::MediaStreamTypeAudio).first();
+ QVERIFY(stream);
+
+ qDebug() << "removing audio stream";
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(streamRemoved(const Tp::StreamedMediaStreamPtr &)),
+ SLOT(onStreamRemoved(const Tp::StreamedMediaStreamPtr &))));
+ QVERIFY(connect(mChan->removeStream(stream),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ if (mChan->streams().size() == 2) {
+ qDebug() << "re-entering mainloop to wait for stream removal being signaled";
+ // wait stream removed signal
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mStreamRemovedReturn, stream);
+ QCOMPARE(mChan->streams().size(), 1);
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeAudio).size(), 0);
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeVideo).size(), 1);
+
+ // test stream direction/state changed
+ stream = mChan->streamsForType(Tp::MediaStreamTypeVideo).first();
+ QVERIFY(stream);
+
+ qDebug() << "changing stream direction, currently" << stream->direction();
+ qDebug() << "state currently" << stream->state();
+
+ if (stream->state() != Tp::MediaStreamStateConnected) {
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(streamStateChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamState)),
+ SLOT(onStreamStateChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamState))));
+ } else {
+ // Pretend that we saw the SSC to Connected (although it might have happened even before the
+ // stream request finished, in which case we have no change of catching it, because we don't
+ // have the stream yet)
+ mSSCStreamReturn = stream;
+ mSSCStateReturn = Tp::MediaStreamStateConnected;
+ }
+
+ QCOMPARE(stream->localSendingRequested(), false);
+ QCOMPARE(stream->remoteSendingRequested(), false);
+ QCOMPARE(stream->sending(), true);
+ QCOMPARE(stream->receiving(), true);
+
+ /* request only receiving now */
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(streamDirectionChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamDirection,
+ Tp::MediaStreamPendingSend)),
+ SLOT(onStreamDirectionChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamDirection,
+ Tp::MediaStreamPendingSend))));
+ QVERIFY(connect(stream.data(),
+ SIGNAL(localSendingStateChanged(Tp::StreamedMediaStream::SendingState)),
+ SLOT(onLSSChanged(Tp::StreamedMediaStream::SendingState))));
+ QVERIFY(connect(stream.data(),
+ SIGNAL(remoteSendingStateChanged(Tp::StreamedMediaStream::SendingState)),
+ SLOT(onRSSChanged(Tp::StreamedMediaStream::SendingState))));
+ QVERIFY(connect(stream->requestDirection(Tp::MediaStreamDirectionReceive),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (!mSDCStreamReturn || !mSSCStreamReturn || (static_cast<int>(mChangedLSS) == -1)) {
+ qDebug() << "re-entering mainloop to wait for stream direction change and state change";
+ // wait direction and state changed signal
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ // If this fails, we also got a remote state change signal, although we shouldn't have
+ QCOMPARE(static_cast<int>(mChangedRSS), -1);
+ QCOMPARE(mSDCStreamReturn, stream);
+ QVERIFY(mSDCDirectionReturn & Tp::MediaStreamDirectionReceive);
+ QVERIFY(stream->direction() & Tp::MediaStreamDirectionReceive);
+ QCOMPARE(stream->pendingSend(), mSDCPendingReturn);
+ QCOMPARE(mSSCStreamReturn, stream);
+ QCOMPARE(mSSCStateReturn, Tp::MediaStreamStateConnected);
+
+ QCOMPARE(stream->sending(), false);
+ QCOMPARE(stream->receiving(), true);
+}
+
+void TestStreamedMediaChan::testOutgoingCallBusy()
+{
+ // This identifier contains the magic string (busy), which means the example
+ // will simulate rejection of the call as busy rather than accepting it.
+ mContacts = mConn->contacts(QStringList() << QLatin1String("alice (busy)"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ QCOMPARE(mChan->streams().size(), 0);
+ QCOMPARE(mChan->groupContacts().size(), 1);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->awaitingLocalAnswer(), false);
+ QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ // Request audio stream
+ QVERIFY(connect(mChan->requestStreams(otherContact,
+ QList<Tp::MediaStreamType>() << Tp::MediaStreamTypeAudio),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectBusyRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ if (mChan->isValid()) {
+ qDebug() << "waiting for the channel to become invalidated";
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(onChanInvalidated(Tp::DBusProxy *,
+ const QString &, const QString &))));
+ QCOMPARE(mLoop->exec(), 0);
+ } else {
+ qDebug() << "not waiting for the channel to become invalidated, it has been invalidated" <<
+ "already";
+ }
+
+ QCOMPARE(mChan->groupContacts().size(), 0);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->streams().size(), 0);
+}
+
+void TestStreamedMediaChan::testOutgoingCallNoAnswer()
+{
+ // This identifier contains the magic string (no answer), which means the example
+ // will never answer.
+ mContacts = mConn->contacts(QStringList() << QLatin1String("alice (no answer)"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ QCOMPARE(mChan->streams().size(), 0);
+ QCOMPARE(mChan->groupContacts().size(), 1);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->awaitingLocalAnswer(), false);
+ QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ // Request audio stream
+ QVERIFY(connect(mChan->requestStreams(otherContact,
+ QList<Tp::MediaStreamType>() << Tp::MediaStreamTypeAudio),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ /* After the initial flurry of D-Bus messages, alice still hasn't answered */
+ processDBusQueue(mConn->client().data());
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(groupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+ // wait the contact to appear on RP
+ while (mChan->groupRemotePendingContacts().size() == 0) {
+ mLoop->processEvents();
+ }
+ QVERIFY(mChan->groupRemotePendingContacts().contains(otherContact));
+ QCOMPARE(mChan->awaitingRemoteAnswer(), true);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 1);
+
+ /* assume we're never going to get an answer, and hang up */
+ mChan->requestClose();
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(onChanInvalidated(Tp::DBusProxy *,
+ const QString &, const QString &))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->groupContacts().size(), 0);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->streams().size(), 0);
+}
+
+void TestStreamedMediaChan::testOutgoingCallTerminate()
+{
+ // This identifier contains the magic string (terminate), which means the example
+ // will simulate answering the call but then terminating it.
+ mContacts = mConn->contacts(QStringList() << QLatin1String("alice (terminate)"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ QCOMPARE(mChan->streams().size(), 0);
+ QCOMPARE(mChan->groupContacts().size(), 1);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->awaitingLocalAnswer(), false);
+ QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ // Request audio stream, and verify that following doing so, we get events for:
+ // [0-2].5) the stream request finishing (sadly this can happen before, or between, any of the
+ // following) - is this a bug?
+ // 1) the remote appearing on the RP contacts -> should be awaitingRemoteAnswer()
+ // 2) the remote answering the call -> should not be awaitingRemoteAnswer(), should have us and
+ // them as the current members
+ // 3) the channel being invalidated (due to the remote having terminated the call) -> exits the
+ // mainloop
+ //
+ // Previously, this test used to spin the mainloop until each of the events had seemingly
+ // happened, only checking for the events between the iterations. This is race-prone however,
+ // as multiple events can happen in one mainloop iteration if the test executes slowly compared
+ // with the simulated network events from the service (eg. in valgrind or under high system
+ // load).
+
+ mTerminateState = TerminateStateInitial;
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(groupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &)),
+ SLOT(onTerminateGroupMembersChanged(
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Contacts &,
+ const Tp::Channel::GroupMemberChangeDetails &))));
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(invalidated(Tp::DBusProxy *,
+ const QString &, const QString &)),
+ SLOT(onTerminateChanInvalidated(Tp::DBusProxy *,
+ const QString &, const QString &))));
+
+ qDebug() << "calling, hope somebody answers and doesn't immediately hang up!";
+
+ QVERIFY(connect(mChan->requestStreams(otherContact,
+ QList<Tp::MediaStreamType>() << Tp::MediaStreamTypeAudio),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectTerminateRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(static_cast<int>(mTerminateState), static_cast<int>(TerminateStateTerminated));
+
+ qDebug() << "oh crap, nobody wants to talk to me";
+}
+
+
+void TestStreamedMediaChan::testIncomingCall()
+{
+ mConn->client()->lowlevel()->setSelfPresence(QLatin1String("away"), QLatin1String("preparing for a test"));
+
+ Client::ConnectionInterfaceRequestsInterface *connRequestsInterface =
+ mConn->client()->optionalInterface<Client::ConnectionInterfaceRequestsInterface>();
+
+ QVERIFY(connect(connRequestsInterface,
+ SIGNAL(NewChannels(const Tp::ChannelDetailsList&)),
+ SLOT(onNewChannels(const Tp::ChannelDetailsList&))));
+ mConn->client()->lowlevel()->setSelfPresence(QLatin1String("available"), QLatin1String("call me?"));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(mChan);
+ QCOMPARE(mChan->streams().size(), 0);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ QCOMPARE(mChan->streams().size(), 1);
+ QCOMPARE(mChan->groupContacts().size(), 1);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 1);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->awaitingLocalAnswer(), true);
+ QCOMPARE(mChan->awaitingRemoteAnswer(), false);
+ QVERIFY(mChan->groupLocalPendingContacts().contains(mConn->client()->selfContact()));
+
+ ContactPtr otherContact = *mChan->groupContacts().begin();
+ QCOMPARE(otherContact, mChan->initiatorContact());
+
+ QVERIFY(connect(mChan->acceptCall(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mChan->groupContacts().size(), 2);
+ QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
+ QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
+ QCOMPARE(mChan->awaitingLocalAnswer(), false);
+ QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
+
+ QCOMPARE(mChan->streams().size(), 1);
+ StreamedMediaStreamPtr stream = mChan->streams().first();
+ QCOMPARE(stream->channel(), mChan);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeAudio);
+
+ qDebug() << "requesting a stream with a bad type";
+
+ // RequestStreams with bad type must fail
+ QVERIFY(connect(mChan->requestStream(otherContact, (Tp::MediaStreamType) -1),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 2);
+ QCOMPARE(mRequestStreamsReturn.size(), 0);
+
+ qDebug() << "requesting a video stream";
+
+ // Request video stream
+ QVERIFY(connect(mChan->requestStream(otherContact, Tp::MediaStreamTypeVideo),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mRequestStreamsReturn.size(), 1);
+ stream = mRequestStreamsReturn.first();
+ QCOMPARE(stream->contact(), otherContact);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeVideo);
+
+ // These checks can't work reliably, unless we add some complex backdoors to the test service,
+ // to only start changing state / direction when we explicitly tell it so (not automatically
+ // when we have requested the stream)
+ // QCOMPARE(stream->state(), Tp::MediaStreamStateDisconnected);
+ // QCOMPARE(stream->direction(), Tp::MediaStreamDirectionBidirectional);
+
+ QCOMPARE(mChan->streams().size(), 2);
+ QVERIFY(mChan->streams().contains(stream));
+
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeAudio).size(), 1);
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeVideo).size(), 1);
+
+ // test stream removal
+ stream = mChan->streamsForType(Tp::MediaStreamTypeAudio).first();
+ QVERIFY(stream);
+
+ qDebug() << "removing the audio stream";
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(streamRemoved(const Tp::StreamedMediaStreamPtr &)),
+ SLOT(onStreamRemoved(const Tp::StreamedMediaStreamPtr &))));
+ QVERIFY(connect(mChan->removeStreams(StreamedMediaStreams() << stream),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ if (mChan->streams().size() == 2) {
+ // wait stream removed signal
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mStreamRemovedReturn, stream);
+ QCOMPARE(mChan->streams().size(), 1);
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeAudio).size(), 0);
+ QCOMPARE(mChan->streamsForType(Tp::MediaStreamTypeVideo).size(), 1);
+
+ // test stream direction/state changed
+ stream = mChan->streamsForType(Tp::MediaStreamTypeVideo).first();
+ QVERIFY(stream);
+
+ qDebug() << "requesting direction (false, true) - currently" << stream->direction();
+ qDebug() << "current stream state" << stream->state();
+
+ if (stream->state() != Tp::MediaStreamStateConnected) {
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(streamStateChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamState)),
+ SLOT(onStreamStateChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamState))));
+ } else {
+ // Pretend that we saw the SSC to Connected (although it might have happened even before the
+ // stream request finished, in which case we have no change of catching it, because we don't
+ // have the stream yet)
+ mSSCStreamReturn = stream;
+ mSSCStateReturn = Tp::MediaStreamStateConnected;
+ }
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(streamDirectionChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamDirection,
+ Tp::MediaStreamPendingSend)),
+ SLOT(onStreamDirectionChanged(const Tp::StreamedMediaStreamPtr &,
+ Tp::MediaStreamDirection,
+ Tp::MediaStreamPendingSend))));
+ QVERIFY(connect(stream.data(),
+ SIGNAL(localSendingStateChanged(Tp::StreamedMediaStream::SendingState)),
+ SLOT(onLSSChanged(Tp::StreamedMediaStream::SendingState))));
+ QVERIFY(connect(stream.data(),
+ SIGNAL(remoteSendingStateChanged(Tp::StreamedMediaStream::SendingState)),
+ SLOT(onRSSChanged(Tp::StreamedMediaStream::SendingState))));
+ QVERIFY(connect(stream->requestDirection(false, true),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (!mSDCStreamReturn || !mSSCStreamReturn || (static_cast<int>(mChangedLSS) == -1)) {
+ // wait direction and state changed signal
+ qDebug() << "re-entering mainloop to wait for stream direction change and state change";
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ // If this fails, we also got a remote state change signal, although we shouldn't have
+ QCOMPARE(static_cast<int>(mChangedRSS), -1);
+ QCOMPARE(mSDCStreamReturn, stream);
+ QVERIFY(mSDCDirectionReturn & Tp::MediaStreamDirectionReceive);
+ QVERIFY(stream->direction() & Tp::MediaStreamDirectionReceive);
+ QCOMPARE(stream->pendingSend(), mSDCPendingReturn);
+ QCOMPARE(mSSCStreamReturn, stream);
+ QCOMPARE(mSSCStateReturn, Tp::MediaStreamStateConnected);
+}
+
+void TestStreamedMediaChan::testHold()
+{
+ mContacts = mConn->contacts(QStringList() << QLatin1String("bob"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureLocalHoldState),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureLocalHoldState));
+
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateUnheld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonNone));
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(localHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason)),
+ SLOT(onLocalHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason))));
+ // Request hold
+ QVERIFY(connect(mChan->requestHold(true),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mLocalHoldStates.size() != 2) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mLocalHoldStates.first(), static_cast<uint>(LocalHoldStatePendingHold));
+ QCOMPARE(mLocalHoldStateReasons.first(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(mLocalHoldStates.last(), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(mLocalHoldStateReasons.last(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonRequested));
+
+ mLocalHoldStates.clear();
+ mLocalHoldStateReasons.clear();
+
+ // Request unhold
+ QVERIFY(connect(mChan->requestHold(false),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mLocalHoldStates.size() != 2) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mLocalHoldStates.first(), static_cast<uint>(LocalHoldStatePendingUnhold));
+ QCOMPARE(mLocalHoldStateReasons.first(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(mLocalHoldStates.last(), static_cast<uint>(LocalHoldStateUnheld));
+ QCOMPARE(mLocalHoldStateReasons.last(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateUnheld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonRequested));
+}
+
+void TestStreamedMediaChan::testHoldNoUnhold()
+{
+ mContacts = mConn->contacts(QStringList() << QLatin1String("bob (no unhold)"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureLocalHoldState),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureLocalHoldState));
+
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateUnheld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonNone));
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(localHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason)),
+ SLOT(onLocalHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason))));
+ // Request hold
+ QPointer<PendingOperation> holdOp = mChan->requestHold(true);
+ while (mLocalHoldStates.size() != 2 || (holdOp && !holdOp->isFinished())) {
+ mLoop->processEvents();
+ }
+ QCOMPARE(!holdOp || holdOp->isValid(), true);
+ QCOMPARE(mLocalHoldStates.first(), static_cast<uint>(LocalHoldStatePendingHold));
+ QCOMPARE(mLocalHoldStateReasons.first(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(mLocalHoldStates.last(), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(mLocalHoldStateReasons.last(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonRequested));
+
+ mLocalHoldStates.clear();
+ mLocalHoldStateReasons.clear();
+
+ qDebug() << "requesting failing unhold";
+
+ // Request unhold (fail)
+ QVERIFY(connect(mChan->requestHold(false),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 1);
+ QCOMPARE(mLocalHoldStates.size(), 0);
+ QCOMPARE(mLocalHoldStateReasons.size(), 0);
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonRequested));
+}
+
+void TestStreamedMediaChan::testHoldInabilityUnhold()
+{
+ mContacts = mConn->contacts(
+ QStringList() << QLatin1String("bob (inability to unhold)"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureLocalHoldState),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureLocalHoldState));
+
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateUnheld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonNone));
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(localHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason)),
+ SLOT(onLocalHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason))));
+ // Request hold
+ QVERIFY(connect(mChan->requestHold(true),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (mLocalHoldStates.size() != 2) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(mLocalHoldStates.first(), static_cast<uint>(LocalHoldStatePendingHold));
+ QCOMPARE(mLocalHoldStateReasons.first(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(mLocalHoldStates.last(), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(mLocalHoldStateReasons.last(), static_cast<uint>(LocalHoldStateReasonRequested));
+ QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(LocalHoldStateHeld));
+ QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(LocalHoldStateReasonRequested));
+
+ mLocalHoldStates.clear();
+ mLocalHoldStateReasons.clear();
+
+ // Request unhold (fail - back to hold)
+ QVERIFY(connect(mChan->requestHold(false),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ while (mLocalHoldStates.size() != 3) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+
+ LocalHoldState state = static_cast<LocalHoldState>(mLocalHoldStates.dequeue());
+ LocalHoldStateReason stateReason = static_cast<LocalHoldStateReason>(mLocalHoldStateReasons.dequeue());
+ QCOMPARE(state, LocalHoldStatePendingUnhold);
+ QCOMPARE(stateReason, LocalHoldStateReasonRequested);
+
+ state = static_cast<LocalHoldState>(mLocalHoldStates.dequeue());
+ stateReason = static_cast<LocalHoldStateReason>(mLocalHoldStateReasons.dequeue());
+ QCOMPARE(state, LocalHoldStatePendingHold);
+ QCOMPARE(stateReason, LocalHoldStateReasonRequested);
+
+ state = static_cast<LocalHoldState>(mLocalHoldStates.dequeue());
+ stateReason = static_cast<LocalHoldStateReason>(mLocalHoldStateReasons.dequeue());
+ QCOMPARE(state, LocalHoldStateHeld);
+ QCOMPARE(stateReason, LocalHoldStateReasonRequested);
+
+ QCOMPARE(mChan->localHoldState(), LocalHoldStateHeld);
+ QCOMPARE(mChan->localHoldStateReason(), LocalHoldStateReasonRequested);
+}
+
+void TestStreamedMediaChan::testDTMF()
+{
+ mContacts = mConn->contacts(QStringList() << QLatin1String("john"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ // Request audio stream
+ QVERIFY(connect(mChan->requestStreams(otherContact,
+ QList<Tp::MediaStreamType>() << Tp::MediaStreamTypeAudio),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mRequestStreamsReturn.size(), 1);
+ StreamedMediaStreamPtr stream = mRequestStreamsReturn.first();
+ QCOMPARE(stream->contact(), otherContact);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeAudio);
+
+ QCOMPARE(mChan->streams().size(), 1);
+ QVERIFY(mChan->streams().contains(stream));
+
+ // start dtmf
+ QVERIFY(connect(stream->startDTMFTone(DTMFEventDigit0),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // stop dtmf
+ QVERIFY(connect(stream->stopDTMFTone(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // stop dtmf again (should succeed)
+ QVERIFY(connect(stream->stopDTMFTone(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // Request video stream
+ QVERIFY(connect(mChan->requestStream(otherContact, Tp::MediaStreamTypeVideo),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mRequestStreamsReturn.size(), 1);
+ stream = mRequestStreamsReturn.first();
+ QCOMPARE(stream->contact(), otherContact);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeVideo);
+
+ QCOMPARE(mChan->streams().size(), 2);
+ QVERIFY(mChan->streams().contains(stream));
+
+ // start dtmf (must fail)
+ QVERIFY(connect(stream->startDTMFTone(DTMFEventDigit0),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 1);
+
+ // stop dtmf (must fail)
+ QVERIFY(connect(stream->stopDTMFTone(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 1);
+}
+
+void TestStreamedMediaChan::testDTMFNoContinuousTone()
+{
+ mContacts = mConn->contacts(
+ QStringList() << QLatin1String("john (no continuous tone)"));
+ QCOMPARE(mContacts.size(), 1);
+ ContactPtr otherContact = mContacts.first();
+ QVERIFY(otherContact);
+
+ mChan = StreamedMediaChannelPtr::qObjectCast(
+ mConn->createChannel(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, otherContact));
+ QVERIFY(mChan);
+
+ QVERIFY(connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(StreamedMediaChannel::FeatureStreams));
+
+ // Request audio stream
+ QVERIFY(connect(mChan->requestStreams(otherContact,
+ QList<Tp::MediaStreamType>() << Tp::MediaStreamTypeAudio),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectRequestStreamsFinished(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mRequestStreamsReturn.size(), 1);
+ StreamedMediaStreamPtr stream = mRequestStreamsReturn.first();
+ QCOMPARE(stream->contact(), otherContact);
+ QCOMPARE(stream->type(), Tp::MediaStreamTypeAudio);
+
+ QCOMPARE(mChan->streams().size(), 1);
+ QVERIFY(mChan->streams().contains(stream));
+
+ // start dtmf
+ QVERIFY(connect(stream->startDTMFTone(DTMFEventDigit0),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ // stop dtmf (must fail)
+ QVERIFY(connect(stream->stopDTMFTone(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
+ QCOMPARE(mLoop->exec(), 1);
+}
+
+void TestStreamedMediaChan::cleanup()
+{
+ mChan.reset();
+ mContacts.clear();
+ mRequestStreamsReturn.clear();
+ mStreamRemovedReturn.reset();
+ mChangedCurrent.clear();
+ mChangedLP.clear();
+ mChangedRP.clear();
+ mChangedRemoved.clear();
+ mDetails = Channel::GroupMemberChangeDetails();
+ mSDCStreamReturn.reset();
+ mSSCStreamReturn.reset();
+ cleanupImpl();
+}
+
+void TestStreamedMediaChan::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestStreamedMediaChan)
+#include "_gen/streamed-media-chan.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/text-chan.cpp b/qt4/tests/dbus/text-chan.cpp
new file mode 100644
index 000000000..a223e91c6
--- /dev/null
+++ b/qt4/tests/dbus/text-chan.cpp
@@ -0,0 +1,557 @@
+#include <tests/lib/test.h>
+
+#include <tests/lib/glib-helpers/test-conn-helper.h>
+
+#include <tests/lib/glib/contacts-conn.h>
+#include <tests/lib/glib/echo/chan.h>
+#include <tests/lib/glib/echo2/chan.h>
+
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/Message>
+#include <TelepathyQt4/PendingReady>
+#include <TelepathyQt4/ReceivedMessage>
+#include <TelepathyQt4/TextChannel>
+
+#include <telepathy-glib/debug.h>
+
+using namespace Tp;
+
+struct SentMessageDetails
+{
+ SentMessageDetails(const Message &message,
+ const Tp::MessageSendingFlags flags,
+ const QString &token)
+ : message(message), flags(flags), token(token) { }
+ Message message;
+ Tp::MessageSendingFlags flags;
+ QString token;
+};
+
+class TestTextChan : public Test
+{
+ Q_OBJECT
+
+public:
+ TestTextChan(QObject *parent = 0)
+ : Test(parent),
+ mConn(0), mContactRepo(0),
+ mTextChanService(0), mMessagesChanService(0),
+ mGotChatStateChanged(false),
+ mChatStateChangedState((ChannelChatState) -1)
+ { }
+
+protected Q_SLOTS:
+ void onMessageReceived(const Tp::ReceivedMessage &);
+ void onMessageRemoved(const Tp::ReceivedMessage &);
+ void onMessageSent(const Tp::Message &,
+ Tp::MessageSendingFlags, const QString &);
+ void onChatStateChanged(const Tp::ContactPtr &contact,
+ Tp::ChannelChatState state);
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testMessages();
+ void testLegacyText();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ void commonTest(bool withMessages);
+ void sendText(const char *text);
+
+ TestConnHelper *mConn;
+ TpHandleRepoIface *mContactRepo;
+ ContactPtr mContact;
+ TextChannelPtr mChan;
+ ExampleEchoChannel *mTextChanService;
+ QString mTextChanPath;
+ ExampleEcho2Channel *mMessagesChanService;
+ QString mMessagesChanPath;
+ QList<SentMessageDetails> sent;
+ QList<ReceivedMessage> received;
+ QList<ReceivedMessage> removed;
+ bool mGotChatStateChanged;
+ ContactPtr mChatStateChangedContact;
+ ChannelChatState mChatStateChangedState;
+};
+
+void TestTextChan::onMessageReceived(const ReceivedMessage &message)
+{
+ qDebug() << "message received";
+ received << message;
+ mLoop->exit(0);
+}
+
+void TestTextChan::onMessageRemoved(const ReceivedMessage &message)
+{
+ qDebug() << "message removed";
+ removed << message;
+}
+
+void TestTextChan::onMessageSent(const Tp::Message &message,
+ Tp::MessageSendingFlags flags, const QString &token)
+{
+ qDebug() << "message sent";
+ sent << SentMessageDetails(message, flags, token);
+}
+
+void TestTextChan::onChatStateChanged(const Tp::ContactPtr &contact,
+ Tp::ChannelChatState state)
+{
+ mGotChatStateChanged = true;
+ mChatStateChangedContact = contact;
+ mChatStateChangedState = state;
+}
+
+void TestTextChan::sendText(const char *text)
+{
+ qDebug() << "sending message:" << text;
+ QVERIFY(connect(mChan->send(QLatin1String(text),
+ Tp::ChannelTextMessageTypeNormal),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ qDebug() << "message send mainloop finished";
+}
+
+void TestTextChan::initTestCase()
+{
+ initTestCaseImpl();
+
+ g_type_init();
+ g_set_prgname("text-chan");
+ tp_debug_set_flags("all");
+ dbus_g_bus_get(DBUS_BUS_STARTER, 0);
+
+ mConn = new TestConnHelper(this,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ "account", "me@example.com",
+ "protocol", "example",
+ NULL);
+ QCOMPARE(mConn->connect(), true);
+
+ mContactRepo = tp_base_connection_get_handles(TP_BASE_CONNECTION(mConn->service()),
+ TP_HANDLE_TYPE_CONTACT);
+ guint handle = tp_handle_ensure(mContactRepo, "someone@localhost", 0, 0);
+
+ mContact = mConn->contacts(UIntList() << handle).first();
+ QVERIFY(mContact);
+
+ // create a Channel by magic, rather than doing D-Bus round-trips for it
+ mTextChanPath = mConn->objectPath() + QLatin1String("/TextChannel");
+ QByteArray chanPath(mTextChanPath.toAscii());
+ mTextChanService = EXAMPLE_ECHO_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ mMessagesChanPath = mConn->objectPath() + QLatin1String("/MessagesChannel");
+ chanPath = mMessagesChanPath.toAscii();
+ mMessagesChanService = EXAMPLE_ECHO_2_CHANNEL(g_object_new(
+ EXAMPLE_TYPE_ECHO_2_CHANNEL,
+ "connection", mConn->service(),
+ "object-path", chanPath.data(),
+ "handle", handle,
+ NULL));
+
+ tp_handle_unref(mContactRepo, handle);
+}
+
+void TestTextChan::init()
+{
+ initImpl();
+
+ mChan.reset();
+ mGotChatStateChanged = false;
+ mChatStateChangedState = (ChannelChatState) -1;
+}
+
+void TestTextChan::commonTest(bool withMessages)
+{
+ Q_ASSERT(mChan);
+ ChannelPtr asChannel = ChannelPtr(dynamic_cast<Channel*>(mChan.data()));
+
+ QVERIFY(connect(asChannel->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(asChannel->isReady());
+ QVERIFY(mChan->isReady());
+
+ // hasChatStateInterface requires FeatureCore only
+ if (withMessages) {
+ QVERIFY(mChan->hasChatStateInterface());
+ } else {
+ QVERIFY(!mChan->hasChatStateInterface());
+ }
+
+ QVERIFY(!mChan->isReady(TextChannel::FeatureChatState));
+ QCOMPARE(mChan->chatState(mContact), ChannelChatStateInactive);
+ QCOMPARE(mChan->chatState(ContactPtr()), ChannelChatStateInactive);
+
+ QVERIFY(connect(asChannel->becomeReady(TextChannel::FeatureChatState),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(mChan->isReady(TextChannel::FeatureChatState));
+
+ if (withMessages) {
+ QVERIFY(mChan->hasChatStateInterface());
+ } else {
+ QVERIFY(!mChan->hasChatStateInterface());
+ }
+
+ QCOMPARE(mChan->chatState(mContact), ChannelChatStateInactive);
+ QCOMPARE(mChan->chatState(ContactPtr()), ChannelChatStateInactive);
+ QCOMPARE(mChan->chatState(mChan->groupSelfContact()), ChannelChatStateInactive);
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(chatStateChanged(Tp::ContactPtr,Tp::ChannelChatState)),
+ SLOT(onChatStateChanged(Tp::ContactPtr,Tp::ChannelChatState))));
+
+ if (withMessages) {
+ QVERIFY(connect(mChan->requestChatState(ChannelChatStateActive),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ while (!mGotChatStateChanged) {
+ mLoop->processEvents();
+ }
+ QCOMPARE(mChatStateChangedContact, mChan->groupSelfContact());
+ QCOMPARE(mChatStateChangedState, mChan->chatState(mChan->groupSelfContact()));
+ QCOMPARE(mChatStateChangedState, ChannelChatStateActive);
+ } else {
+ QVERIFY(connect(mChan->requestChatState(ChannelChatStateActive),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectFailure(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+ QCOMPARE(mLastError, TP_QT4_ERROR_NOT_IMPLEMENTED);
+ QVERIFY(!mLastErrorMessage.isEmpty());
+ }
+
+ QVERIFY(!mChan->canInviteContacts());
+
+ Features features = Features() << TextChannel::FeatureMessageQueue;
+ QVERIFY(!mChan->isReady(features));
+ // Implementation detail: in legacy text channels, capabilities arrive
+ // early, so don't assert about that
+ QVERIFY(!mChan->isReady(features));
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(messageReceived(const Tp::ReceivedMessage &)),
+ SLOT(onMessageReceived(const Tp::ReceivedMessage &))));
+ QCOMPARE(received.size(), 0);
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(pendingMessageRemoved(const Tp::ReceivedMessage &)),
+ SLOT(onMessageRemoved(const Tp::ReceivedMessage &))));
+ QCOMPARE(removed.size(), 0);
+
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(messageSent(const Tp::Message &,
+ Tp::MessageSendingFlags,
+ const QString &)),
+ SLOT(onMessageSent(const Tp::Message &,
+ Tp::MessageSendingFlags,
+ const QString &))));
+ QCOMPARE(sent.size(), 0);
+
+ sendText("One");
+
+ // Flush the D-Bus queue to make sure we've got the Sent signal the service will send, even if
+ // we are scheduled to execute between the time it calls return_from_send and emit_sent
+ processDBusQueue(mChan.data());
+
+ qDebug() << "making the Sent signal ready";
+
+ // Make the Sent signal become ready
+ features = Features() << TextChannel::FeatureMessageSentSignal;
+ QVERIFY(connect(mChan->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(asChannel->isReady());
+ QVERIFY(mChan->isReady());
+ features = Features() << TextChannel::FeatureMessageSentSignal;
+ QVERIFY(mChan->isReady(features));
+ features = Features() << TextChannel::FeatureMessageQueue;
+ QVERIFY(!mChan->isReady(features));
+
+ qDebug() << "the Sent signal is ready";
+
+ sendText("Two");
+
+ // Flush the D-Bus queue to make sure we've got the Sent signal the service will send, even if
+ // we are scheduled to execute between the time it calls return_from_send and emit_sent
+ processDBusQueue(mChan.data());
+
+ // We should have received "Two", but not "One"
+ QCOMPARE(sent.size(), 1);
+ QCOMPARE(static_cast<uint>(sent.at(0).flags), 0U);
+ QCOMPARE(sent.at(0).token, QLatin1String(""));
+
+ Message m(sent.at(0).message);
+ QCOMPARE(static_cast<uint>(m.messageType()),
+ static_cast<uint>(Tp::ChannelTextMessageTypeNormal));
+ QVERIFY(!m.isTruncated());
+ QVERIFY(!m.hasNonTextContent());
+ QCOMPARE(m.messageToken(), QLatin1String(""));
+ QVERIFY(!m.isSpecificToDBusInterface());
+ QCOMPARE(m.dbusInterface(), QLatin1String(""));
+ QCOMPARE(m.size(), 2);
+ QCOMPARE(m.header().value(QLatin1String("message-type")).variant().toUInt(),
+ 0U);
+ QCOMPARE(m.part(1).value(QLatin1String("content-type")).variant().toString(),
+ QLatin1String("text/plain"));
+ QCOMPARE(m.text(), QLatin1String("Two"));
+
+ // Make capabilities become ready
+ features = Features() << TextChannel::FeatureMessageCapabilities;
+ QVERIFY(connect(mChan->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(asChannel->isReady());
+ QVERIFY(mChan->isReady());
+ features = Features() << TextChannel::FeatureMessageCapabilities;
+ QVERIFY(mChan->isReady(features));
+ features = Features() << TextChannel::FeatureMessageQueue;
+ QVERIFY(!mChan->isReady(features));
+
+ if (withMessages) {
+ QCOMPARE(mChan->supportedContentTypes(), QStringList() << QLatin1String("*/*"));
+ QCOMPARE(static_cast<uint>(mChan->messagePartSupport()),
+ static_cast<uint>(Tp::MessagePartSupportFlagOneAttachment |
+ Tp::MessagePartSupportFlagMultipleAttachments));
+ QCOMPARE(mChan->deliveryReportingSupport(), DeliveryReportingSupportFlagReceiveFailures);
+ } else {
+ QCOMPARE(mChan->supportedContentTypes(), QStringList() << QLatin1String("text/plain"));
+ QCOMPARE(static_cast<uint>(mChan->messagePartSupport()), 0U);
+ QCOMPARE(static_cast<uint>(mChan->deliveryReportingSupport()), 0U);
+ }
+
+ // Make the message queue become ready too
+ QCOMPARE(received.size(), 0);
+ QCOMPARE(mChan->messageQueue().size(), 0);
+ features = Features() << TextChannel::FeatureMessageQueue;
+ QVERIFY(connect(mChan->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation *)),
+ SLOT(expectSuccessfulCall(Tp::PendingOperation *))));
+ QCOMPARE(mLoop->exec(), 0);
+
+ QVERIFY(asChannel->isReady());
+ QVERIFY(mChan->isReady());
+ features = Features() << TextChannel::FeatureMessageQueue << TextChannel::FeatureMessageCapabilities;
+ QVERIFY(mChan->isReady(features));
+
+ // Assert that both our sent messages were echoed by the remote contact
+ while (received.size() != 2) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(received.size(), 2);
+ QCOMPARE(mChan->messageQueue().size(), 2);
+ QVERIFY(mChan->messageQueue().at(0) == received.at(0));
+ QVERIFY(mChan->messageQueue().at(1) == received.at(1));
+ QVERIFY(received.at(0) != received.at(1));
+
+ ReceivedMessage r(received.at(0));
+ QVERIFY(r == received.at(0));
+ QCOMPARE(static_cast<uint>(r.messageType()),
+ static_cast<uint>(Tp::ChannelTextMessageTypeNormal));
+ QVERIFY(!r.isTruncated());
+ QVERIFY(!r.hasNonTextContent());
+ if (withMessages) {
+ QCOMPARE(r.messageToken(), QLatin1String("0000"));
+ QCOMPARE(r.supersededToken(), QLatin1String("1234"));
+ } else {
+ QCOMPARE(r.messageToken(), QLatin1String(""));
+ QCOMPARE(r.supersededToken(), QString());
+ }
+ QVERIFY(!r.isSpecificToDBusInterface());
+ QCOMPARE(r.dbusInterface(), QLatin1String(""));
+ QCOMPARE(r.size(), 2);
+ QCOMPARE(r.header().value(QLatin1String("message-type")).variant().toUInt(),
+ 0U);
+ QCOMPARE(r.part(1).value(QLatin1String("content-type")).variant().toString(),
+ QLatin1String("text/plain"));
+ QCOMPARE(r.sender()->id(), QLatin1String("someone@localhost"));
+ QCOMPARE(r.senderNickname(), QLatin1String("someone@localhost"));
+ if (withMessages) {
+ QVERIFY(r.isScrollback());
+ } else {
+ QVERIFY(!r.isScrollback());
+ }
+ QVERIFY(!r.isRescued());
+ QVERIFY(!r.isDeliveryReport());
+
+ // one "echo" implementation echoes the message literally, the other edits
+ // it slightly
+ if (withMessages) {
+ QCOMPARE(r.text(), QLatin1String("One"));
+ } else {
+ QCOMPARE(r.text(), QLatin1String("You said: One"));
+ }
+
+ r = received.at(1);
+ QCOMPARE(static_cast<uint>(r.messageType()),
+ static_cast<uint>(Tp::ChannelTextMessageTypeNormal));
+ QVERIFY(!r.isTruncated());
+ QVERIFY(!r.hasNonTextContent());
+ if (withMessages) {
+ QCOMPARE(r.messageToken(), QLatin1String("0000"));
+ QCOMPARE(r.supersededToken(), QLatin1String("1234"));
+ } else {
+ QCOMPARE(r.messageToken(), QLatin1String(""));
+ QCOMPARE(r.supersededToken(), QString());
+ }
+ QVERIFY(!r.isSpecificToDBusInterface());
+ QCOMPARE(r.dbusInterface(), QLatin1String(""));
+ QCOMPARE(r.size(), 2);
+ QCOMPARE(r.header().value(QLatin1String("message-type")).variant().toUInt(),
+ 0U);
+ QCOMPARE(r.part(1).value(QLatin1String("content-type")).variant().toString(),
+ QLatin1String("text/plain"));
+ QCOMPARE(r.sender()->id(), QLatin1String("someone@localhost"));
+ QVERIFY(!r.isScrollback());
+ QVERIFY(!r.isRescued());
+
+ if (withMessages) {
+ QCOMPARE(r.text(), QLatin1String("Two"));
+ } else {
+ QCOMPARE(r.text(), QLatin1String("You said: Two"));
+ }
+
+ // go behind the TextChannel's back to acknowledge the first message:
+ // this emulates another client doing so
+ QVERIFY(connect(mChan.data(),
+ SIGNAL(pendingMessageRemoved(Tp::ReceivedMessage)),
+ mLoop,
+ SLOT(quit())));
+ mChan->acknowledge(QList< ReceivedMessage >() << received.at(0));
+ QCOMPARE(mLoop->exec(), 0);
+ QVERIFY(disconnect(mChan.data(),
+ SIGNAL(pendingMessageRemoved(Tp::ReceivedMessage)),
+ mLoop,
+ SLOT(quit())));
+
+ QCOMPARE(mChan->messageQueue().size(), 1);
+ QVERIFY(mChan->messageQueue().at(0) == received.at(1));
+ QCOMPARE(removed.size(), 1);
+ QVERIFY(removed.at(0) == received.at(0));
+
+ // In the Messages case this will ack one message, successfully. In the
+ // Text case it will fail to ack two messages, fall back to one call
+ // per message, and fail one while succeeding with the other.
+ mChan->acknowledge(mChan->messageQueue());
+
+ if (withMessages) {
+ sendText("Three (fail)");
+
+ // Flush the D-Bus queue to make sure we've got the Sent signal the service will send, even if
+ // we are scheduled to execute between the time it calls return_from_send and emit_sent
+ processDBusQueue(mChan.data());
+
+ // Assert that both our sent messages were echoed by the remote contact
+ while (received.size() != 3) {
+ QCOMPARE(mLoop->exec(), 0);
+ }
+ QCOMPARE(received.size(), 3);
+ QCOMPARE(mChan->messageQueue().size(), 1);
+ QVERIFY(mChan->messageQueue().at(0) == received.at(2));
+
+ r = received.at(2);
+ QVERIFY(r == received.at(2));
+ QCOMPARE(r.messageType(), Tp::ChannelTextMessageTypeDeliveryReport);
+ QVERIFY(!r.isTruncated());
+ QVERIFY(r.hasNonTextContent());
+ QCOMPARE(r.messageToken(), QLatin1String(""));
+ QVERIFY(!r.isSpecificToDBusInterface());
+ QCOMPARE(r.dbusInterface(), QLatin1String(""));
+ QCOMPARE(r.size(), 1);
+ QCOMPARE(r.header().value(QLatin1String("message-type")).variant().toUInt(),
+ static_cast<uint>(Tp::ChannelTextMessageTypeDeliveryReport));
+ QCOMPARE(r.sender()->id(), QLatin1String("someone@localhost"));
+ QCOMPARE(r.senderNickname(), QLatin1String("someone@localhost"));
+ QVERIFY(!r.isScrollback());
+ QVERIFY(!r.isRescued());
+ QCOMPARE(r.supersededToken(), QString());
+ QVERIFY(r.isDeliveryReport());
+ QVERIFY(r.deliveryDetails().isValid());
+ QVERIFY(r.deliveryDetails().hasOriginalToken());
+ QCOMPARE(r.deliveryDetails().originalToken(), QLatin1String("1111"));
+ QCOMPARE(r.deliveryDetails().status(), Tp::DeliveryStatusPermanentlyFailed);
+ QVERIFY(r.deliveryDetails().isError());
+ QCOMPARE(r.deliveryDetails().error(), Tp::ChannelTextSendErrorPermissionDenied);
+ QVERIFY(r.deliveryDetails().hasDebugMessage());
+ QCOMPARE(r.deliveryDetails().debugMessage(), QLatin1String("You asked for it"));
+ QCOMPARE(r.deliveryDetails().dbusError(), TP_QT4_ERROR_PERMISSION_DENIED);
+ QVERIFY(r.deliveryDetails().hasEchoedMessage());
+ QCOMPARE(r.deliveryDetails().echoedMessage().text(), QLatin1String("Three (fail)"));
+
+ mChan->acknowledge(QList<ReceivedMessage>() << received.at(2));
+ }
+
+ // wait for everything to settle down
+ while (tp_text_mixin_has_pending_messages(
+ G_OBJECT(mTextChanService), 0)
+ || tp_message_mixin_has_pending_messages(
+ G_OBJECT(mMessagesChanService), 0)) {
+ QTest::qWait(1);
+ }
+
+ QVERIFY(!tp_text_mixin_has_pending_messages(
+ G_OBJECT(mTextChanService), 0));
+ QVERIFY(!tp_message_mixin_has_pending_messages(
+ G_OBJECT(mMessagesChanService), 0));
+}
+
+void TestTextChan::testMessages()
+{
+ mChan = TextChannel::create(mConn->client(), mMessagesChanPath, QVariantMap());
+
+ commonTest(true);
+}
+
+void TestTextChan::testLegacyText()
+{
+ mChan = TextChannel::create(mConn->client(), mTextChanPath, QVariantMap());
+
+ commonTest(false);
+}
+
+void TestTextChan::cleanup()
+{
+ received.clear();
+ removed.clear();
+ sent.clear();
+
+ cleanupImpl();
+}
+
+void TestTextChan::cleanupTestCase()
+{
+ QCOMPARE(mConn->disconnect(), true);
+ delete mConn;
+
+ if (mTextChanService != 0) {
+ g_object_unref(mTextChanService);
+ mTextChanService = 0;
+ }
+
+ if (mMessagesChanService != 0) {
+ g_object_unref(mMessagesChanService);
+ mMessagesChanService = 0;
+ }
+
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestTextChan)
+#include "_gen/text-chan.cpp.moc.hpp"
diff --git a/qt4/tests/dbus/types.cpp b/qt4/tests/dbus/types.cpp
new file mode 100644
index 000000000..864ad3277
--- /dev/null
+++ b/qt4/tests/dbus/types.cpp
@@ -0,0 +1,141 @@
+#include <tests/lib/test.h>
+
+#include <TelepathyQt4/Channel>
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/types-internal.h>
+
+using namespace Tp;
+
+/* We need a interface returning a QVariantMap property where we will insert the various
+ * combinations we are testing, so let's use Channel.Tube for that as we already have the
+ * autogenerated interface for it */
+class TubeAdaptor : public QDBusAbstractAdaptor
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Channel.Interface.Tube")
+ Q_CLASSINFO("D-Bus Introspection", ""
+" <interface name=\"org.freedesktop.Telepathy.Channel.Interface.Tube\" >\n"
+" <property name=\"Parameters\" type=\"a{sv}\" access=\"read\" />\n"
+" </interface>\n"
+ "")
+
+ Q_PROPERTY(QVariantMap Parameters READ Parameters)
+
+public:
+ TubeAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) {}
+ ~TubeAdaptor() {}
+
+public: // Properties
+ inline QVariantMap Parameters() const
+ {
+ QVariantMap ret;
+
+ SUSocketAddress su;
+ su.address = QLatin1String("1.1.1.1");
+ su.port = 1111;
+ SocketAddressIPv4 saIPv4;
+ saIPv4.address = QLatin1String("2.2.2.2");
+ saIPv4.port = 2222;
+ SocketAddressIPv6 saIPv6;
+ saIPv6.address = QLatin1String("3.3.3.3");
+ saIPv6.port = 3333;
+ ret.insert(QLatin1String("SU"), qVariantFromValue(su));
+ ret.insert(QLatin1String("saIPv4"), qVariantFromValue(saIPv4));
+ ret.insert(QLatin1String("saIPv6"), qVariantFromValue(saIPv6));
+
+ return ret;
+ }
+};
+
+class TestTypes : public Test
+{
+ Q_OBJECT
+
+public:
+ TestTypes(QObject *parent = 0)
+ : Test(parent)
+ { }
+
+protected Q_SLOTS:
+
+private Q_SLOTS:
+ void initTestCase();
+ void init();
+
+ void testParameters();
+
+ void cleanup();
+ void cleanupTestCase();
+
+private:
+ QVariantMap mParameters;
+};
+
+void TestTypes::initTestCase()
+{
+ initTestCaseImpl();
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+
+ // setup adaptor
+ QString tubeBusName = QLatin1String("org.freedesktop.Telepathy.Test.Types");
+ QString tubePath = QLatin1String("/org/freedesktop/Telepathy/Test/Types");
+
+ QObject *adaptorObject = new QObject(this);
+ (void) new TubeAdaptor(adaptorObject);
+ QVERIFY(bus.registerService(tubeBusName));
+ QVERIFY(bus.registerObject(tubePath, adaptorObject));
+
+ Client::ChannelInterfaceTubeInterface *tubeIface = new Client::ChannelInterfaceTubeInterface(
+ bus, tubeBusName, tubePath, this);
+ QVERIFY(waitForProperty(tubeIface->requestPropertyParameters(), &mParameters));
+}
+
+void TestTypes::init()
+{
+ initImpl();
+}
+
+void TestTypes::testParameters()
+{
+ SUSocketAddress su;
+ SocketAddressIPv4 saIPv4;
+ SocketAddressIPv6 saIPv6;
+
+ // SUSocketAddress should properly convert to SUSocketAddress itself and
+ // SocketAddressIPv4/IPv6
+ su = qdbus_cast<SUSocketAddress>(mParameters.value(QLatin1String("SU")));
+ QCOMPARE(su.address, QLatin1String("1.1.1.1"));
+ QCOMPARE(su.port, static_cast<uint>(1111));
+
+ saIPv4 = qdbus_cast<SocketAddressIPv4>(mParameters.value(QLatin1String("SU")));
+ QCOMPARE(saIPv4.address, QLatin1String("1.1.1.1"));
+ QCOMPARE(saIPv4.port, static_cast<ushort>(1111));
+
+ saIPv6 = qdbus_cast<SocketAddressIPv6>(mParameters.value(QLatin1String("SU")));
+ QCOMPARE(saIPv6.address, QLatin1String("1.1.1.1"));
+ QCOMPARE(saIPv6.port, static_cast<ushort>(1111));
+
+ // SocketAddressIPv4->SocketAddressIPv4
+ saIPv4 = qdbus_cast<SocketAddressIPv4>(mParameters.value(QLatin1String("saIPv4")));
+ QCOMPARE(saIPv4.address, QLatin1String("2.2.2.2"));
+ QCOMPARE(saIPv4.port, static_cast<ushort>(2222));
+
+ // SocketAddressIPv6->SocketAddressIPv6
+ saIPv6 = qdbus_cast<SocketAddressIPv6>(mParameters.value(QLatin1String("saIPv6")));
+ QCOMPARE(saIPv6.address, QLatin1String("3.3.3.3"));
+ QCOMPARE(saIPv6.port, static_cast<ushort>(3333));
+}
+
+void TestTypes::cleanup()
+{
+ cleanupImpl();
+}
+
+void TestTypes::cleanupTestCase()
+{
+ cleanupTestCaseImpl();
+}
+
+QTEST_MAIN(TestTypes)
+#include "_gen/types.cpp.moc.hpp"
diff --git a/qt4/tests/features.cpp b/qt4/tests/features.cpp
new file mode 100644
index 000000000..c8c605d9e
--- /dev/null
+++ b/qt4/tests/features.cpp
@@ -0,0 +1,73 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Feature>
+#include <TelepathyQt4/Types>
+
+using namespace Tp;
+
+namespace {
+
+QList<Feature> reverse(const QList<Feature> &list)
+{
+ QList<Feature> ret(list);
+ for (int k = 0; k < (list.size() / 2); k++) {
+ ret.swap(k, list.size() - (1 + k));
+ }
+ return ret;
+}
+
+};
+
+class TestFeatures : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestFeatures(QObject *parent = 0);
+
+private Q_SLOTS:
+ void testFeaturesHash();
+};
+
+TestFeatures::TestFeatures(QObject *parent)
+ : QObject(parent)
+{
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+}
+
+void TestFeatures::testFeaturesHash()
+{
+ QList<Feature> fs1;
+ QList<Feature> fs2;
+ for (int i = 0; i < 100; ++i) {
+ fs1 << Feature(QString::number(i), i);
+ fs2 << Feature(QString::number(i), i);
+ }
+
+ QCOMPARE(qHash(fs1.toSet()), qHash(fs2.toSet()));
+
+ fs2.clear();
+ for (int i = 0; i < 5; ++i) {
+ for (int j = 0; j < 100; ++j) {
+ fs2 << Feature(QString::number(j), j);
+ }
+ }
+
+ QCOMPARE(qHash(fs1.toSet()), qHash(fs2.toSet()));
+
+ fs1 = reverse(fs1);
+ QCOMPARE(qHash(fs1.toSet()), qHash(fs2.toSet()));
+
+ fs2 = reverse(fs2);
+ QCOMPARE(qHash(fs1.toSet()), qHash(fs2.toSet()));
+
+ fs2 << Feature(QLatin1String("100"), 100);
+ QVERIFY(qHash(fs1.toSet()) != qHash(fs2.toSet()));
+}
+
+QTEST_MAIN(TestFeatures)
+
+#include "_gen/features.cpp.moc.hpp"
diff --git a/qt4/tests/file-transfer-channel-creation-properties.cpp b/qt4/tests/file-transfer-channel-creation-properties.cpp
new file mode 100644
index 000000000..8f40eb459
--- /dev/null
+++ b/qt4/tests/file-transfer-channel-creation-properties.cpp
@@ -0,0 +1,145 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/FileTransferChannelCreationProperties>
+
+using namespace Tp;
+
+class TestFileTransferCreationProperties : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testFileTransferCreationPropertiesDefaultConstructor();
+ void testFileTransferCreationPropertiesDefaultByMandatoryProperties();
+ void testFileTransferCreationPropertiesDefaultByPath();
+ void testFileTransferCreationPropertiesDefaultByPathFail();
+};
+
+void TestFileTransferCreationProperties::testFileTransferCreationPropertiesDefaultConstructor()
+{
+ FileTransferChannelCreationProperties ftprops;
+ QVERIFY(!ftprops.isValid());
+ QVERIFY(ftprops.suggestedFileName().isEmpty());
+ QVERIFY(ftprops.contentType().isEmpty());
+ QCOMPARE(ftprops.size(), (qulonglong)0);
+
+ QVERIFY(!ftprops.hasContentHash());
+ QCOMPARE(ftprops.contentHashType(), FileHashTypeNone);
+ QVERIFY(ftprops.contentHash().isEmpty());
+ ftprops.setContentHash(FileHashTypeMD5, QLatin1String("ffffffffffffffff"));
+ QVERIFY(!ftprops.isValid());
+ QVERIFY(!ftprops.hasContentHash());
+ QCOMPARE(ftprops.contentHashType(), FileHashTypeNone);
+ QVERIFY(ftprops.contentHash().isEmpty());
+
+ QVERIFY(!ftprops.hasDescription());
+ QVERIFY(ftprops.description().isEmpty());
+ ftprops.setDescription(QLatin1String("description"));
+ QVERIFY(!ftprops.isValid());
+ QVERIFY(!ftprops.hasDescription());
+ QVERIFY(ftprops.description().isEmpty());
+
+ QVERIFY(!ftprops.hasLastModificationTime());
+ QVERIFY(!ftprops.lastModificationTime().isValid());
+ ftprops.setLastModificationTime(QDateTime::currentDateTime());
+ QVERIFY(!ftprops.isValid());
+ QVERIFY(!ftprops.hasLastModificationTime());
+ QVERIFY(!ftprops.lastModificationTime().isValid());
+
+ QVERIFY(!ftprops.hasUri());
+ QVERIFY(ftprops.uri().isEmpty());
+ ftprops.setUri(QLatin1String("file:///path/filename"));
+ QVERIFY(!ftprops.isValid());
+ QVERIFY(!ftprops.hasUri());
+ QVERIFY(ftprops.uri().isEmpty());
+}
+
+void TestFileTransferCreationProperties::testFileTransferCreationPropertiesDefaultByMandatoryProperties()
+{
+ FileTransferChannelCreationProperties ftprops(QLatin1String("suggestedFileName"),
+ QLatin1String("application/octet-stream"), (qulonglong)10000);
+ QCOMPARE(ftprops.isValid(), true);
+ QCOMPARE(ftprops.suggestedFileName(), QLatin1String("suggestedFileName"));
+ QCOMPARE(ftprops.contentType(), QLatin1String("application/octet-stream"));
+ QCOMPARE(ftprops.size(), (qulonglong)10000);
+
+ QVERIFY(!ftprops.hasContentHash());
+ QCOMPARE(ftprops.contentHashType(), FileHashTypeNone);
+ QVERIFY(ftprops.contentHash().isEmpty());
+ ftprops.setContentHash(FileHashTypeMD5, QLatin1String("ffffffffffffffff"));
+ QVERIFY(ftprops.isValid());
+ QVERIFY(ftprops.hasContentHash());
+ QCOMPARE(ftprops.contentHashType(), FileHashTypeMD5);
+ QCOMPARE(ftprops.contentHash(), QLatin1String("ffffffffffffffff"));
+
+ QVERIFY(!ftprops.hasDescription());
+ QVERIFY(ftprops.description().isEmpty());
+ ftprops.setDescription(QLatin1String("description"));
+ QVERIFY(ftprops.isValid());
+ QVERIFY(ftprops.hasDescription());
+ QCOMPARE(ftprops.description(), QLatin1String("description"));
+
+ QVERIFY(!ftprops.hasLastModificationTime());
+ QVERIFY(!ftprops.lastModificationTime().isValid());
+ QDateTime now = QDateTime::currentDateTime();
+ ftprops.setLastModificationTime(now);
+ QVERIFY(ftprops.hasLastModificationTime());
+ QCOMPARE(ftprops.lastModificationTime(), now);
+
+ QVERIFY(!ftprops.hasUri());
+ QVERIFY(ftprops.uri().isEmpty());
+ ftprops.setUri(QLatin1String("file:///path/filename"));
+ QVERIFY(ftprops.isValid());
+ QVERIFY(ftprops.hasUri());
+ QCOMPARE(ftprops.uri(), QLatin1String("file:///path/filename"));
+}
+
+void TestFileTransferCreationProperties::testFileTransferCreationPropertiesDefaultByPath()
+{
+ // Test constructor by local file path with existing file
+ QString filePath = QDir::currentPath() + QLatin1String("/test-file-transfer-channel-creation-properties");
+ QFileInfo fileInfo(filePath);
+ QUrl fileUri = QUrl::fromLocalFile(filePath);
+
+ FileTransferChannelCreationProperties ftprops(filePath,
+ QLatin1String("application/octet-stream"));
+ QVERIFY(ftprops.isValid());
+ QCOMPARE(ftprops.suggestedFileName(), fileInfo.fileName());
+ QCOMPARE(ftprops.contentType(), QLatin1String("application/octet-stream"));
+ QCOMPARE(ftprops.size(), (quint64)fileInfo.size());
+
+ QVERIFY(!ftprops.hasContentHash());
+ QCOMPARE(ftprops.contentHashType(), FileHashTypeNone);
+ QVERIFY(ftprops.contentHash().isEmpty());
+ QVERIFY(!ftprops.hasDescription());
+ QVERIFY(ftprops.description().isEmpty());
+ QVERIFY(ftprops.hasLastModificationTime());
+ QCOMPARE(ftprops.lastModificationTime(), fileInfo.lastModified());
+ QVERIFY(ftprops.hasUri());
+ QCOMPARE(ftprops.uri(), fileUri.toString());
+}
+
+void TestFileTransferCreationProperties::testFileTransferCreationPropertiesDefaultByPathFail()
+{
+ // Test constructor by local file path with non-existing file
+ FileTransferChannelCreationProperties ftprops(QLatin1String("/non-existent-path/non-existent-filename"),
+ QLatin1String("application/octet-stream"));
+ QVERIFY(!ftprops.isValid());
+ QVERIFY(ftprops.suggestedFileName().isEmpty());
+ QVERIFY(ftprops.contentType().isEmpty());
+ QCOMPARE(ftprops.size(), (qulonglong)0);
+
+ QVERIFY(!ftprops.hasContentHash());
+ QCOMPARE(ftprops.contentHashType(), FileHashTypeNone);
+ QVERIFY(ftprops.contentHash().isEmpty());
+ QVERIFY(!ftprops.hasDescription());
+ QVERIFY(ftprops.description().isEmpty());
+ QVERIFY(!ftprops.hasLastModificationTime());
+ QVERIFY(!ftprops.lastModificationTime().isValid());
+ QVERIFY(!ftprops.hasUri());
+ QVERIFY(ftprops.uri().isEmpty());
+}
+
+QTEST_MAIN(TestFileTransferCreationProperties)
+
+#include "_gen/file-transfer-channel-creation-properties.cpp.moc.hpp"
diff --git a/qt4/tests/key-file.cpp b/qt4/tests/key-file.cpp
new file mode 100644
index 000000000..077e1a091
--- /dev/null
+++ b/qt4/tests/key-file.cpp
@@ -0,0 +1,111 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/KeyFile>
+
+using namespace Tp;
+
+class TestKeyFile : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testKeyFile();
+};
+
+void TestKeyFile::testKeyFile()
+{
+ QString top_srcdir = QString::fromLocal8Bit(::getenv("abs_top_srcdir"));
+ if (!top_srcdir.isEmpty()) {
+ QDir::setCurrent(top_srcdir + QLatin1String("/tests"));
+ }
+
+ KeyFile defaultKeyFile;
+ QCOMPARE(defaultKeyFile.status(), KeyFile::None);
+
+ KeyFile notFoundkeyFile(QLatin1String("test-key-file-not-found.ini"));
+ QCOMPARE(notFoundkeyFile.status(), KeyFile::NotFoundError);
+
+ KeyFile formatErrorkeyFile(QLatin1String("test-key-file-format-error.ini"));
+ QCOMPARE(formatErrorkeyFile.status(), KeyFile::FormatError);
+
+ KeyFile keyFile(QLatin1String("test-key-file.ini"));
+ QCOMPARE(keyFile.status(), KeyFile::NoError);
+
+ QCOMPARE(keyFile.allGroups(),
+ QStringList() << QString() <<
+ QLatin1String("test group 1") <<
+ QLatin1String("test group 2"));
+
+ QStringList allKeys = keyFile.allKeys();
+ allKeys.sort();
+ QCOMPARE(allKeys,
+ QStringList() <<
+ QLatin1String("a") <<
+ QLatin1String("b") <<
+ QLatin1String("c") <<
+ QLatin1String("d") <<
+ QLatin1String("e"));
+
+ keyFile.setGroup(QLatin1String("test group 1"));
+ QCOMPARE(keyFile.contains(QLatin1String("f")), false);
+ QCOMPARE(keyFile.value(QLatin1String("c")).length(), 5);
+
+ keyFile.setGroup(QLatin1String("test group 2"));
+ QCOMPARE(keyFile.contains(QLatin1String("e")), true);
+ QCOMPARE(keyFile.value(QLatin1String("e")), QString(QLatin1String("space")));
+
+ keyFile.setFileName(QLatin1String("telepathy/managers/test-manager-file.manager"));
+ QCOMPARE(keyFile.status(), KeyFile::NoError);
+
+ keyFile.setGroup(QLatin1String("Protocol somewhat-pathological"));
+ QCOMPARE(keyFile.value(QLatin1String("param-foo")), QString(QLatin1String("s required")));
+ QCOMPARE(keyFile.value(QLatin1String("default-foo")), QString(QLatin1String("hello world")));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-semicolons")), QString(QLatin1String("s secret")));
+ QCOMPARE(keyFile.value(QLatin1String("default-semicolons")), QString(QLatin1String("list;of;misc;")));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-list")), QString(QLatin1String("list;of;misc;")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-list")),
+ QStringList() << QLatin1String("list") << QLatin1String("of") << QLatin1String("misc"));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-unterminated-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-unterminated-list")), QString(QLatin1String("list;of;misc")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-unterminated-list")),
+ QStringList() << QLatin1String("list") << QLatin1String("of") << QLatin1String("misc"));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-spaces-in-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-spaces-in-list")), QString(QLatin1String("list; of; misc ;")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-spaces-in-list")),
+ QStringList() << QLatin1String("list") << QLatin1String(" of") << QLatin1String(" misc "));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-escaped-semicolon-in-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-escaped-semicolon-in-list")), QString(QLatin1String("list;of;misc;")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-escaped-semicolon-in-list")),
+ QStringList() << QLatin1String("list;of") << QLatin1String("misc"));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-doubly-escaped-semicolon-in-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-doubly-escaped-semicolon-in-list")), QString(QLatin1String("list\\;of;misc;")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-doubly-escaped-semicolon-in-list")),
+ QStringList() << QLatin1String("list\\") << QLatin1String("of") << QLatin1String("misc"));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-triply-escaped-semicolon-in-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-triply-escaped-semicolon-in-list")), QString(QLatin1String("list\\;of;misc;")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-triply-escaped-semicolon-in-list")),
+ QStringList() << QLatin1String("list\\;of") << QLatin1String("misc"));
+
+ QCOMPARE(keyFile.value(QLatin1String("param-empty-list")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-empty-list")), QString(QLatin1String("")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-empty-list")), QStringList());
+
+ QCOMPARE(keyFile.value(QLatin1String("param-list-of-empty-string")), QString(QLatin1String("as")));
+ QCOMPARE(keyFile.value(QLatin1String("default-list-of-empty-string")), QString(QLatin1String(";")));
+ QCOMPARE(keyFile.valueAsStringList(QLatin1String("default-list-of-empty-string")), QStringList() << QString());
+
+ QCOMPARE(keyFile.value(QLatin1String("param-escaped-semicolon")), QString(QLatin1String("s")));
+ QCOMPARE(keyFile.value(QLatin1String("default-escaped-semicolon")), QString(QLatin1String("foo;bar")));
+}
+
+QTEST_MAIN(TestKeyFile)
+
+#include "_gen/key-file.cpp.moc.hpp"
diff --git a/qt4/tests/lib/CMakeLists.txt b/qt4/tests/lib/CMakeLists.txt
new file mode 100644
index 000000000..ac4768911
--- /dev/null
+++ b/qt4/tests/lib/CMakeLists.txt
@@ -0,0 +1,12 @@
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR})
+
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen")
+tpqt4_generate_moc_i(test.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/test.h.moc.hpp)
+add_library(tp-qt4-tests test.cpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/test.h.moc.hpp)
+target_link_libraries(tp-qt4-tests ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} telepathy-qt4)
+
+if(ENABLE_TP_GLIB_TESTS)
+ add_subdirectory(glib)
+ add_subdirectory(glib-helpers)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib-helpers/CMakeLists.txt b/qt4/tests/lib/glib-helpers/CMakeLists.txt
new file mode 100644
index 000000000..9bac50328
--- /dev/null
+++ b/qt4/tests/lib/glib-helpers/CMakeLists.txt
@@ -0,0 +1,22 @@
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${TELEPATHY_GLIB_INCLUDE_DIR}
+ ${GLIB2_INCLUDE_DIR}
+ ${GOBJECT_INCLUDE_DIR}
+ ${DBUS_INCLUDE_DIR})
+
+add_definitions(-DQT_NO_KEYWORDS)
+
+if(ENABLE_TP_GLIB_TESTS)
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen")
+ tpqt4_generate_moc_i(test-conn-helper.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/test-conn-helper.moc.hpp)
+ add_library(tp-qt4-tests-glib-helpers test-conn-helper.cpp ${CMAKE_CURRENT_BINARY_DIR}/_gen/test-conn-helper.moc.hpp)
+ target_link_libraries(tp-qt4-tests-glib-helpers
+ ${TELEPATHY_GLIB_LIBRARIES}
+ ${GOBJECT_LIBRARIES}
+ ${GLIB2_LIBRARIES}
+ ${DBUS_GLIB_LIBRARIES}
+ ${QT_QTCORE_LIBRARY}
+ ${QT_QTDBUS_LIBRARY}
+ telepathy-qt4)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib-helpers/test-conn-helper.cpp b/qt4/tests/lib/glib-helpers/test-conn-helper.cpp
new file mode 100644
index 000000000..c9951733e
--- /dev/null
+++ b/qt4/tests/lib/glib-helpers/test-conn-helper.cpp
@@ -0,0 +1,415 @@
+#include "tests/lib/glib-helpers/test-conn-helper.h"
+#include "tests/lib/glib-helpers/_gen/test-conn-helper.moc.hpp"
+
+#define TP_QT4_ENABLE_LOWLEVEL_API
+
+#include <TelepathyQt4/ChannelFactory>
+#include <TelepathyQt4/Connection>
+#include <TelepathyQt4/ConnectionLowlevel>
+#include <TelepathyQt4/ContactFactory>
+#include <TelepathyQt4/ContactManager>
+#include <TelepathyQt4/PendingChannel>
+#include <TelepathyQt4/PendingContacts>
+#include <TelepathyQt4/PendingReady>
+
+TestConnHelper::TestConnHelper(Test *parent,
+ GType gType, const QString &account, const QString &protocol)
+ : QObject(parent)
+{
+ Tp::ChannelFactoryPtr channelFactory = Tp::ChannelFactory::create(QDBusConnection::sessionBus());
+ Tp::ContactFactoryPtr contactFactory = Tp::ContactFactory::create();
+
+ init(parent, channelFactory, contactFactory,
+ gType,
+ "account", account.toLatin1().constData(),
+ "protocol", protocol.toLatin1().constData(),
+ NULL);
+}
+
+TestConnHelper::TestConnHelper(Test *parent,
+ GType gType, const char *firstPropertyName, ...)
+ : QObject(parent)
+{
+ Tp::ChannelFactoryPtr channelFactory = Tp::ChannelFactory::create(QDBusConnection::sessionBus());
+ Tp::ContactFactoryPtr contactFactory = Tp::ContactFactory::create();
+
+ va_list varArgs;
+ va_start(varArgs, firstPropertyName);
+ init(parent, channelFactory, contactFactory,
+ gType, firstPropertyName, varArgs);
+ va_end(varArgs);
+}
+
+TestConnHelper::TestConnHelper(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const QString &account, const QString &protocol)
+ : QObject(parent)
+{
+ init(parent, channelFactory, contactFactory,
+ gType,
+ "account", account.toLatin1().constData(),
+ "protocol", protocol.toLatin1().constData(),
+ NULL);
+}
+
+TestConnHelper::TestConnHelper(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const char *firstPropertyName, ...)
+ : QObject(parent)
+{
+ va_list varArgs;
+ va_start(varArgs, firstPropertyName);
+ init(parent, channelFactory, contactFactory,
+ gType, firstPropertyName, varArgs);
+ va_end(varArgs);
+}
+
+TestConnHelper::~TestConnHelper()
+{
+ disconnect();
+
+ if (mService != 0) {
+ g_object_unref(mService);
+ mService = 0;
+ }
+}
+
+void TestConnHelper::init(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const char *firstPropertyName, ...)
+{
+ va_list varArgs;
+ va_start(varArgs, firstPropertyName);
+ init(parent, channelFactory, contactFactory,
+ gType, firstPropertyName, varArgs);
+ va_end(varArgs);
+}
+
+void TestConnHelper::init(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const char *firstPropertyName, va_list varArgs)
+{
+ mParent = parent;
+ mLoop = parent->mLoop;
+
+ mService = g_object_new_valist(gType, firstPropertyName, varArgs);
+ QVERIFY(mService != 0);
+
+ gchar *connBusName;
+ gchar *connPath;
+ GError *error = 0;
+ QVERIFY(tp_base_connection_register(TP_BASE_CONNECTION(mService),
+ "testcm", &connBusName, &connPath, &error));
+ QVERIFY(error == 0);
+ QVERIFY(connBusName != 0);
+ QVERIFY(connPath != 0);
+
+ mClient = Tp::Connection::create(QLatin1String(connBusName), QLatin1String(connPath),
+ channelFactory, contactFactory);
+ QCOMPARE(mClient->isReady(), false);
+
+ g_free(connBusName);
+ g_free(connPath);
+}
+
+QString TestConnHelper::objectPath() const
+{
+ return mClient->objectPath();
+}
+
+bool TestConnHelper::isValid() const
+{
+ return mClient->isValid();
+}
+
+bool TestConnHelper::isReady(const Tp::Features &features) const
+{
+ return mClient->isReady(features);
+}
+
+bool TestConnHelper::enableFeatures(const Tp::Features &features)
+{
+ mLoop->processEvents();
+
+ QObject::connect(mClient->becomeReady(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ mParent,
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*)));
+ return ((mLoop->exec() == 0) && mClient->isReady(features));
+}
+
+bool TestConnHelper::connect(const Tp::Features &features)
+{
+ mLoop->processEvents();
+
+ QObject::connect(mClient->lowlevel()->requestConnect(features),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ mParent,
+ SLOT(expectSuccessfulCall(Tp::PendingOperation*)));
+ return ((mLoop->exec() == 0) &&
+ (mClient->status() == Tp::ConnectionStatusConnected) && mClient->isReady(features));
+}
+
+bool TestConnHelper::disconnect()
+{
+ if (!mClient->isValid()) {
+ return false;
+ }
+
+ mLoop->processEvents();
+
+ QObject::connect(mClient.data(),
+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
+ SLOT(expectConnInvalidated()));
+ tp_base_connection_change_status(TP_BASE_CONNECTION(mService),
+ TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED);
+ return ((mLoop->exec() == 0) &&
+ !mClient->isValid() && (mClient->status() == Tp::ConnectionStatusDisconnected));
+}
+
+QList<Tp::ContactPtr> TestConnHelper::contacts(const QStringList &ids,
+ const Tp::Features &features)
+{
+ mLoop->processEvents();
+
+ QList<Tp::ContactPtr> ret;
+ Tp::PendingContacts *pc = mClient->contactManager()->contactsForIdentifiers(ids, features);
+ mContactFeatures = features;
+ QObject::connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectContactsForIdentifiersFinished(Tp::PendingOperation*)));
+ if (mLoop->exec() == 0) {
+ ret = mContacts;
+ }
+ mContactFeatures.clear();
+ mContacts.clear();
+ return ret;
+}
+
+QList<Tp::ContactPtr> TestConnHelper::contacts(const Tp::UIntList &handles,
+ const Tp::Features &features)
+{
+ mLoop->processEvents();
+
+ QList<Tp::ContactPtr> ret;
+ Tp::PendingContacts *pc = mClient->contactManager()->contactsForHandles(handles, features);
+ mContactFeatures = features;
+ QObject::connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectContactsForHandlesFinished(Tp::PendingOperation*)));
+ if (mLoop->exec() == 0) {
+ ret = mContacts;
+ }
+ mContactFeatures.clear();
+ mContacts.clear();
+ return ret;
+}
+
+QList<Tp::ContactPtr> TestConnHelper::upgradeContacts(const QList<Tp::ContactPtr> &contacts,
+ const Tp::Features &features)
+{
+ mLoop->processEvents();
+
+ QList<Tp::ContactPtr> ret;
+ Tp::PendingContacts *pc = mClient->contactManager()->upgradeContacts(contacts, features);
+ mContactFeatures = features;
+ QObject::connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectUpgradeContactsFinished(Tp::PendingOperation*)));
+ if (mLoop->exec() == 0) {
+ ret = mContacts;
+ }
+ mContactFeatures.clear();
+ mContacts.clear();
+ return ret;
+}
+
+Tp::ChannelPtr TestConnHelper::createChannel(const QVariantMap &request)
+{
+ mLoop->processEvents();
+
+ Tp::ChannelPtr ret;
+ Tp::PendingChannel *pc = mClient->lowlevel()->createChannel(request);
+ QObject::connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectCreateChannelFinished(Tp::PendingOperation*)));
+ if (mLoop->exec() == 0) {
+ ret = mChannel;
+ }
+ mChannel.reset();
+ return ret;
+}
+
+Tp::ChannelPtr TestConnHelper::createChannel(const QString &channelType, const Tp::ContactPtr &target)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ channelType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ target->handle()[0]);
+ return createChannel(request);
+}
+
+Tp::ChannelPtr TestConnHelper::createChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, uint targetHandle)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ channelType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) targetHandleType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ targetHandle);
+ return createChannel(request);
+}
+
+Tp::ChannelPtr TestConnHelper::createChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, const QString &targetID)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ channelType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) targetHandleType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ targetID);
+ return ensureChannel(request);
+}
+
+Tp::ChannelPtr TestConnHelper::ensureChannel(const QVariantMap &request)
+{
+ mLoop->processEvents();
+
+ Tp::ChannelPtr ret;
+ Tp::PendingChannel *pc = mClient->lowlevel()->ensureChannel(request);
+ QObject::connect(pc,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectEnsureChannelFinished(Tp::PendingOperation*)));
+ if (mLoop->exec() == 0) {
+ ret = mChannel;
+ }
+ mChannel.reset();
+ return ret;
+}
+
+Tp::ChannelPtr TestConnHelper::ensureChannel(const QString &channelType, const Tp::ContactPtr &target)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ channelType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) Tp::HandleTypeContact);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ target->handle()[0]);
+ return ensureChannel(request);
+}
+
+Tp::ChannelPtr TestConnHelper::ensureChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, uint targetHandle)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ channelType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) targetHandleType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"),
+ targetHandle);
+ return ensureChannel(request);
+}
+
+Tp::ChannelPtr TestConnHelper::ensureChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, const QString &targetID)
+{
+ QVariantMap request;
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ channelType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ (uint) targetHandleType);
+ request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"),
+ targetID);
+ return ensureChannel(request);
+}
+
+void TestConnHelper::expectConnInvalidated()
+{
+ mLoop->exit(0);
+}
+
+void TestConnHelper::expectContactsForIdentifiersFinished(Tp::PendingOperation *op)
+{
+ Tp::PendingContacts *pc = qobject_cast<Tp::PendingContacts *>(op);
+ QCOMPARE(pc->isForHandles(), false);
+ QCOMPARE(pc->isForIdentifiers(), true);
+ QCOMPARE(pc->isUpgrade(), false);
+
+ expectPendingContactsFinished(pc);
+}
+
+void TestConnHelper::expectContactsForHandlesFinished(Tp::PendingOperation *op)
+{
+ Tp::PendingContacts *pc = qobject_cast<Tp::PendingContacts *>(op);
+ QCOMPARE(pc->isForHandles(), true);
+ QCOMPARE(pc->isForIdentifiers(), false);
+ QCOMPARE(pc->isUpgrade(), false);
+
+ expectPendingContactsFinished(pc);
+}
+
+void TestConnHelper::expectUpgradeContactsFinished(Tp::PendingOperation *op)
+{
+ Tp::PendingContacts *pc = qobject_cast<Tp::PendingContacts *>(op);
+ QCOMPARE(pc->isUpgrade(), true);
+
+ expectPendingContactsFinished(pc);
+}
+
+void TestConnHelper::expectPendingContactsFinished(Tp::PendingContacts *pc)
+{
+ QCOMPARE(pc->manager(), mClient->contactManager());
+
+ if (pc->isError()) {
+ qWarning().nospace() << pc->errorName() << ": " << pc->errorMessage();
+ mContacts.clear();
+ mLoop->exit(1);
+ } else {
+ mContacts = pc->contacts();
+ Q_FOREACH (const Tp::ContactPtr &contact, mContacts) {
+ QVERIFY(contact->requestedFeatures().contains(mContactFeatures));
+ }
+ mLoop->exit(0);
+ }
+}
+
+void TestConnHelper::expectCreateChannelFinished(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning().nospace() << op->errorName() << ": " << op->errorMessage();
+ mChannel.reset();
+ mLoop->exit(1);
+ } else {
+ Tp::PendingChannel *pc = qobject_cast<Tp::PendingChannel *>(op);
+ QCOMPARE(pc->yours(), true);
+ mChannel = pc->channel();
+ mLoop->exit(0);
+ }
+}
+
+void TestConnHelper::expectEnsureChannelFinished(Tp::PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning().nospace() << op->errorName() << ": " << op->errorMessage();
+ mChannel.reset();
+ mLoop->exit(1);
+ } else {
+ Tp::PendingChannel *pc = qobject_cast<Tp::PendingChannel *>(op);
+ QCOMPARE(pc->yours(), false);
+ mChannel = pc->channel();
+ mLoop->exit(0);
+ }
+}
diff --git a/qt4/tests/lib/glib-helpers/test-conn-helper.h b/qt4/tests/lib/glib-helpers/test-conn-helper.h
new file mode 100644
index 000000000..d1c2f4d25
--- /dev/null
+++ b/qt4/tests/lib/glib-helpers/test-conn-helper.h
@@ -0,0 +1,102 @@
+#ifndef _TelepathyQt4_tests_lib_glib_helpers_test_conn_helper_h_HEADER_GUARD_
+#define _TelepathyQt4_tests_lib_glib_helpers_test_conn_helper_h_HEADER_GUARD_
+
+#include <tests/lib/test.h>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Features>
+#include <TelepathyQt4/Types>
+
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+namespace Tp
+{
+class PendingContacts;
+}
+
+class TestConnHelper : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestConnHelper(Test *parent,
+ GType gType, const QString &account, const QString &protocol);
+ TestConnHelper(Test *parent,
+ GType gType, const char *firstPropertyName, ...);
+ TestConnHelper(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const QString &account, const QString &protocol);
+ TestConnHelper(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const char *firstPropertyName, ...);
+
+ virtual ~TestConnHelper();
+
+ GObject *service() const { return mService; }
+ Tp::ConnectionPtr client() const { return mClient; }
+ QString objectPath() const;
+
+ bool isValid() const;
+ bool isReady(const Tp::Features &features = Tp::Features()) const;
+ bool enableFeatures(const Tp::Features &features);
+ bool connect(const Tp::Features &features = Tp::Features());
+ bool disconnect();
+
+ QList<Tp::ContactPtr> contacts(const QStringList &ids,
+ const Tp::Features &features = Tp::Features());
+ QList<Tp::ContactPtr> contacts(const Tp::UIntList &handles,
+ const Tp::Features &features = Tp::Features());
+ QList<Tp::ContactPtr> upgradeContacts(const QList<Tp::ContactPtr> &contacts,
+ const Tp::Features &features = Tp::Features());
+
+ Tp::ChannelPtr createChannel(const QVariantMap &request);
+ Tp::ChannelPtr createChannel(const QString &channelType, const Tp::ContactPtr &target);
+ Tp::ChannelPtr createChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, uint targetHandle);
+ Tp::ChannelPtr createChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, const QString &targetID);
+ Tp::ChannelPtr ensureChannel(const QVariantMap &request);
+ Tp::ChannelPtr ensureChannel(const QString &channelType, const Tp::ContactPtr &target);
+ Tp::ChannelPtr ensureChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, uint targetHandle);
+ Tp::ChannelPtr ensureChannel(const QString &channelType,
+ Tp::HandleType targetHandleType, const QString &targetID);
+
+private Q_SLOTS:
+ void expectConnInvalidated();
+ void expectContactsForIdentifiersFinished(Tp::PendingOperation *op);
+ void expectContactsForHandlesFinished(Tp::PendingOperation *op);
+ void expectUpgradeContactsFinished(Tp::PendingOperation *op);
+ void expectCreateChannelFinished(Tp::PendingOperation *op);
+ void expectEnsureChannelFinished(Tp::PendingOperation *op);
+
+private:
+ void init(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const char *firstPropertyName, ...);
+ void init(Test *parent,
+ const Tp::ChannelFactoryConstPtr &channelFactory,
+ const Tp::ContactFactoryConstPtr &contactFactory,
+ GType gType, const char *firstPropertyName, va_list varArgs);
+
+ void expectPendingContactsFinished(Tp::PendingContacts *pc);
+
+ Test *mParent;
+ QEventLoop *mLoop;
+ GObject *mService;
+ Tp::ConnectionPtr mClient;
+
+ // The property retrieved by expectPendingContactsFinished()
+ QList<Tp::ContactPtr> mContacts;
+ // Property used by expectPendingContactsFinished()
+ Tp::Features mContactFeatures;
+ // The property retrieved by expectCreate/EnsureChannelFinished()
+ Tp::ChannelPtr mChannel;
+};
+
+#endif // _TelepathyQt4_tests_lib_glib_helpers_test_conn_helper_h_HEADER_GUARD_
diff --git a/qt4/tests/lib/glib/CMakeLists.txt b/qt4/tests/lib/glib/CMakeLists.txt
new file mode 100644
index 000000000..5979dabf2
--- /dev/null
+++ b/qt4/tests/lib/glib/CMakeLists.txt
@@ -0,0 +1,69 @@
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${TELEPATHY_GLIB_INCLUDE_DIR}
+ ${GLIB2_INCLUDE_DIR}
+ ${GOBJECT_INCLUDE_DIR}
+ ${GIO_INCLUDE_DIR}
+ ${GIOUNIX_INCLUDE_DIR}
+ ${DBUS_INCLUDE_DIR})
+
+if(ENABLE_TP_GLIB_TESTS)
+ add_subdirectory(callable)
+ add_subdirectory(contactlist)
+ add_subdirectory(contactlist2)
+ add_subdirectory(csh)
+ add_subdirectory(echo)
+ add_subdirectory(echo2)
+ add_subdirectory(future)
+
+ set(tp_glib_tests_SRCS
+ bug16307-conn.c
+ bug16307-conn.h
+ contacts-conn.c
+ contacts-conn.h
+ contacts-noroster-conn.c
+ contacts-noroster-conn.h
+ contact-list-manager.h
+ contact-list-manager.c
+ contact-search-chan.c
+ contact-search-chan.h
+ debug.h
+ params-cm.c
+ params-cm.h
+ simple-account.c
+ simple-account.h
+ simple-account-manager.c
+ simple-account-manager.h
+ simple-channel-dispatch-operation.c
+ simple-channel-dispatch-operation.h
+ simple-client.c
+ simple-client.h
+ simple-conn.c
+ simple-conn.h
+ simple-manager.c
+ simple-manager.h
+ textchan-group.c
+ textchan-group.h
+ textchan-null.c
+ textchan-null.h
+ util.c
+ util.h)
+ if(ENABLE_TP_GLIB_GIO_TESTS)
+ list(APPEND tp_glib_tests_SRCS stream-tube-chan.c stream-tube-chan.h)
+ endif(ENABLE_TP_GLIB_GIO_TESTS)
+ add_library(tp-glib-tests SHARED ${tp_glib_tests_SRCS})
+ target_link_libraries(tp-glib-tests
+ ${TELEPATHY_GLIB_LIBRARIES}
+ ${GLIB2_LIBRARIES}
+ ${GOBJECT_LIBRARIES}
+ ${GIO_LIBRARIES}
+ ${DBUS_GLIB_LIBRARIES}
+ example-cm-callable
+ example-cm-contactlist
+ example-cm-csh
+ example-cm-echo
+ example-cm-echo2
+ tp-glib-tests-future-extensions
+ future-example-cm-conference)
+
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/bug16307-conn.c b/qt4/tests/lib/glib/bug16307-conn.c
new file mode 100644
index 000000000..94c8368bf
--- /dev/null
+++ b/qt4/tests/lib/glib/bug16307-conn.c
@@ -0,0 +1,220 @@
+/*
+ * bug16307-conn.c - connection that reproduces the #15307 bug
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+#include "bug16307-conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/util.h>
+
+static void service_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsBug16307Connection,
+ tp_tests_bug16307_connection,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION,
+ service_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ NULL);
+ );
+
+/* type definition stuff */
+
+enum
+{
+ SIGNAL_GET_STATUS_RECEIVED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = {0};
+
+struct _TpTestsBug16307ConnectionPrivate
+{
+ /* In a real connection manager, the underlying implementation start
+ * connecting, then go to state CONNECTED when finished. Here there isn't
+ * actually a connection, so the connection process is fake and the time
+ * when it connects is, for this test purpose, when the D-Bus method GetStatus
+ * is called.
+ *
+ * Also, the GetStatus D-Bus reply is delayed until
+ * tp_tests_bug16307_connection_inject_get_status_return() is called
+ */
+ DBusGMethodInvocation *get_status_invocation;
+};
+
+static void
+tp_tests_bug16307_connection_init (TpTestsBug16307Connection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_BUG16307_CONNECTION, TpTestsBug16307ConnectionPrivate);
+}
+
+static void
+finalize (GObject *object)
+{
+ G_OBJECT_CLASS (tp_tests_bug16307_connection_parent_class)->finalize (object);
+}
+
+static gboolean
+pretend_connected (gpointer data)
+{
+ TpTestsBug16307Connection *self = TP_TESTS_BUG16307_CONNECTION (data);
+ TpBaseConnection *conn = (TpBaseConnection *) self;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+ gchar *account;
+
+ g_object_get (self, "account", &account, NULL);
+
+ conn->self_handle = tp_handle_ensure (contact_repo, account,
+ NULL, NULL);
+
+ g_free (account);
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return FALSE;
+}
+
+void
+tp_tests_bug16307_connection_inject_get_status_return (TpTestsBug16307Connection *self)
+{
+ TpBaseConnection *self_base = TP_BASE_CONNECTION (self);
+ DBusGMethodInvocation *context;
+ gulong get_signal_id;
+
+ /* if we don't have a pending get_status yet, wait for it */
+ if (self->priv->get_status_invocation == NULL)
+ {
+ GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+
+ get_signal_id = g_signal_connect_swapped (self, "get-status-received",
+ G_CALLBACK (g_main_loop_quit), loop);
+
+ g_main_loop_run (loop);
+
+ g_signal_handler_disconnect (self, get_signal_id);
+
+ g_main_loop_unref (loop);
+ }
+
+ context = self->priv->get_status_invocation;
+ g_assert (context != NULL);
+
+ if (self_base->status == TP_INTERNAL_CONNECTION_STATUS_NEW)
+ {
+ tp_svc_connection_return_from_get_status (
+ context, TP_CONNECTION_STATUS_DISCONNECTED);
+ }
+ else
+ {
+ tp_svc_connection_return_from_get_status (
+ context, self_base->status);
+ }
+
+ self->priv->get_status_invocation = NULL;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTING,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+tp_tests_bug16307_connection_class_init (TpTestsBug16307ConnectionClass *klass)
+{
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_CAPABILITIES,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ NULL };
+ static TpDBusPropertiesMixinPropImpl connection_properties[] = {
+ { "Status", "dbus-status-except-i-broke-it", NULL },
+ { NULL }
+ };
+
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass, sizeof (TpTestsBug16307ConnectionPrivate));
+
+ base_class->start_connecting = start_connecting;
+
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ signals[SIGNAL_GET_STATUS_RECEIVED] = g_signal_new ("get-status-received",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /* break the Connection D-Bus properties implementation, so that we always
+ * cause the slower introspection codepath (the one that actually calls
+ * GetStatus) in TpConnection to be invoked */
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TP_IFACE_QUARK_CONNECTION,
+ NULL, NULL, connection_properties);
+}
+
+/**
+ * tp_tests_bug16307_connection_get_status
+ *
+ * Implements D-Bus method GetStatus
+ * on interface org.freedesktop.Telepathy.Connection
+ */
+static void
+tp_tests_bug16307_connection_get_status (TpSvcConnection *iface,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *self_base = TP_BASE_CONNECTION (iface);
+ TpTestsBug16307Connection *self = TP_TESTS_BUG16307_CONNECTION (iface);
+
+ /* auto-connect on get_status */
+ if ((self_base->status == TP_INTERNAL_CONNECTION_STATUS_NEW ||
+ self_base->status == TP_CONNECTION_STATUS_DISCONNECTED))
+ {
+ pretend_connected (self);
+ }
+
+ /* D-Bus return call later */
+ g_assert (self->priv->get_status_invocation == NULL);
+ g_assert (context != NULL);
+ self->priv->get_status_invocation = context;
+
+ g_signal_emit (self, signals[SIGNAL_GET_STATUS_RECEIVED], 0);
+}
+
+
+static void
+service_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcConnectionClass *klass = g_iface;
+
+#define IMPLEMENT(prefix,x) tp_svc_connection_implement_##x (klass, \
+ tp_tests_bug16307_connection_##prefix##x)
+ IMPLEMENT(,get_status);
+#undef IMPLEMENT
+}
+
diff --git a/qt4/tests/lib/glib/bug16307-conn.h b/qt4/tests/lib/glib/bug16307-conn.h
new file mode 100644
index 000000000..678ba4582
--- /dev/null
+++ b/qt4/tests/lib/glib/bug16307-conn.h
@@ -0,0 +1,61 @@
+/*
+ * bug16307-conn.h - header for a connection that reproduces the #15307 bug
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_BUG16307_CONN_H__
+#define __TP_TESTS_BUG16307_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+#include "simple-conn.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsBug16307Connection TpTestsBug16307Connection;
+typedef struct _TpTestsBug16307ConnectionClass TpTestsBug16307ConnectionClass;
+typedef struct _TpTestsBug16307ConnectionPrivate TpTestsBug16307ConnectionPrivate;
+
+struct _TpTestsBug16307ConnectionClass {
+ TpTestsSimpleConnectionClass parent_class;
+};
+
+struct _TpTestsBug16307Connection {
+ TpTestsSimpleConnection parent;
+
+ TpTestsBug16307ConnectionPrivate *priv;
+};
+
+GType tp_tests_bug16307_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_BUG16307_CONNECTION \
+ (tp_tests_bug16307_connection_get_type ())
+#define TP_TESTS_BUG16307_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_BUG16307_CONNECTION, \
+ TpTestsBug16307Connection))
+#define TP_TESTS_BUG16307_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_BUG16307_CONNECTION, \
+ TpTestsBug16307ConnectionClass))
+#define TP_TESTS_BUG16307_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_BUG16307_CONNECTION))
+#define TP_TESTS_BUG16307_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_BUG16307_CONNECTION))
+#define TP_TESTS_BUG16307_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_BUG16307_CONNECTION, \
+ TpTestsBug16307ConnectionClass))
+
+/* Cause "network events", for debugging/testing */
+
+void tp_tests_bug16307_connection_inject_get_status_return (TpTestsBug16307Connection *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_BUG16307_CONN_H__ */
diff --git a/qt4/tests/lib/glib/callable/CMakeLists.txt b/qt4/tests/lib/glib/callable/CMakeLists.txt
new file mode 100644
index 000000000..d305b7813
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/CMakeLists.txt
@@ -0,0 +1,17 @@
+if(ENABLE_TP_GLIB_TESTS)
+ set(example_cm_callable_SRCS
+ conn.c
+ conn.h
+ connection-manager.c
+ connection-manager.h
+ media-channel.c
+ media-channel.h
+ media-manager.c
+ media-manager.h
+ media-stream.c
+ media-stream.h)
+
+ add_library(example-cm-callable STATIC ${example_cm_callable_SRCS})
+ target_link_libraries(example-cm-callable ${TPGLIB_LIBRARIES})
+ tpqt4_generate_manager_file(${CMAKE_CURRENT_SOURCE_DIR}/manager-file.py example_callable.manager connection-manager.c)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/callable/conn.c b/qt4/tests/lib/glib/callable/conn.c
new file mode 100644
index 000000000..04bc31045
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/conn.c
@@ -0,0 +1,421 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo-static.h>
+
+#include "media-manager.h"
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallableConnection,
+ example_callable_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init))
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_AVAILABLE,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallableConnectionPrivate
+{
+ gchar *account;
+ guint simulation_delay;
+ gboolean away;
+ gchar *presence_message;
+};
+
+static void
+example_callable_connection_init (ExampleCallableConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_CONNECTION,
+ ExampleCallableConnectionPrivate);
+ self->priv->away = FALSE;
+ self->priv->presence_message = g_strdup ("");
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_value_dup_string (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_free (self->priv->account);
+ g_free (self->priv->presence_message);
+
+ G_OBJECT_CLASS (example_callable_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (conn);
+
+ return g_strdup_printf ("%s@%p", self->priv->account, self);
+}
+
+gchar *
+example_callable_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Contact ID must not be empty");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_callable_normalize_contact, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ g_ptr_array_add (ret,
+ g_object_new (EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER,
+ "connection", conn,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL));
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleCallableConnection *self = EXAMPLE_CALLABLE_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, error);
+
+ if (conn->self_handle == 0)
+ return FALSE;
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static void
+constructed (GObject *object)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (example_callable_connection_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallableConnection, contacts_mixin));
+ tp_base_connection_register_with_contacts_mixin (base);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallableConnection, presence_mixin));
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
+}
+
+static gboolean
+status_available (GObject *object,
+ guint index_)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+
+ if (base->status != TP_CONNECTION_STATUS_CONNECTED)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GHashTable *
+get_contact_statuses (GObject *object,
+ const GArray *contacts,
+ GError **error)
+{
+ ExampleCallableConnection *self =
+ EXAMPLE_CALLABLE_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ guint i;
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+ ExampleCallablePresence presence;
+ GHashTable *parameters;
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ /* we know our own status from the connection; for this example CM,
+ * everyone else's status is assumed to be "available" */
+ if (contact == base->self_handle)
+ {
+ presence = (self->priv->away ? EXAMPLE_CALLABLE_PRESENCE_AWAY
+ : EXAMPLE_CALLABLE_PRESENCE_AVAILABLE);
+
+ if (self->priv->presence_message[0] != '\0')
+ g_hash_table_insert (parameters, "message",
+ tp_g_value_slice_new_string (self->priv->presence_message));
+ }
+ else
+ {
+ presence = EXAMPLE_CALLABLE_PRESENCE_AVAILABLE;
+ }
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ tp_presence_status_new (presence, parameters));
+ g_hash_table_destroy (parameters);
+ }
+
+ return result;
+}
+
+static gboolean
+set_own_status (GObject *object,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ ExampleCallableConnection *self =
+ EXAMPLE_CALLABLE_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ GHashTable *presences;
+ const gchar *message = "";
+
+ if (status->optional_arguments != NULL)
+ {
+ GValue *v = g_hash_table_lookup (status->optional_arguments, "message");
+
+ if (v != NULL && G_VALUE_HOLDS_STRING (v))
+ {
+ message = g_value_get_string (v);
+
+ if (message == NULL)
+ message = "";
+ }
+ }
+
+ if (status->index == EXAMPLE_CALLABLE_PRESENCE_AWAY)
+ {
+ if (self->priv->away && !tp_strdiff (message,
+ self->priv->presence_message))
+ return TRUE;
+
+ self->priv->away = TRUE;
+ }
+ else
+ {
+ if (!self->priv->away && !tp_strdiff (message,
+ self->priv->presence_message))
+ return TRUE;
+
+ self->priv->away = FALSE;
+ }
+
+ g_free (self->priv->presence_message);
+ self->priv->presence_message = g_strdup (message);
+
+ presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+ g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle),
+ (gpointer) status);
+ tp_presence_mixin_emit_presence_update (object, presences);
+ g_hash_table_destroy (presences);
+
+ if (!self->priv->away)
+ {
+ g_signal_emit (self, signals[SIGNAL_AVAILABLE], 0, message);
+ }
+
+ return TRUE;
+}
+
+static const TpPresenceStatusOptionalArgumentSpec can_have_message[] = {
+ { "message", "s", NULL, NULL },
+ { NULL }
+};
+
+/* Must be kept in sync with ExampleCallablePresence enum in header */
+static const TpPresenceStatusSpec presence_statuses[] = {
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
+ { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, can_have_message },
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE,
+ can_have_message },
+ { NULL }
+};
+
+static void
+example_callable_connection_class_init (
+ ExampleCallableConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ NULL };
+ TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ /* Used in the media manager, to simulate an incoming call when we become
+ * available */
+ signals[SIGNAL_AVAILABLE] = g_signal_new ("available",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableConnectionClass, contacts_mixin));
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableConnectionClass, presence_mixin),
+ status_available, get_contact_statuses, set_own_status,
+ presence_statuses);
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+}
diff --git a/qt4/tests/lib/glib/callable/conn.h b/qt4/tests/lib/glib/callable/conn.h
new file mode 100644
index 000000000..f3d4690d3
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/conn.h
@@ -0,0 +1,78 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CALLABLE_CONN_H__
+#define __EXAMPLE_CALLABLE_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableConnection ExampleCallableConnection;
+typedef struct _ExampleCallableConnectionPrivate
+ ExampleCallableConnectionPrivate;
+
+typedef struct _ExampleCallableConnectionClass ExampleCallableConnectionClass;
+typedef struct _ExampleCallableConnectionClassPrivate
+ ExampleCallableConnectionClassPrivate;
+
+struct _ExampleCallableConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+
+ ExampleCallableConnectionClassPrivate *priv;
+};
+
+struct _ExampleCallableConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ ExampleCallableConnectionPrivate *priv;
+};
+
+GType example_callable_connection_get_type (void);
+
+#define EXAMPLE_TYPE_CALLABLE_CONNECTION \
+ (example_callable_connection_get_type ())
+#define EXAMPLE_CALLABLE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION, \
+ ExampleCallableConnection))
+#define EXAMPLE_CALLABLE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION, \
+ ExampleCallableConnectionClass))
+#define EXAMPLE_IS_CALLABLE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION))
+#define EXAMPLE_IS_CALLABLE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION))
+#define EXAMPLE_CALLABLE_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION, \
+ ExampleCallableConnectionClass))
+
+gchar *example_callable_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id, gpointer context, GError **error);
+
+/* Must be kept in sync with the array presence_statuses in conn.c */
+typedef enum {
+ EXAMPLE_CALLABLE_PRESENCE_OFFLINE = 0,
+ EXAMPLE_CALLABLE_PRESENCE_UNKNOWN,
+ EXAMPLE_CALLABLE_PRESENCE_ERROR,
+ EXAMPLE_CALLABLE_PRESENCE_AWAY,
+ EXAMPLE_CALLABLE_PRESENCE_AVAILABLE
+} ExampleCallablePresence;
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/callable/connection-manager.c b/qt4/tests/lib/glib/callable/connection-manager.c
new file mode 100644
index 000000000..fa8e0f0e5
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/connection-manager.c
@@ -0,0 +1,130 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleCallableConnectionManager,
+ example_callable_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _ExampleCallableConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+example_callable_connection_manager_init (
+ ExampleCallableConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER,
+ ExampleCallableConnectionManagerPrivate);
+}
+
+typedef struct {
+ gchar *account;
+ guint simulation_delay;
+} ExampleParams;
+
+static gboolean
+account_param_filter (const TpCMParamSpec *paramspec,
+ GValue *value,
+ GError **error)
+{
+ const gchar *id = g_value_get_string (value);
+
+ g_value_take_string (value,
+ example_callable_normalize_contact (NULL, id, NULL, error));
+
+ if (g_value_get_string (value) == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+#include "_gen/param-spec-struct.h"
+
+static gpointer
+alloc_params (void)
+{
+ ExampleParams *params = g_slice_new0 (ExampleParams);
+
+ params->simulation_delay = 1000;
+ return params;
+}
+
+static void
+free_params (gpointer p)
+{
+ ExampleParams *params = p;
+
+ g_free (params->account);
+
+ g_slice_free (ExampleParams, params);
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+ { "example", example_callable_example_params,
+ alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ ExampleParams *params = parsed_params;
+ ExampleCallableConnection *conn;
+
+ conn = EXAMPLE_CALLABLE_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_CALLABLE_CONNECTION,
+ "account", params->account,
+ "simulation-delay", params->simulation_delay,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_callable_connection_manager_class_init (
+ ExampleCallableConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableConnectionManagerPrivate));
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_callable";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/qt4/tests/lib/glib/callable/connection-manager.h b/qt4/tests/lib/glib/callable/connection-manager.h
new file mode 100644
index 000000000..5d854cb1c
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/connection-manager.h
@@ -0,0 +1,73 @@
+/*
+ * manager.h - header for an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_CONNECTION_MANAGER_H__
+#define __EXAMPLE_CALLABLE_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableConnectionManager
+ ExampleCallableConnectionManager;
+typedef struct _ExampleCallableConnectionManagerPrivate
+ ExampleCallableConnectionManagerPrivate;
+
+typedef struct _ExampleCallableConnectionManagerClass
+ ExampleCallableConnectionManagerClass;
+typedef struct _ExampleCallableConnectionManagerClassPrivate
+ ExampleCallableConnectionManagerClassPrivate;
+
+struct _ExampleCallableConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ ExampleCallableConnectionManagerClassPrivate *priv;
+};
+
+struct _ExampleCallableConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleCallableConnectionManagerPrivate *priv;
+};
+
+GType example_callable_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER \
+ (example_callable_connection_manager_get_type ())
+#define EXAMPLE_CALLABLE_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER, \
+ ExampleCallableConnectionManager))
+#define EXAMPLE_CALLABLE_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER, \
+ ExampleCallableConnectionManagerClass))
+#define EXAMPLE_IS_CALLABLE_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CALLABLE_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER))
+#define EXAMPLE_CALLABLE_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_CONNECTION_MANAGER, \
+ ExampleCallableConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/callable/manager-file.py b/qt4/tests/lib/glib/callable/manager-file.py
new file mode 100644
index 000000000..4fdb7e8b5
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/manager-file.py
@@ -0,0 +1,23 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_callable'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'account_param_filter',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ 'simulation-delay': {
+ 'dtype': 'u',
+ 'default': 1000,
+ },
+ },
+ }
+STRUCTS = {
+ 'example': 'ExampleParams'
+ }
diff --git a/qt4/tests/lib/glib/callable/media-channel.c b/qt4/tests/lib/glib/callable/media-channel.c
new file mode 100644
index 000000000..ddf47b4c8
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/media-channel.c
@@ -0,0 +1,1467 @@
+/*
+ * media-channel.c - an example 1-1 streamed media call.
+ *
+ * For simplicity, this channel emulates a device with its own
+ * audio/video user interface, like a video-equipped form of the phones
+ * manipulated by telepathy-snom or gnome-phone-manager.
+ *
+ * As a result, this channel does not have the MediaSignalling interface, and
+ * clients should not attempt to do their own streaming using
+ * telepathy-farsight, telepathy-stream-engine or maemo-stream-engine.
+ *
+ * In practice, nearly all connection managers also have the MediaSignalling
+ * interface on their streamed media channels. Usage for those CMs is the
+ * same, except that whichever client is the primary handler for the channel
+ * should also hand the channel over to telepathy-farsight or
+ * telepathy-stream-engine to implement the actual streaming.
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "media-channel.h"
+
+#include "media-stream.h"
+
+#include <string.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+static void media_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+static void hold_iface_init (gpointer iface, gpointer data);
+static void dtmf_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallableMediaChannel,
+ example_callable_media_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
+ media_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
+ hold_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DTMF,
+ dtmf_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL))
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_SIMULATION_DELAY,
+ PROP_INITIAL_AUDIO,
+ PROP_INITIAL_VIDEO,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_CALL_TERMINATED,
+ N_SIGNALS
+};
+
+typedef enum {
+ PROGRESS_NONE,
+ PROGRESS_CALLING,
+ PROGRESS_ACTIVE,
+ PROGRESS_ENDED
+} ExampleCallableCallProgress;
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallableMediaChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+ ExampleCallableCallProgress progress;
+
+ guint simulation_delay;
+
+ guint next_stream_id;
+
+ GHashTable *streams;
+
+ guint hold_state;
+ guint hold_state_reason;
+
+ gboolean locally_requested;
+ gboolean initial_audio;
+ gboolean initial_video;
+ gboolean disposed;
+};
+
+static const char * example_callable_media_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ TP_IFACE_CHANNEL_INTERFACE_HOLD,
+ TP_IFACE_CHANNEL_INTERFACE_DTMF,
+ NULL
+};
+
+static void
+example_callable_media_channel_init (ExampleCallableMediaChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ ExampleCallableMediaChannelPrivate);
+
+ self->priv->next_stream_id = 1;
+ self->priv->streams = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
+ self->priv->hold_state_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
+}
+
+static ExampleCallableMediaStream *example_callable_media_channel_add_stream (
+ ExampleCallableMediaChannel *self, TpMediaStreamType media_type,
+ gboolean locally_requested);
+
+static void
+constructed (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_callable_media_channel_parent_class)->constructed;
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpIntSet *members;
+ TpIntSet *local_pending;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+ tp_handle_ref (contact_repo, self->priv->initiator);
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn),
+ self->priv->object_path, self);
+
+ tp_group_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCallableMediaChannel, group),
+ contact_repo, self->priv->conn->self_handle);
+
+ /* Initially, the channel contains the initiator as a member; they are also
+ * the actor for the change that adds any initial members. */
+
+ members = tp_intset_new_containing (self->priv->initiator);
+
+ if (self->priv->locally_requested)
+ {
+ /* Nobody is locally pending. The remote peer will turn up in
+ * remote-pending state when we actually contact them, which is done
+ * in RequestStreams */
+ self->priv->progress = PROGRESS_NONE;
+ local_pending = NULL;
+ }
+ else
+ {
+ /* This is an incoming call, so the self-handle is locally
+ * pending, to indicate that we need to answer. */
+ self->priv->progress = PROGRESS_CALLING;
+ local_pending = tp_intset_new_containing (self->priv->conn->self_handle);
+ }
+
+ tp_group_mixin_change_members (object, "",
+ members /* added */,
+ NULL /* nobody removed */,
+ local_pending, /* added to local-pending */
+ NULL /* nobody added to remote-pending */,
+ self->priv->initiator /* actor */, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (members);
+
+ if (local_pending != NULL)
+ tp_intset_destroy (local_pending);
+
+ /* We don't need to allow adding or removing members to this Group in ways
+ * that need flags set, so the only flag we set is to say we support the
+ * Properties interface to the Group.
+ *
+ * It doesn't make sense to add anyone to the Group, since we already know
+ * who we're going to call (or were called by). The only call to AddMembers
+ * we need to support is to move ourselves from local-pending to member in
+ * the incoming call case, and that's always allowed anyway.
+ *
+ * (Connection managers that support the various backwards-compatible
+ * ways to make an outgoing StreamedMedia channel have to support adding the
+ * peer to remote-pending, but that has no actual effect other than to
+ * obscure what's going on; in this one, there's no need to support that
+ * usage.)
+ *
+ * Similarly, it doesn't make sense to remove anyone from this Group apart
+ * from ourselves (to hang up), and removing the SelfHandle is always
+ * allowed anyway.
+ */
+ tp_group_mixin_change_flags (object, TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+
+ /* Future versions of telepathy-spec will allow a channel request to
+ * say "initially include an audio stream" and/or "initially include a video
+ * stream", which would be represented like this; we don't support this
+ * usage yet, though, so ExampleCallableMediaManager will never invoke
+ * our constructor in this way. */
+ g_assert (!(self->priv->locally_requested && self->priv->initial_audio));
+ g_assert (!(self->priv->locally_requested && self->priv->initial_video));
+
+ if (!self->priv->locally_requested)
+ {
+ /* the caller has almost certainly asked us for some streams - there's
+ * not much point in having a call otherwise */
+
+ if (self->priv->initial_audio)
+ {
+ g_message ("Channel initially has an audio stream");
+ example_callable_media_channel_add_stream (self,
+ TP_MEDIA_STREAM_TYPE_AUDIO, FALSE);
+ }
+
+ if (self->priv->initial_video)
+ {
+ g_message ("Channel initially has a video stream");
+ example_callable_media_channel_add_stream (self,
+ TP_MEDIA_STREAM_TYPE_VIDEO, FALSE);
+ }
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->handle));
+ }
+ break;
+
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, self->priv->locally_requested);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->initiator);
+ break;
+
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->initiator));
+ }
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, example_callable_media_channel_interfaces);
+ break;
+
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, (self->priv->progress == PROGRESS_ENDED));
+ break;
+
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ case PROP_INITIAL_AUDIO:
+ g_value_set_boolean (value, self->priv->initial_audio);
+ break;
+
+ case PROP_INITIAL_VIDEO:
+ g_value_set_boolean (value, self->priv->initial_video);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_assert (self->priv->object_path == NULL);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * contact repo yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ /* likewise */
+ self->priv->initiator = g_value_get_uint (value);
+ break;
+
+ case PROP_REQUESTED:
+ self->priv->locally_requested = g_value_get_boolean (value);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ case PROP_INITIAL_AUDIO:
+ self->priv->initial_audio = g_value_get_boolean (value);
+ break;
+
+ case PROP_INITIAL_VIDEO:
+ self->priv->initial_video = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+example_callable_media_channel_close (ExampleCallableMediaChannel *self,
+ TpHandle actor,
+ TpChannelGroupChangeReason reason)
+{
+ if (self->priv->progress != PROGRESS_ENDED)
+ {
+ TpIntSet *everyone;
+
+ self->priv->progress = PROGRESS_ENDED;
+
+ if (actor == self->group.self_handle)
+ {
+ const gchar *send_reason;
+
+ /* In a real protocol these would be some sort of real protocol
+ * construct, like an XMPP error stanza or a SIP error code */
+ switch (reason)
+ {
+ case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY:
+ send_reason = "<user-is-busy/>";
+ break;
+
+ case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER:
+ send_reason = "<no-answer/>";
+ break;
+
+ default:
+ send_reason = "<call-terminated/>";
+ }
+
+ g_message ("SIGNALLING: send: Terminating call: %s", send_reason);
+ }
+
+ everyone = tp_intset_new_containing (self->priv->handle);
+ tp_intset_add (everyone, self->group.self_handle);
+ tp_group_mixin_change_members ((GObject *) self, "",
+ NULL /* nobody added */,
+ everyone /* removed */,
+ NULL /* nobody locally pending */,
+ NULL /* nobody remotely pending */,
+ actor,
+ reason);
+ tp_intset_destroy (everyone);
+
+ g_signal_emit (self, signals[SIGNAL_CALL_TERMINATED], 0);
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ g_hash_table_destroy (self->priv->streams);
+ self->priv->streams = NULL;
+
+ example_callable_media_channel_close (self, self->group.self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ ((GObjectClass *) example_callable_media_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, self->priv->handle);
+ tp_handle_unref (contact_handles, self->priv->initiator);
+
+ g_free (self->priv->object_path);
+
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) example_callable_media_channel_parent_class)->finalize (object);
+}
+
+static gboolean
+add_member (GObject *object,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ /* In connection managers that supported the RequestChannel method for
+ * streamed media channels, it would be necessary to support adding the
+ * called contact to the members of an outgoing call. However, in this
+ * legacy-free example, we don't support that usage, so the only use for
+ * AddMembers is to accept an incoming call.
+ */
+
+ if (member == self->group.self_handle &&
+ tp_handle_set_is_member (self->group.local_pending, member))
+ {
+ /* We're in local-pending, move to members to accept. */
+ TpIntSet *set = tp_intset_new_containing (member);
+ GHashTableIter iter;
+ gpointer v;
+
+ g_assert (self->priv->progress == PROGRESS_CALLING);
+
+ g_message ("SIGNALLING: send: Accepting incoming call from %s",
+ tp_handle_inspect (contact_repo, self->priv->handle));
+
+ self->priv->progress = PROGRESS_ACTIVE;
+
+ tp_group_mixin_change_members (object, "",
+ set /* added */,
+ NULL /* nobody removed */,
+ NULL /* nobody added to local pending */,
+ NULL /* nobody added to remote pending */,
+ member /* actor */, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (set);
+
+ g_hash_table_iter_init (&iter, self->priv->streams);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ /* we accept the proposed stream direction... */
+ example_callable_media_stream_accept_proposed_direction (v);
+ /* ... and the stream tries to connect */
+ example_callable_media_stream_connect (v);
+ }
+
+ return TRUE;
+ }
+
+ /* Otherwise it's a meaningless request, so reject it. */
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Cannot add handle %u to channel", member);
+ return FALSE;
+}
+
+static gboolean
+remove_member_with_reason (GObject *object,
+ TpHandle member,
+ const gchar *message,
+ guint reason,
+ GError **error)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (object);
+
+ /* The TpGroupMixin won't call this unless removing the member is allowed
+ * by the group flags, which in this case means it must be our own handle
+ * (because the other user never appears in local-pending).
+ */
+
+ g_assert (member == self->group.self_handle);
+
+ example_callable_media_channel_close (self, self->group.self_handle, reason);
+ return TRUE;
+}
+
+static void
+example_callable_media_channel_class_init (ExampleCallableMediaChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableMediaChannelPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("initial-audio", "Initial audio?",
+ "True if this channel had an audio stream when first announced",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("initial-video", "Initial video?",
+ "True if this channel had a video stream when first announced",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
+ param_spec);
+
+ signals[SIGNAL_CALL_TERMINATED] = g_signal_new ("call-terminated",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableMediaChannelClass,
+ dbus_properties_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCallableMediaChannelClass, group_class),
+ add_member,
+ NULL);
+ tp_group_mixin_class_allow_self_removal (object_class);
+ tp_group_mixin_class_set_remove_with_reason_func (object_class,
+ remove_member_with_reason);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+
+ example_callable_media_channel_close (self, self->group.self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ example_callable_media_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+media_list_streams (TpSvcChannelTypeStreamedMedia *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ GPtrArray *array = g_ptr_array_sized_new (g_hash_table_size (
+ self->priv->streams));
+ GHashTableIter iter;
+ gpointer v;
+
+ g_hash_table_iter_init (&iter, self->priv->streams);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ ExampleCallableMediaStream *stream = v;
+ GValueArray *va;
+
+ g_object_get (stream,
+ "stream-info", &va,
+ NULL);
+
+ g_ptr_array_add (array, va);
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_list_streams (context,
+ array);
+ g_ptr_array_foreach (array, (GFunc) g_value_array_free, NULL);
+ g_ptr_array_free (array, TRUE);
+}
+
+static void
+media_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
+ const GArray *stream_ids,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ guint i;
+
+ for (i = 0; i < stream_ids->len; i++)
+ {
+ guint id = g_array_index (stream_ids, guint, i);
+
+ if (g_hash_table_lookup (self->priv->streams,
+ GUINT_TO_POINTER (id)) == NULL)
+ {
+ GError *error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No stream with ID %u in this channel", id);
+
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ for (i = 0; i < stream_ids->len; i++)
+ {
+ guint id = g_array_index (stream_ids, guint, i);
+
+ example_callable_media_stream_close (
+ g_hash_table_lookup (self->priv->streams, GUINT_TO_POINTER (id)));
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_remove_streams (context);
+}
+
+static void
+media_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface,
+ guint stream_id,
+ guint stream_direction,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ ExampleCallableMediaStream *stream = g_hash_table_lookup (
+ self->priv->streams, GUINT_TO_POINTER (stream_id));
+ GError *error = NULL;
+
+ if (stream == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No stream with ID %u in this channel", stream_id);
+ goto error;
+ }
+
+ if (stream_direction > TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Stream direction %u is not valid", stream_direction);
+ goto error;
+ }
+
+ /* In some protocols, streams cannot be neither sending nor receiving, so
+ * if a stream is set to TP_MEDIA_STREAM_DIRECTION_NONE, this is equivalent
+ * to removing it with RemoveStreams. (This is true in XMPP, for instance.)
+ *
+ * If this was the case, there would be code like this here:
+ *
+ * if (stream_direction == TP_MEDIA_STREAM_DIRECTION_NONE)
+ * {
+ * example_callable_media_stream_close (stream);
+ * tp_svc_channel_type_streamed_media_return_from_request_stream_direction (
+ * context);
+ * return;
+ * }
+ *
+ * However, for this example we'll emulate a protocol where streams can be
+ * directionless.
+ */
+
+ if (!example_callable_media_stream_change_direction (stream,
+ stream_direction, &error))
+ goto error;
+
+ tp_svc_channel_type_streamed_media_return_from_request_stream_direction (
+ context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+stream_removed_cb (ExampleCallableMediaStream *stream,
+ ExampleCallableMediaChannel *self)
+{
+ guint id;
+
+ g_object_get (stream,
+ "id", &id,
+ NULL);
+
+ g_signal_handlers_disconnect_matched (stream, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ g_hash_table_remove (self->priv->streams, GUINT_TO_POINTER (id));
+ tp_svc_channel_type_streamed_media_emit_stream_removed (self, id);
+
+ if (g_hash_table_size (self->priv->streams) == 0)
+ {
+ /* no streams left, so the call terminates */
+ example_callable_media_channel_close (self, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ }
+}
+
+static void
+stream_direction_changed_cb (ExampleCallableMediaStream *stream,
+ ExampleCallableMediaChannel *self)
+{
+ guint id, direction, pending;
+
+ g_object_get (stream,
+ "id", &id,
+ "direction", &direction,
+ "pending-send", &pending,
+ NULL);
+
+ tp_svc_channel_type_streamed_media_emit_stream_direction_changed (self, id,
+ direction, pending);
+}
+
+static void
+stream_state_changed_cb (ExampleCallableMediaStream *stream,
+ GParamSpec *spec G_GNUC_UNUSED,
+ ExampleCallableMediaChannel *self)
+{
+ guint id, state;
+
+ g_object_get (stream,
+ "id", &id,
+ "state", &state,
+ NULL);
+
+ tp_svc_channel_type_streamed_media_emit_stream_state_changed (self, id,
+ state);
+}
+
+static gboolean
+simulate_contact_ended_cb (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to do so, do nothing! */
+ if (self->priv->progress == PROGRESS_ENDED)
+ return FALSE;
+
+ g_message ("SIGNALLING: receive: call terminated: <call-terminated/>");
+
+ example_callable_media_channel_close (self, self->priv->handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ return FALSE;
+}
+
+static gboolean
+simulate_contact_answered_cb (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+ TpIntSet *peer_set;
+ GHashTableIter iter;
+ gpointer v;
+ TpHandleRepoIface *contact_repo;
+ const gchar *peer;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to answer, do nothing */
+ if (self->priv->progress == PROGRESS_ENDED)
+ return FALSE;
+
+ /* otherwise, we're waiting for a response from the contact, which now
+ * arrives */
+ g_assert (self->priv->progress == PROGRESS_CALLING);
+
+ g_message ("SIGNALLING: receive: contact answered our call");
+
+ self->priv->progress = PROGRESS_ACTIVE;
+
+ peer_set = tp_intset_new_containing (self->priv->handle);
+ tp_group_mixin_change_members ((GObject *) self, "",
+ peer_set /* added */,
+ NULL /* nobody removed */,
+ NULL /* nobody added to local-pending */,
+ NULL /* nobody added to remote-pending */,
+ self->priv->handle /* actor */,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (peer_set);
+
+ g_hash_table_iter_init (&iter, self->priv->streams);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ /* remote contact accepts our proposed stream direction... */
+ example_callable_media_stream_simulate_contact_agreed_to_send (v);
+ /* ... and the stream tries to connect */
+ example_callable_media_stream_connect (v);
+ }
+
+ contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ /* If the contact's ID contains the magic string "(terminate)", simulate
+ * them hanging up after a moment. */
+ if (strstr (peer, "(terminate)") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_ended_cb, g_object_ref (self),
+ g_object_unref);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+simulate_contact_busy_cb (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+
+ /* if the call has been cancelled while we were waiting for the
+ * contact to answer, do nothing */
+ if (self->priv->progress == PROGRESS_ENDED)
+ return FALSE;
+
+ /* otherwise, we're waiting for a response from the contact, which now
+ * arrives */
+ g_assert (self->priv->progress == PROGRESS_CALLING);
+
+ g_message ("SIGNALLING: receive: call terminated: <user-is-busy/>");
+
+ example_callable_media_channel_close (self, self->priv->handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_BUSY);
+
+ return FALSE;
+}
+
+static ExampleCallableMediaStream *
+example_callable_media_channel_add_stream (ExampleCallableMediaChannel *self,
+ TpMediaStreamType media_type,
+ gboolean locally_requested)
+{
+ ExampleCallableMediaStream *stream;
+ guint id = self->priv->next_stream_id++;
+ guint state, direction, pending_send;
+
+ if (locally_requested)
+ {
+ g_message ("SIGNALLING: send: new %s stream",
+ media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
+ }
+
+ stream = g_object_new (EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM,
+ "channel", self,
+ "id", id,
+ "handle", self->priv->handle,
+ "type", media_type,
+ "locally-requested", locally_requested,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL);
+
+ g_hash_table_insert (self->priv->streams, GUINT_TO_POINTER (id), stream);
+
+ tp_svc_channel_type_streamed_media_emit_stream_added (self, id,
+ self->priv->handle, media_type);
+
+ g_object_get (stream,
+ "state", &state,
+ "direction", &direction,
+ "pending-send", &pending_send,
+ NULL);
+
+ /* this is the "implicit" initial state mandated by telepathy-spec */
+ if (state != TP_MEDIA_STREAM_STATE_DISCONNECTED)
+ {
+ tp_svc_channel_type_streamed_media_emit_stream_state_changed (self, id,
+ state);
+ }
+
+ /* this is the "implicit" initial direction mandated by telepathy-spec */
+ if (direction != TP_MEDIA_STREAM_DIRECTION_RECEIVE ||
+ pending_send != TP_MEDIA_STREAM_PENDING_LOCAL_SEND)
+ {
+ tp_svc_channel_type_streamed_media_emit_stream_direction_changed (self,
+ id, direction, pending_send);
+ }
+
+ g_signal_connect (stream, "removed", G_CALLBACK (stream_removed_cb),
+ self);
+ g_signal_connect (stream, "notify::state",
+ G_CALLBACK (stream_state_changed_cb), self);
+ g_signal_connect (stream, "direction-changed",
+ G_CALLBACK (stream_direction_changed_cb), self);
+
+ if (self->priv->progress == PROGRESS_ACTIVE)
+ {
+ example_callable_media_stream_connect (stream);
+ }
+
+ return stream;
+}
+
+static void
+media_request_streams (TpSvcChannelTypeStreamedMedia *iface,
+ guint contact_handle,
+ const GArray *media_types,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GPtrArray *array;
+ guint i;
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (contact_repo, contact_handle, &error))
+ goto error;
+
+ if (contact_handle != self->priv->handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "This channel is for handle #%u, we can't make a stream to #%u",
+ self->priv->handle, contact_handle);
+ goto error;
+ }
+
+ if (self->priv->progress == PROGRESS_ENDED)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Call has terminated");
+ goto error;
+ }
+
+ for (i = 0; i < media_types->len; i++)
+ {
+ guint media_type = g_array_index (media_types, guint, i);
+
+ switch (media_type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ break;
+ default:
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%u is not a valid Media_Stream_Type", media_type);
+ goto error;
+ }
+ }
+
+ array = g_ptr_array_sized_new (media_types->len);
+
+ for (i = 0; i < media_types->len; i++)
+ {
+ guint media_type = g_array_index (media_types, guint, i);
+ ExampleCallableMediaStream *stream;
+ GValueArray *info;
+
+ if (self->priv->progress < PROGRESS_CALLING)
+ {
+ TpIntSet *peer_set = tp_intset_new_containing (self->priv->handle);
+ const gchar *peer;
+
+ g_message ("SIGNALLING: send: new streamed media call");
+ self->priv->progress = PROGRESS_CALLING;
+
+ tp_group_mixin_change_members ((GObject *) self, "",
+ NULL /* nobody added */,
+ NULL /* nobody removed */,
+ NULL /* nobody added to local-pending */,
+ peer_set /* added to remote-pending */,
+ self->group.self_handle /* actor */,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (peer_set);
+
+ /* In this example there is no real contact, so just simulate them
+ * answering after a short time - unless the contact's name
+ * contains "(no answer)" or "(busy)" */
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ if (strstr (peer, "(busy)") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_busy_cb, g_object_ref (self),
+ g_object_unref);
+ }
+ else if (strstr (peer, "(no answer)") != NULL)
+ {
+ /* do nothing - the call just rings forever */
+ }
+ else
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_contact_answered_cb, g_object_ref (self),
+ g_object_unref);
+ }
+ }
+
+ stream = example_callable_media_channel_add_stream (self, media_type,
+ TRUE);
+
+ g_object_get (stream,
+ "stream-info", &info,
+ NULL);
+
+ g_ptr_array_add (array, info);
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_request_streams (context,
+ array);
+ g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, array);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+media_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeStreamedMediaClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_type_streamed_media_implement_##x (klass, media_##x)
+ IMPLEMENT (list_streams);
+ IMPLEMENT (remove_streams);
+ IMPLEMENT (request_stream_direction);
+ IMPLEMENT (request_streams);
+#undef IMPLEMENT
+}
+
+static gboolean
+simulate_hold (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_HELD;
+ g_message ("SIGNALLING: hold state changed to held");
+ tp_svc_channel_interface_hold_emit_hold_state_changed (self,
+ self->priv->hold_state, self->priv->hold_state_reason);
+ return FALSE;
+}
+
+static gboolean
+simulate_unhold (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
+ g_message ("SIGNALLING: hold state changed to unheld");
+ tp_svc_channel_interface_hold_emit_hold_state_changed (self,
+ self->priv->hold_state, self->priv->hold_state_reason);
+ return FALSE;
+}
+
+static gboolean
+simulate_inability_to_unhold (gpointer p)
+{
+ ExampleCallableMediaChannel *self = p;
+
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_HOLD;
+ g_message ("SIGNALLING: unable to unhold - hold state changed to "
+ "pending hold");
+ tp_svc_channel_interface_hold_emit_hold_state_changed (self,
+ self->priv->hold_state, self->priv->hold_state_reason);
+ /* hold again */
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ simulate_hold, g_object_ref (self),
+ g_object_unref);
+ return FALSE;
+}
+
+static void
+hold_get_hold_state (TpSvcChannelInterfaceHold *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+
+ tp_svc_channel_interface_hold_return_from_get_hold_state (context,
+ self->priv->hold_state, self->priv->hold_state_reason);
+}
+
+static void
+hold_request_hold (TpSvcChannelInterfaceHold *iface,
+ gboolean hold,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ const gchar *peer;
+ GSourceFunc callback;
+
+ if ((hold && self->priv->hold_state == TP_LOCAL_HOLD_STATE_HELD) ||
+ (!hold && self->priv->hold_state == TP_LOCAL_HOLD_STATE_UNHELD))
+ {
+ tp_svc_channel_interface_hold_return_from_request_hold (context);
+ return;
+ }
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ if (!hold && strstr (peer, "(no unhold)") != NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "unable to unhold");
+ goto error;
+ }
+
+ self->priv->hold_state_reason = TP_LOCAL_HOLD_STATE_REASON_REQUESTED;
+
+ if (hold)
+ {
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_HOLD;
+ callback = simulate_hold;
+ }
+ else
+ {
+ self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_UNHOLD;
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+
+ if (strstr (peer, "(inability to unhold)") != NULL)
+ {
+ callback = simulate_inability_to_unhold;
+ }
+ else
+ {
+ callback = simulate_unhold;
+ }
+ }
+
+ g_message ("SIGNALLING: hold state changed to pending %s",
+ (hold ? "hold" : "unhold"));
+ tp_svc_channel_interface_hold_emit_hold_state_changed (iface,
+ self->priv->hold_state, self->priv->hold_state_reason);
+
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ callback, g_object_ref (self),
+ g_object_unref);
+
+ tp_svc_channel_interface_hold_return_from_request_hold (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+
+void
+hold_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelInterfaceHoldClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_interface_hold_implement_##x (klass, hold_##x)
+ IMPLEMENT (get_hold_state);
+ IMPLEMENT (request_hold);
+#undef IMPLEMENT
+}
+
+static void
+dtmf_start_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id,
+ guchar event,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ ExampleCallableMediaStream *stream = g_hash_table_lookup (self->priv->streams,
+ GUINT_TO_POINTER (stream_id));
+ GError *error = NULL;
+ guint media_type;
+
+ if (stream == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No stream with ID %u in this channel", stream_id);
+ goto error;
+ }
+
+ g_object_get (G_OBJECT (stream), "type", &media_type, NULL);
+ if (media_type != TP_MEDIA_STREAM_TYPE_AUDIO)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "DTMF is only supported by audio streams");
+ goto error;
+ }
+
+ tp_svc_channel_interface_dtmf_return_from_start_tone (context);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+dtmf_stop_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id,
+ DBusGMethodInvocation *context)
+{
+ ExampleCallableMediaChannel *self = EXAMPLE_CALLABLE_MEDIA_CHANNEL (iface);
+ ExampleCallableMediaStream *stream = g_hash_table_lookup (self->priv->streams,
+ GUINT_TO_POINTER (stream_id));
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ const gchar *peer;
+ guint media_type;
+
+ if (stream == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No stream with ID %u in this channel", stream_id);
+ goto error;
+ }
+
+ g_object_get (G_OBJECT (stream), "type", &media_type, NULL);
+ if (media_type != TP_MEDIA_STREAM_TYPE_AUDIO)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "DTMF is only supported by audio streams");
+ goto error;
+ }
+
+ peer = tp_handle_inspect (contact_repo, self->priv->handle);
+ if (strstr (peer, "(no continuous tone)") != NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Continuous tones are not supported by this stream");
+ goto error;
+ }
+
+ tp_svc_channel_interface_dtmf_return_from_stop_tone (context);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+dtmf_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelInterfaceDTMFClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_interface_dtmf_implement_##x (klass, dtmf_##x)
+ IMPLEMENT (start_tone);
+ IMPLEMENT (stop_tone);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/callable/media-channel.h b/qt4/tests/lib/glib/callable/media-channel.h
new file mode 100644
index 000000000..428370d46
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/media-channel.h
@@ -0,0 +1,74 @@
+/*
+ * media-channel.h - header for an example channel
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_MEDIA_CHANNEL_H__
+#define __EXAMPLE_CALLABLE_MEDIA_CHANNEL_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableMediaChannel ExampleCallableMediaChannel;
+typedef struct _ExampleCallableMediaChannelPrivate
+ ExampleCallableMediaChannelPrivate;
+
+typedef struct _ExampleCallableMediaChannelClass
+ ExampleCallableMediaChannelClass;
+typedef struct _ExampleCallableMediaChannelClassPrivate
+ ExampleCallableMediaChannelClassPrivate;
+
+GType example_callable_media_channel_get_type (void);
+
+#define EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL \
+ (example_callable_media_channel_get_type ())
+#define EXAMPLE_CALLABLE_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL, \
+ ExampleCallableMediaChannel))
+#define EXAMPLE_CALLABLE_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL, \
+ ExampleCallableMediaChannelClass))
+#define EXAMPLE_IS_CALLABLE_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL))
+#define EXAMPLE_IS_CALLABLE_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL))
+#define EXAMPLE_CALLABLE_MEDIA_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL, \
+ ExampleCallableMediaChannelClass))
+
+struct _ExampleCallableMediaChannelClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+
+ ExampleCallableMediaChannelClassPrivate *priv;
+};
+
+struct _ExampleCallableMediaChannel {
+ GObject parent;
+ TpGroupMixin group;
+
+ ExampleCallableMediaChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/callable/media-manager.c b/qt4/tests/lib/glib/callable/media-manager.c
new file mode 100644
index 000000000..b1646ede8
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/media-manager.c
@@ -0,0 +1,496 @@
+/*
+ * media-manager.c - an example channel manager for StreamedMedia calls.
+ * This channel manager emulates a protocol like XMPP Jingle, where you can
+ * make several simultaneous calls to the same or different contacts.
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "media-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "media-channel.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCallableMediaManager,
+ example_callable_media_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleCallableMediaManagerPrivate
+{
+ TpBaseConnection *conn;
+ guint simulation_delay;
+
+ /* Map from reffed ExampleCallableMediaChannel to the same pointer; used as a
+ * set.
+ */
+ GHashTable *channels;
+
+ /* Next channel will be ("MediaChannel%u", next_channel_index) */
+ guint next_channel_index;
+
+ gulong status_changed_id;
+ gulong available_id;
+};
+
+static void
+example_callable_media_manager_init (ExampleCallableMediaManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER,
+ ExampleCallableMediaManagerPrivate);
+
+ self->priv->conn = NULL;
+ self->priv->channels = g_hash_table_new_full (NULL, NULL, g_object_unref,
+ NULL);
+ self->priv->status_changed_id = 0;
+ self->priv->available_id = 0;
+}
+
+static void
+example_callable_media_manager_close_all (ExampleCallableMediaManager *self)
+{
+ if (self->priv->channels != NULL)
+ {
+ GHashTable *tmp = self->priv->channels;
+
+ self->priv->channels = NULL;
+
+ g_hash_table_unref (tmp);
+ }
+
+ if (self->priv->available_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->available_id);
+ self->priv->available_id = 0;
+ }
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+
+ example_callable_media_manager_close_all (self);
+ g_assert (self->priv->channels == NULL);
+
+ ((GObjectClass *) example_callable_media_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * channel manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleCallableMediaManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ example_callable_media_manager_close_all (self);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static ExampleCallableMediaChannel *new_channel (
+ ExampleCallableMediaManager *self, TpHandle handle, TpHandle initiator,
+ gpointer request_token, gboolean initial_audio, gboolean initial_video);
+
+static gboolean
+simulate_incoming_call_cb (gpointer p)
+{
+ ExampleCallableMediaManager *self = p;
+ TpHandleRepoIface *contact_repo;
+ TpHandle caller;
+
+ /* do nothing if we've been disconnected while waiting for the contact to
+ * call us */
+ if (self->priv->available_id == 0)
+ return FALSE;
+
+ /* We're called by someone whose ID on the IM service is "caller" */
+ contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ caller = tp_handle_ensure (contact_repo, "caller", NULL, NULL);
+
+ new_channel (self, caller, caller, NULL, TRUE, FALSE);
+
+ return FALSE;
+}
+
+/* Whenever our presence changes from away to available, and whenever our
+ * presence message changes while remaining available, simulate a call from
+ * a contact */
+static void
+available_cb (GObject *conn G_GNUC_UNUSED,
+ const gchar *message,
+ ExampleCallableMediaManager *self)
+{
+ g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay,
+ simulate_incoming_call_cb, g_object_ref (self), g_object_unref);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_callable_media_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+
+ self->priv->available_id = g_signal_connect (self->priv->conn,
+ "available", (GCallback) available_cb, self);
+}
+
+static void
+example_callable_media_manager_class_init (
+ ExampleCallableMediaManagerClass *klass)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ param_spec = g_param_spec_object ("connection", "Connection object",
+ "The connection that owns this channel manager",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableMediaManagerPrivate));
+}
+
+static void
+example_callable_media_manager_foreach_channel (
+ TpChannelManager *iface,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleCallableMediaManager *self = EXAMPLE_CALLABLE_MEDIA_MANAGER (iface);
+ GHashTableIter iter;
+ gpointer chan;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &chan, NULL))
+ callback (chan, user_data);
+}
+
+static void
+channel_closed_cb (ExampleCallableMediaChannel *chan,
+ ExampleCallableMediaManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->channels != NULL)
+ g_hash_table_remove (self->priv->channels, chan);
+}
+
+static ExampleCallableMediaChannel *
+new_channel (ExampleCallableMediaManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token,
+ gboolean initial_audio,
+ gboolean initial_video)
+{
+ ExampleCallableMediaChannel *chan;
+ gchar *object_path;
+ GSList *requests = NULL;
+
+ /* FIXME: This could potentially wrap around, but only after 4 billion
+ * calls, which is probably plenty. */
+ object_path = g_strdup_printf ("%s/MediaChannel%u",
+ self->priv->conn->object_path, self->priv->next_channel_index++);
+
+ chan = g_object_new (EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ "connection", self->priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ "initiator-handle", initiator,
+ "requested", (self->priv->conn->self_handle == initiator),
+ "simulation-delay", self->priv->simulation_delay,
+ "initial-audio", initial_audio,
+ "initial-video", initial_video,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (channel_closed_cb), self);
+
+ g_hash_table_insert (self->priv->channels, chan, chan);
+
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+ g_slist_free (requests);
+
+ return chan;
+}
+
+static const gchar * const fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ NULL
+};
+
+static const gchar * const allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ NULL
+};
+
+static void
+example_callable_media_manager_foreach_channel_class (
+ TpChannelManager *manager,
+ TpChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+ NULL);
+
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_callable_media_manager_request (ExampleCallableMediaManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandle handle;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
+ {
+ return FALSE;
+ }
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ fixed_properties, allowed_properties, &error))
+ {
+ goto error;
+ }
+
+ if (handle == self->priv->conn->self_handle)
+ {
+ /* In protocols with a concept of multiple "resources" signed in to
+ * one account (XMPP, and possibly MSN) it is technically possible to
+ * call yourself - e.g. if you're signed in on two PCs, you can call one
+ * from the other. For simplicity, this example simulates a protocol
+ * where this is not the case.
+ */
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "In this protocol, you can't call yourself");
+ goto error;
+ }
+
+ if (!require_new)
+ {
+ /* see if we're already calling that handle */
+ GHashTableIter iter;
+ gpointer chan;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &chan, NULL))
+ {
+ guint its_handle;
+
+ g_object_get (chan,
+ "handle", &its_handle,
+ NULL);
+
+ if (its_handle == handle)
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ return TRUE;
+ }
+ }
+ }
+
+ new_channel (self, handle, self->priv->conn->self_handle,
+ request_token, FALSE, FALSE);
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+example_callable_media_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_callable_media_manager_request (
+ EXAMPLE_CALLABLE_MEDIA_MANAGER (manager),
+ request_token, request_properties, TRUE);
+}
+
+static gboolean
+example_callable_media_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_callable_media_manager_request (
+ EXAMPLE_CALLABLE_MEDIA_MANAGER (manager),
+ request_token, request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_callable_media_manager_foreach_channel;
+ iface->foreach_channel_class =
+ example_callable_media_manager_foreach_channel_class;
+ iface->create_channel = example_callable_media_manager_create_channel;
+ iface->ensure_channel = example_callable_media_manager_ensure_channel;
+ /* In this channel manager, RequestChannel is not supported (it's new
+ * code so there's no reason to be backwards compatible). The requirements
+ * for RequestChannel are somewhat complicated for backwards compatibility
+ * reasons: see telepathy-gabble or
+ * http://telepathy.freedesktop.org/wiki/Requesting%20StreamedMedia%20channels
+ * for the gory details. */
+ iface->request_channel = NULL;
+}
diff --git a/qt4/tests/lib/glib/callable/media-manager.h b/qt4/tests/lib/glib/callable/media-manager.h
new file mode 100644
index 000000000..5be239e6e
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/media-manager.h
@@ -0,0 +1,71 @@
+/*
+ * media-manager.h - header for an example channel manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_MEDIA_MANAGER_H__
+#define __EXAMPLE_CALLABLE_MEDIA_MANAGER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableMediaManager ExampleCallableMediaManager;
+typedef struct _ExampleCallableMediaManagerPrivate
+ ExampleCallableMediaManagerPrivate;
+
+typedef struct _ExampleCallableMediaManagerClass
+ ExampleCallableMediaManagerClass;
+typedef struct _ExampleCallableMediaManagerClassPrivate
+ ExampleCallableMediaManagerClassPrivate;
+
+struct _ExampleCallableMediaManagerClass {
+ GObjectClass parent_class;
+
+ ExampleCallableMediaManagerClassPrivate *priv;
+};
+
+struct _ExampleCallableMediaManager {
+ GObject parent;
+
+ ExampleCallableMediaManagerPrivate *priv;
+};
+
+GType example_callable_media_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER \
+ (example_callable_media_manager_get_type ())
+#define EXAMPLE_CALLABLE_MEDIA_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER, \
+ ExampleCallableMediaManager))
+#define EXAMPLE_CALLABLE_MEDIA_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER, \
+ ExampleCallableMediaManagerClass))
+#define EXAMPLE_IS_CALLABLE_MEDIA_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER))
+#define EXAMPLE_IS_CALLABLE_MEDIA_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER))
+#define EXAMPLE_CALLABLE_MEDIA_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_MANAGER, \
+ ExampleCallableMediaManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/callable/media-stream.c b/qt4/tests/lib/glib/callable/media-stream.c
new file mode 100644
index 000000000..4c19e108b
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/media-stream.c
@@ -0,0 +1,650 @@
+/*
+ * media-stream.c - a stream in a streamed media call.
+ *
+ * In connection managers with MediaSignalling, this object would be a D-Bus
+ * object in its own right. In this CM, MediaSignalling is not used, and this
+ * object just represents internal state of the MediaChannel.
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "media-stream.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/gtypes.h>
+
+#include "media-channel.h"
+
+G_DEFINE_TYPE (ExampleCallableMediaStream,
+ example_callable_media_stream,
+ G_TYPE_OBJECT)
+
+enum
+{
+ PROP_CHANNEL = 1,
+ PROP_ID,
+ PROP_HANDLE,
+ PROP_TYPE,
+ PROP_STATE,
+ PROP_PENDING_SEND,
+ PROP_DIRECTION,
+ PROP_STREAM_INFO,
+ PROP_SIMULATION_DELAY,
+ PROP_LOCALLY_REQUESTED,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_REMOVED,
+ SIGNAL_DIRECTION_CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+struct _ExampleCallableMediaStreamPrivate
+{
+ TpBaseConnection *conn;
+ ExampleCallableMediaChannel *channel;
+ guint id;
+ TpHandle handle;
+ TpMediaStreamType type;
+ TpMediaStreamState state;
+ TpMediaStreamDirection direction;
+ TpMediaStreamPendingSend pending_send;
+
+ guint simulation_delay;
+
+ gulong call_terminated_id;
+
+ guint connected_event_id;
+
+ gboolean locally_requested;
+ gboolean removed;
+};
+
+static void
+example_callable_media_stream_init (ExampleCallableMediaStream *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM,
+ ExampleCallableMediaStreamPrivate);
+
+ /* start off directionless */
+ self->priv->direction = TP_MEDIA_STREAM_DIRECTION_NONE;
+ self->priv->pending_send = 0;
+ self->priv->state = TP_MEDIA_STREAM_STATE_DISCONNECTED;
+}
+
+static void
+call_terminated_cb (ExampleCallableMediaChannel *channel,
+ ExampleCallableMediaStream *self)
+{
+ g_signal_handler_disconnect (channel, self->priv->call_terminated_id);
+ self->priv->call_terminated_id = 0;
+ example_callable_media_stream_close (self);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_callable_media_stream_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_object_get (self->priv->channel,
+ "connection", &self->priv->conn,
+ NULL);
+ self->priv->call_terminated_id = g_signal_connect (self->priv->channel,
+ "call-terminated", G_CALLBACK (call_terminated_cb), self);
+
+ if (self->priv->handle != 0)
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_uint (value, self->priv->id);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+
+ case PROP_TYPE:
+ g_value_set_uint (value, self->priv->type);
+ break;
+
+ case PROP_STATE:
+ g_value_set_uint (value, self->priv->state);
+ break;
+
+ case PROP_PENDING_SEND:
+ g_value_set_uint (value, self->priv->pending_send);
+ break;
+
+ case PROP_DIRECTION:
+ g_value_set_uint (value, self->priv->direction);
+ break;
+
+ case PROP_CHANNEL:
+ g_value_set_object (value, self->priv->channel);
+ break;
+
+ case PROP_STREAM_INFO:
+ {
+ GValueArray *va = g_value_array_new (6);
+ guint i;
+
+ for (i = 0; i < 6; i++)
+ {
+ g_value_array_append (va, NULL);
+ g_value_init (va->values + i, G_TYPE_UINT);
+ }
+
+ g_value_set_uint (va->values + 0, self->priv->id);
+ g_value_set_uint (va->values + 1, self->priv->handle);
+ g_value_set_uint (va->values + 2, self->priv->type);
+ g_value_set_uint (va->values + 3, self->priv->state);
+ g_value_set_uint (va->values + 4, self->priv->direction);
+ g_value_set_uint (va->values + 5, self->priv->pending_send);
+
+ g_value_take_boxed (value, va);
+ }
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ case PROP_LOCALLY_REQUESTED:
+ g_value_set_boolean (value, self->priv->locally_requested);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ self->priv->id = g_value_get_uint (value);
+ break;
+
+ case PROP_HANDLE:
+ self->priv->handle = g_value_get_uint (value);
+ break;
+
+ case PROP_TYPE:
+ self->priv->type = g_value_get_uint (value);
+ break;
+
+ case PROP_CHANNEL:
+ g_assert (self->priv->channel == NULL);
+ self->priv->channel = g_value_dup_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ case PROP_LOCALLY_REQUESTED:
+ self->priv->locally_requested = g_value_get_boolean (value);
+
+ if (self->priv->locally_requested)
+ {
+ example_callable_media_stream_change_direction (self,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, NULL);
+ }
+ else
+ {
+ example_callable_media_stream_receive_direction_request (self,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+ }
+
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ example_callable_media_stream_close (self);
+
+ if (self->priv->handle != 0)
+ {
+ tp_handle_unref (contact_repo, self->priv->handle);
+ self->priv->handle = 0;
+ }
+
+ if (self->priv->channel != NULL)
+ {
+ if (self->priv->call_terminated_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->channel,
+ self->priv->call_terminated_id);
+ self->priv->call_terminated_id = 0;
+ }
+
+ g_object_unref (self->priv->channel);
+ self->priv->channel = NULL;
+ }
+
+ if (self->priv->conn != NULL)
+ {
+ g_object_unref (self->priv->conn);
+ self->priv->conn = NULL;
+ }
+
+ ((GObjectClass *) example_callable_media_stream_parent_class)->dispose (object);
+}
+
+static void
+example_callable_media_stream_class_init (ExampleCallableMediaStreamClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleCallableMediaStreamPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+
+ param_spec = g_param_spec_object ("channel", "ExampleCallableMediaChannel",
+ "Media channel that owns this stream",
+ EXAMPLE_TYPE_CALLABLE_MEDIA_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CHANNEL, param_spec);
+
+ param_spec = g_param_spec_uint ("id", "Stream ID",
+ "ID of this stream",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("handle", "Peer's TpHandle",
+ "The handle with which this stream communicates or 0 if not applicable",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HANDLE, param_spec);
+
+ param_spec = g_param_spec_uint ("type", "TpMediaStreamType",
+ "Media stream type",
+ 0, NUM_TP_MEDIA_STREAM_TYPES - 1, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TYPE, param_spec);
+
+ param_spec = g_param_spec_uint ("state", "TpMediaStreamState",
+ "Media stream connection state",
+ 0, NUM_TP_MEDIA_STREAM_STATES - 1, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ param_spec = g_param_spec_uint ("direction", "TpMediaStreamDirection",
+ "Media stream direction",
+ 0, NUM_TP_MEDIA_STREAM_DIRECTIONS - 1, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DIRECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("pending-send", "TpMediaStreamPendingSend",
+ "Requested media stream directions pending approval",
+ 0,
+ TP_MEDIA_STREAM_PENDING_LOCAL_SEND | TP_MEDIA_STREAM_PENDING_REMOTE_SEND,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PENDING_SEND, param_spec);
+
+ param_spec = g_param_spec_boxed ("stream-info", "Stream info",
+ "6-entry GValueArray as returned by ListStreams and RequestStreams",
+ TP_STRUCT_TYPE_MEDIA_STREAM_INFO,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM_INFO, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("locally-requested", "Locally requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCALLY_REQUESTED,
+ param_spec);
+
+ signals[SIGNAL_REMOVED] = g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_DIRECTION_CHANGED] = g_signal_new ("direction-changed",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+void
+example_callable_media_stream_close (ExampleCallableMediaStream *self)
+{
+ if (!self->priv->removed)
+ {
+ self->priv->removed = TRUE;
+
+ g_message ("Sending to server: Closing stream %u",
+ self->priv->id);
+
+ if (self->priv->connected_event_id != 0)
+ {
+ g_source_remove (self->priv->connected_event_id);
+ }
+
+ /* this has to come last, because the MediaChannel may unref us in
+ * response to the removed signal */
+ g_signal_emit (self, signals[SIGNAL_REMOVED], 0);
+ }
+}
+
+void
+example_callable_media_stream_accept_proposed_direction (
+ ExampleCallableMediaStream *self)
+{
+ if (self->priv->removed ||
+ !(self->priv->pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND))
+ return;
+
+ g_message ("SIGNALLING: send: OK, I'll send you media on stream %u",
+ self->priv->id);
+
+ self->priv->direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
+
+ g_signal_emit (self, signals[SIGNAL_DIRECTION_CHANGED], 0);
+}
+
+void
+example_callable_media_stream_simulate_contact_agreed_to_send (
+ ExampleCallableMediaStream *self)
+{
+ if (self->priv->removed ||
+ !(self->priv->pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND))
+ return;
+
+ g_message ("SIGNALLING: receive: OK, I'll send you media on stream %u",
+ self->priv->id);
+
+ self->priv->direction |= TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
+
+ g_signal_emit (self, signals[SIGNAL_DIRECTION_CHANGED], 0);
+}
+
+static gboolean
+simulate_contact_agreed_to_send_cb (gpointer p)
+{
+ example_callable_media_stream_simulate_contact_agreed_to_send (p);
+ return FALSE;
+}
+
+gboolean
+example_callable_media_stream_change_direction (
+ ExampleCallableMediaStream *self,
+ TpMediaStreamDirection direction,
+ GError **error)
+{
+ gboolean sending =
+ ((self->priv->direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0);
+ gboolean receiving =
+ ((self->priv->direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean want_to_send =
+ ((direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0);
+ gboolean want_to_receive =
+ ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean pending_remote_send =
+ ((self->priv->pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0);
+ gboolean pending_local_send =
+ ((self->priv->pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0);
+ gboolean changed = FALSE;
+
+ if (want_to_send)
+ {
+ if (!sending)
+ {
+ if (pending_local_send)
+ {
+ g_message ("SIGNALLING: send: I will now send you media on "
+ "stream %u", self->priv->id);
+ }
+
+ g_message ("MEDIA: Sending media to peer for stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
+ }
+ }
+ else
+ {
+ if (sending)
+ {
+ g_message ("SIGNALLING: send: I will no longer send you media on "
+ "stream %u", self->priv->id);
+ g_message ("MEDIA: No longer sending media to peer for stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->direction &= ~TP_MEDIA_STREAM_DIRECTION_SEND;
+ }
+ else if (pending_local_send)
+ {
+ g_message ("SIGNALLING: send: No, I refuse to send you media on "
+ "stream %u", self->priv->id);
+ changed = TRUE;
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
+ }
+ }
+
+ if (want_to_receive)
+ {
+ if (!receiving && !pending_remote_send)
+ {
+ g_message ("SIGNALLING: send: Please start sending me stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->pending_send |= TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
+ g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay,
+ simulate_contact_agreed_to_send_cb, g_object_ref (self),
+ g_object_unref);
+ }
+ }
+ else
+ {
+ if (receiving)
+ {
+ g_message ("SIGNALLING: send: Please stop sending me stream %u",
+ self->priv->id);
+ g_message ("MEDIA: Suppressing output of stream %u",
+ self->priv->id);
+ changed = TRUE;
+ self->priv->direction &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ }
+ }
+
+ if (changed)
+ g_signal_emit (self, signals[SIGNAL_DIRECTION_CHANGED], 0);
+
+ return TRUE;
+}
+
+static gboolean
+simulate_stream_connected_cb (gpointer p)
+{
+ ExampleCallableMediaStream *self = EXAMPLE_CALLABLE_MEDIA_STREAM (p);
+
+ g_message ("MEDIA: stream connected");
+ self->priv->state = TP_MEDIA_STREAM_STATE_CONNECTED;
+ g_object_notify ((GObject *) self, "state");
+
+ return FALSE;
+}
+
+void
+example_callable_media_stream_connect (ExampleCallableMediaStream *self)
+{
+ /* if already trying to connect, do nothing */
+ if (self->priv->connected_event_id != 0)
+ return;
+
+ /* simulate it taking a short time to connect */
+ self->priv->connected_event_id = g_timeout_add (self->priv->simulation_delay,
+ simulate_stream_connected_cb, self);
+}
+
+void
+example_callable_media_stream_receive_direction_request (
+ ExampleCallableMediaStream *self,
+ TpMediaStreamDirection direction)
+{
+ /* The remote user wants to change the direction of this stream to
+ * @direction. Shall we let him? */
+ gboolean sending =
+ ((self->priv->direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0);
+ gboolean receiving =
+ ((self->priv->direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean send_requested =
+ ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean receive_requested =
+ ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 0);
+ gboolean pending_remote_send =
+ ((self->priv->pending_send & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0);
+ gboolean pending_local_send =
+ ((self->priv->pending_send & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0);
+ gboolean changed = FALSE;
+
+ if (send_requested)
+ {
+ g_message ("SIGNALLING: receive: Please start sending me stream %u",
+ self->priv->id);
+
+ if (!sending)
+ {
+ /* ask the user for permission */
+ self->priv->pending_send |= TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
+ changed = TRUE;
+ }
+ else
+ {
+ /* nothing to do, we're already sending on that stream */
+ }
+ }
+ else
+ {
+ g_message ("SIGNALLING: receive: Please stop sending me stream %u",
+ self->priv->id);
+ g_message ("SIGNALLING: send: OK, not sending stream %u",
+ self->priv->id);
+
+ if (sending)
+ {
+ g_message ("MEDIA: No longer sending media to peer for stream %u",
+ self->priv->id);
+ self->priv->direction &= ~TP_MEDIA_STREAM_DIRECTION_SEND;
+ changed = TRUE;
+ }
+ else if (pending_local_send)
+ {
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
+ changed = TRUE;
+ }
+ else
+ {
+ /* nothing to do, we're not sending on that stream anyway */
+ }
+ }
+
+ if (receive_requested)
+ {
+ g_message ("SIGNALLING: receive: I will now send you media on stream %u",
+ self->priv->id);
+
+ if (!receiving)
+ {
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
+ self->priv->direction |= TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ changed = TRUE;
+ }
+ }
+ else
+ {
+ if (pending_remote_send)
+ {
+ g_message ("SIGNALLING: receive: No, I refuse to send you media on "
+ "stream %u", self->priv->id);
+ self->priv->pending_send &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
+ changed = TRUE;
+ }
+ else if (receiving)
+ {
+ g_message ("SIGNALLING: receive: I will no longer send you media on "
+ "stream %u", self->priv->id);
+ self->priv->direction &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
+ changed = TRUE;
+ }
+ }
+
+ if (changed)
+ g_signal_emit (self, signals[SIGNAL_DIRECTION_CHANGED], 0);
+}
diff --git a/qt4/tests/lib/glib/callable/media-stream.h b/qt4/tests/lib/glib/callable/media-stream.h
new file mode 100644
index 000000000..e7ec04804
--- /dev/null
+++ b/qt4/tests/lib/glib/callable/media-stream.h
@@ -0,0 +1,88 @@
+/*
+ * media-stream.h - header for an example stream
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __EXAMPLE_CALLABLE_MEDIA_STREAM_H__
+#define __EXAMPLE_CALLABLE_MEDIA_STREAM_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/enums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCallableMediaStream ExampleCallableMediaStream;
+typedef struct _ExampleCallableMediaStreamPrivate
+ ExampleCallableMediaStreamPrivate;
+
+typedef struct _ExampleCallableMediaStreamClass
+ ExampleCallableMediaStreamClass;
+typedef struct _ExampleCallableMediaStreamClassPrivate
+ ExampleCallableMediaStreamClassPrivate;
+
+GType example_callable_media_stream_get_type (void);
+
+#define EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM \
+ (example_callable_media_stream_get_type ())
+#define EXAMPLE_CALLABLE_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM, \
+ ExampleCallableMediaStream))
+#define EXAMPLE_CALLABLE_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM, \
+ ExampleCallableMediaStreamClass))
+#define EXAMPLE_IS_CALLABLE_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM))
+#define EXAMPLE_IS_CALLABLE_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM))
+#define EXAMPLE_CALLABLE_MEDIA_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALLABLE_MEDIA_STREAM, \
+ ExampleCallableMediaStreamClass))
+
+struct _ExampleCallableMediaStreamClass {
+ GObjectClass parent_class;
+
+ ExampleCallableMediaStreamClassPrivate *priv;
+};
+
+struct _ExampleCallableMediaStream {
+ GObject parent;
+
+ ExampleCallableMediaStreamPrivate *priv;
+};
+
+void example_callable_media_stream_close (ExampleCallableMediaStream *self);
+gboolean example_callable_media_stream_change_direction (
+ ExampleCallableMediaStream *self, TpMediaStreamDirection direction,
+ GError **error);
+void example_callable_media_stream_accept_proposed_direction (
+ ExampleCallableMediaStream *self);
+void example_callable_media_stream_connect (ExampleCallableMediaStream *self);
+
+/* This controls receiving emulated network events, so it wouldn't exist in
+ * a real connection manager */
+void example_callable_media_stream_simulate_contact_agreed_to_send (
+ ExampleCallableMediaStream *self);
+
+void example_callable_media_stream_receive_direction_request (
+ ExampleCallableMediaStream *self, TpMediaStreamDirection direction);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contact-list-manager.c b/qt4/tests/lib/glib/contact-list-manager.c
new file mode 100644
index 000000000..0da0509b8
--- /dev/null
+++ b/qt4/tests/lib/glib/contact-list-manager.c
@@ -0,0 +1,745 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2010 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contact-list-manager.h"
+
+#include <string.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+struct _TestContactListManagerPrivate
+{
+ TpBaseConnection *conn;
+
+ gulong status_changed_id;
+
+ /* TpHandle => ContactDetails */
+ GHashTable *contact_details;
+
+ TpHandleRepoIface *contact_repo;
+ TpHandleRepoIface *group_repo;
+ TpHandleSet *groups;
+};
+
+static void contact_groups_iface_init (TpContactGroupListInterface *iface);
+static void mutable_contact_groups_iface_init (
+ TpMutableContactGroupListInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (TestContactListManager, test_contact_list_manager,
+ TP_TYPE_BASE_CONTACT_LIST,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CONTACT_GROUP_LIST,
+ contact_groups_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_GROUP_LIST,
+ mutable_contact_groups_iface_init))
+
+typedef struct {
+ TpSubscriptionState subscribe;
+ TpSubscriptionState publish;
+ gchar *publish_request;
+ TpHandleSet *groups;
+
+ TpHandle handle;
+ TpHandleRepoIface *contact_repo;
+} ContactDetails;
+
+static void
+contact_detail_destroy (gpointer p)
+{
+ ContactDetails *d = p;
+
+ g_free (d->publish_request);
+ tp_handle_set_destroy (d->groups);
+ tp_handle_unref (d->contact_repo, d->handle);
+
+ g_slice_free (ContactDetails, d);
+}
+
+static ContactDetails *
+lookup_contact (TestContactListManager *self,
+ TpHandle handle)
+{
+ return g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (handle));
+}
+
+static ContactDetails *
+ensure_contact (TestContactListManager *self,
+ TpHandle handle)
+{
+ ContactDetails *d = lookup_contact (self, handle);
+
+ if (d == NULL)
+ {
+ d = g_slice_new0 (ContactDetails);
+ d->subscribe = TP_SUBSCRIPTION_STATE_NO;
+ d->publish = TP_SUBSCRIPTION_STATE_NO;
+ d->publish_request = NULL;
+ d->groups = tp_handle_set_new (self->priv->group_repo);
+ d->handle = handle;
+ d->contact_repo = self->priv->contact_repo;
+ tp_handle_ref (d->contact_repo, d->handle);
+
+ g_hash_table_insert (self->priv->contact_details,
+ GUINT_TO_POINTER (handle), d);
+ }
+
+ return d;
+}
+
+static void
+test_contact_list_manager_init (TestContactListManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TEST_TYPE_CONTACT_LIST_MANAGER, TestContactListManagerPrivate);
+
+ self->priv->contact_details = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, contact_detail_destroy);
+}
+
+static void
+close_all (TestContactListManager *self)
+{
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+ tp_clear_pointer (&self->priv->contact_details, g_hash_table_unref);
+ tp_clear_pointer (&self->priv->groups, tp_handle_set_destroy);
+}
+
+static void
+dispose (GObject *object)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (object);
+
+ close_all (self);
+
+ ((GObjectClass *) test_contact_list_manager_parent_class)->dispose (
+ object);
+}
+
+static TpHandleSet *
+contact_list_dup_contacts (TpBaseContactList *base)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (base);
+ TpHandleSet *set;
+ GHashTableIter iter;
+ gpointer k, v;
+
+ set = tp_handle_set_new (self->priv->contact_repo);
+
+ g_hash_table_iter_init (&iter, self->priv->contact_details);
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ ContactDetails *d = v;
+
+ /* add all the interesting items */
+ if (d->subscribe != TP_SUBSCRIPTION_STATE_NO ||
+ d->publish != TP_SUBSCRIPTION_STATE_NO)
+ tp_handle_set_add (set, GPOINTER_TO_UINT (k));
+ }
+
+ return set;
+}
+
+static void
+contact_list_dup_states (TpBaseContactList *base,
+ TpHandle contact,
+ TpSubscriptionState *subscribe,
+ TpSubscriptionState *publish,
+ gchar **publish_request)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (base);
+ ContactDetails *d = lookup_contact (self, contact);
+
+ if (d == NULL)
+ {
+ if (subscribe != NULL)
+ *subscribe = TP_SUBSCRIPTION_STATE_NO;
+
+ if (publish != NULL)
+ *publish = TP_SUBSCRIPTION_STATE_NO;
+
+ if (publish_request != NULL)
+ *publish_request = NULL;
+ }
+ else
+ {
+ if (subscribe != NULL)
+ *subscribe = d->subscribe;
+
+ if (publish != NULL)
+ *publish = d->publish;
+
+ if (publish_request != NULL)
+ *publish_request = g_strdup (d->publish_request);
+ }
+}
+
+static GStrv
+contact_list_dup_groups (TpBaseContactList *base)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (base);
+ GPtrArray *ret;
+
+ if (self->priv->groups != NULL)
+ {
+ TpIntSetFastIter iter;
+ TpHandle group;
+
+ ret = g_ptr_array_sized_new (tp_handle_set_size (self->priv->groups) + 1);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->groups));
+ while (tp_intset_fast_iter_next (&iter, &group))
+ {
+ g_ptr_array_add (ret, g_strdup (tp_handle_inspect (
+ self->priv->group_repo, group)));
+ }
+ }
+ else
+ {
+ ret = g_ptr_array_sized_new (1);
+ }
+
+ g_ptr_array_add (ret, NULL);
+
+ return (GStrv) g_ptr_array_free (ret, FALSE);
+}
+
+static GStrv
+contact_list_dup_contact_groups (TpBaseContactList *base,
+ TpHandle contact)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (base);
+ ContactDetails *d = lookup_contact (self, contact);
+ GPtrArray *ret;
+
+ if (d != NULL && d->groups != NULL)
+ {
+ TpIntSetFastIter iter;
+ TpHandle group;
+
+ ret = g_ptr_array_sized_new (tp_handle_set_size (d->groups) + 1);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (d->groups));
+ while (tp_intset_fast_iter_next (&iter, &group))
+ {
+ g_ptr_array_add (ret, g_strdup (tp_handle_inspect (
+ self->priv->group_repo, group)));
+ }
+ }
+ else
+ {
+ ret = g_ptr_array_sized_new (1);
+ }
+
+ g_ptr_array_add (ret, NULL);
+
+ return (GStrv) g_ptr_array_free (ret, FALSE);
+}
+
+static TpHandleSet *
+contact_list_dup_group_members (TpBaseContactList *base,
+ const gchar *group)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (base);
+ TpHandleSet *set;
+ TpHandle group_handle;
+ GHashTableIter iter;
+ gpointer k, v;
+
+ set = tp_handle_set_new (self->priv->contact_repo);
+ group_handle = tp_handle_lookup (self->priv->group_repo, group, NULL, NULL);
+ if (G_UNLIKELY (group_handle == 0))
+ {
+ /* clearly it doesn't have members */
+ return set;
+ }
+
+ g_hash_table_iter_init (&iter, self->priv->contact_details);
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ ContactDetails *d = v;
+
+ if (d->groups != NULL &&
+ tp_handle_set_is_member (d->groups, group_handle))
+ tp_handle_set_add (set, GPOINTER_TO_UINT (k));
+ }
+
+ return set;
+}
+
+static void
+contact_list_set_contact_groups_async (TpBaseContactList *base,
+ TpHandle contact,
+ const gchar * const *names,
+ gsize n,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (base);
+ ContactDetails *d;
+ TpIntset *set, *added_set, *removed_set;
+ GPtrArray *added_names, *removed_names;
+ TpIntSetFastIter iter;
+ TpHandle group_handle;
+ guint i;
+
+ d = ensure_contact (self, contact);
+
+ set = tp_intset_new ();
+ for (i = 0; i < n; i++)
+ {
+ group_handle = tp_handle_ensure (self->priv->group_repo, names[i], NULL, NULL);
+ tp_intset_add (set, group_handle);
+ }
+
+ added_set = tp_intset_difference (set, tp_handle_set_peek (d->groups));
+ added_names = g_ptr_array_sized_new (tp_intset_size (added_set));
+ tp_intset_fast_iter_init (&iter, added_set);
+ while (tp_intset_fast_iter_next (&iter, &group_handle))
+ {
+ g_ptr_array_add (added_names, (gchar *) tp_handle_inspect (
+ self->priv->group_repo, group_handle));
+ }
+ tp_intset_destroy (added_set);
+
+ removed_set = tp_intset_difference (tp_handle_set_peek (d->groups), set);
+ removed_names = g_ptr_array_sized_new (tp_intset_size (removed_set));
+ tp_intset_fast_iter_init (&iter, removed_set);
+ while (tp_intset_fast_iter_next (&iter, &group_handle))
+ {
+ g_ptr_array_add (removed_names, (gchar *) tp_handle_inspect (
+ self->priv->group_repo, group_handle));
+ }
+ tp_intset_destroy (removed_set);
+
+ tp_handle_set_destroy (d->groups);
+ d->groups = tp_handle_set_new_from_intset (self->priv->group_repo, set);
+ tp_intset_destroy (set);
+
+ tp_base_contact_list_one_contact_groups_changed (base, contact,
+ (const gchar * const *) added_names->pdata, added_names->len,
+ (const gchar * const *) removed_names->pdata, removed_names->len);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, contact_list_set_contact_groups_async);
+
+ g_ptr_array_unref (added_names);
+ g_ptr_array_unref (removed_names);
+}
+
+static void
+contact_list_set_group_members_async (TpBaseContactList *base,
+ const gchar *normalized_group,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ simple = g_simple_async_result_new_error ((GObject *) base, callback,
+ user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static void
+contact_list_add_to_group_async (TpBaseContactList *base,
+ const gchar *group,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ simple = g_simple_async_result_new_error ((GObject *) base, callback,
+ user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static void
+contact_list_remove_from_group_async (TpBaseContactList *base,
+ const gchar *group,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ simple = g_simple_async_result_new_error ((GObject *) base, callback,
+ user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static void
+contact_list_remove_group_async (TpBaseContactList *base,
+ const gchar *group,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ simple = g_simple_async_result_new_error ((GObject *) base, callback,
+ user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ TestContactListManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTED:
+ {
+ tp_base_contact_list_set_list_received (TP_BASE_CONTACT_LIST (self));
+ }
+ break;
+
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ close_all (self);
+ }
+ break;
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ TestContactListManager *self = TEST_CONTACT_LIST_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) test_contact_list_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->conn = tp_base_contact_list_get_connection (
+ TP_BASE_CONTACT_LIST (self), NULL);
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", G_CALLBACK (status_changed_cb), self);
+
+ self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_GROUP);
+ self->priv->groups = tp_handle_set_new (self->priv->group_repo);
+}
+
+static void
+contact_groups_iface_init (TpContactGroupListInterface *iface)
+{
+ iface->dup_groups = contact_list_dup_groups;
+ iface->dup_contact_groups = contact_list_dup_contact_groups;
+ iface->dup_group_members = contact_list_dup_group_members;
+}
+
+static void
+mutable_contact_groups_iface_init (
+ TpMutableContactGroupListInterface *iface)
+{
+ iface->set_contact_groups_async = contact_list_set_contact_groups_async;
+ iface->set_group_members_async = contact_list_set_group_members_async;
+ iface->add_to_group_async = contact_list_add_to_group_async;
+ iface->remove_from_group_async = contact_list_remove_from_group_async;
+ iface->remove_group_async = contact_list_remove_group_async;
+}
+
+static void
+test_contact_list_manager_class_init (TestContactListManagerClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ TpBaseContactListClass *base_class =(TpBaseContactListClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (TestContactListManagerPrivate));
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+
+ base_class->dup_states = contact_list_dup_states;
+ base_class->dup_contacts = contact_list_dup_contacts;
+}
+
+void
+test_contact_list_manager_add_to_group (TestContactListManager *self,
+ const gchar *group_name, TpHandle member)
+{
+ TpBaseContactList *base = TP_BASE_CONTACT_LIST (self);
+ ContactDetails *d = ensure_contact (self, member);
+ TpHandle group_handle;
+
+ group_handle = tp_handle_ensure (self->priv->group_repo, group_name, NULL, NULL);
+
+ tp_handle_set_add (d->groups, group_handle);
+ tp_base_contact_list_one_contact_groups_changed (base, member,
+ &group_name, 1, NULL, 0);
+}
+
+void
+test_contact_list_manager_remove_from_group (TestContactListManager *self,
+ const gchar *group_name, TpHandle member)
+{
+ TpBaseContactList *base = TP_BASE_CONTACT_LIST (self);
+ ContactDetails *d = lookup_contact (self, member);
+ TpHandle group_handle;
+
+ if (d == NULL)
+ return;
+
+ group_handle = tp_handle_ensure (self->priv->group_repo, group_name, NULL, NULL);
+
+ tp_handle_set_remove (d->groups, group_handle);
+ tp_base_contact_list_one_contact_groups_changed (base, member,
+ NULL, 0, &group_name, 1);
+}
+
+typedef struct {
+ TestContactListManager *self;
+ TpHandleSet *handles;
+} SelfAndContact;
+
+static SelfAndContact *
+self_and_contact_new (TestContactListManager *self,
+ TpHandleSet *handles)
+{
+ SelfAndContact *ret = g_slice_new0 (SelfAndContact);
+
+ ret->self = g_object_ref (self);
+ ret->handles = tp_handle_set_copy (handles);
+
+ return ret;
+}
+
+static void
+self_and_contact_destroy (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ tp_handle_set_destroy (s->handles);
+ g_object_unref (s->self);
+ g_slice_free (SelfAndContact, s);
+}
+
+static gboolean
+receive_authorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ GArray *handles_array;
+ guint i;
+
+ handles_array = tp_handle_set_to_array (s->handles);
+ for (i = 0; i < handles_array->len; i++)
+ {
+ ContactDetails *d = lookup_contact (s->self,
+ g_array_index (handles_array, TpHandle, i));
+
+ if (d == NULL)
+ continue;
+
+ d->subscribe = TP_SUBSCRIPTION_STATE_YES;
+
+ /* if we're not publishing to them, also pretend they have asked us to do so */
+ if (d->publish != TP_SUBSCRIPTION_STATE_YES)
+ {
+ d->publish = TP_SUBSCRIPTION_STATE_ASK;
+ tp_clear_pointer (&d->publish_request, g_free);
+ d->publish_request = g_strdup ("automatic publish request");
+ }
+ }
+ g_array_unref (handles_array);
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (s->self),
+ s->handles, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+receive_unauthorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ GArray *handles_array;
+ guint i;
+
+ handles_array = tp_handle_set_to_array (s->handles);
+ for (i = 0; i < handles_array->len; i++)
+ {
+ ContactDetails *d = lookup_contact (s->self,
+ g_array_index (handles_array, TpHandle, i));
+
+ if (d == NULL)
+ continue;
+
+ d->subscribe = TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY;
+ }
+ g_array_unref (handles_array);
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (s->self),
+ s->handles, NULL);
+
+ return FALSE;
+}
+
+void
+test_contact_list_manager_request_subscription (TestContactListManager *self,
+ guint n_members, TpHandle *members, const gchar *message)
+{
+ TpHandleSet *handles;
+ guint i;
+ gchar *message_lc;
+
+ handles = tp_handle_set_new (self->priv->contact_repo);
+ for (i = 0; i < n_members; i++)
+ {
+ ContactDetails *d = ensure_contact (self, members[i]);
+
+ if (d->subscribe == TP_SUBSCRIPTION_STATE_YES)
+ continue;
+
+ d->subscribe = TP_SUBSCRIPTION_STATE_ASK;
+ tp_handle_set_add (handles, members[i]);
+ }
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+ NULL);
+
+ message_lc = g_ascii_strdown (message, -1);
+ if (strstr (message_lc, "please") != NULL)
+ {
+ g_idle_add_full (G_PRIORITY_DEFAULT,
+ receive_authorized,
+ self_and_contact_new (self, handles),
+ self_and_contact_destroy);
+ }
+ else if (strstr (message_lc, "no") != NULL)
+ {
+ g_idle_add_full (G_PRIORITY_DEFAULT,
+ receive_unauthorized,
+ self_and_contact_new (self, handles),
+ self_and_contact_destroy);
+ }
+
+ g_free (message_lc);
+ tp_handle_set_destroy (handles);
+}
+
+void
+test_contact_list_manager_unsubscribe (TestContactListManager *self,
+ guint n_members, TpHandle *members)
+{
+ TpHandleSet *handles;
+ guint i;
+
+ handles = tp_handle_set_new (self->priv->contact_repo);
+ for (i = 0; i < n_members; i++)
+ {
+ ContactDetails *d = lookup_contact (self, members[i]);
+
+ if (d == NULL || d->subscribe == TP_SUBSCRIPTION_STATE_NO)
+ continue;
+
+ d->subscribe = TP_SUBSCRIPTION_STATE_NO;
+ tp_handle_set_add (handles, members[i]);
+ }
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+ NULL);
+
+ tp_handle_set_destroy (handles);
+}
+
+void
+test_contact_list_manager_authorize_publication (TestContactListManager *self,
+ guint n_members, TpHandle *members)
+{
+ TpHandleSet *handles;
+ guint i;
+
+ handles = tp_handle_set_new (self->priv->contact_repo);
+ for (i = 0; i < n_members; i++)
+ {
+ ContactDetails *d = lookup_contact (self, members[i]);
+
+ if (d == NULL || d->publish != TP_SUBSCRIPTION_STATE_ASK)
+ continue;
+
+ d->publish = TP_SUBSCRIPTION_STATE_YES;
+ tp_clear_pointer (&d->publish_request, g_free);
+ tp_handle_set_add (handles, members[i]);
+ }
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+ NULL);
+
+ tp_handle_set_destroy (handles);
+}
+
+void
+test_contact_list_manager_unpublish (TestContactListManager *self,
+ guint n_members, TpHandle *members)
+{
+ TpHandleSet *handles;
+ guint i;
+
+ handles = tp_handle_set_new (self->priv->contact_repo);
+ for (i = 0; i < n_members; i++)
+ {
+ ContactDetails *d = lookup_contact (self, members[i]);
+
+ if (d == NULL || d->publish == TP_SUBSCRIPTION_STATE_NO)
+ continue;
+
+ d->publish = TP_SUBSCRIPTION_STATE_NO;
+ tp_clear_pointer (&d->publish_request, g_free);
+ tp_handle_set_add (handles, members[i]);
+ }
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+ NULL);
+
+ tp_handle_set_destroy (handles);
+}
+
+void
+test_contact_list_manager_remove (TestContactListManager *self,
+ guint n_members, TpHandle *members)
+{
+ TpHandleSet *handles;
+ guint i;
+
+ handles = tp_handle_set_new (self->priv->contact_repo);
+ for (i = 0; i < n_members; i++)
+ {
+ ContactDetails *d = lookup_contact (self, members[i]);
+
+ if (d == NULL)
+ continue;
+
+ g_hash_table_remove (self->priv->contact_details,
+ GUINT_TO_POINTER (members[i]));
+ tp_handle_set_add (handles, members[i]);
+ }
+
+ tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), NULL,
+ handles);
+
+ tp_handle_set_destroy (handles);
+}
+
diff --git a/qt4/tests/lib/glib/contact-list-manager.h b/qt4/tests/lib/glib/contact-list-manager.h
new file mode 100644
index 000000000..a7763cee3
--- /dev/null
+++ b/qt4/tests/lib/glib/contact-list-manager.h
@@ -0,0 +1,69 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2010 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TEST_CONTACT_LIST_MANAGER_H__
+#define __TEST_CONTACT_LIST_MANAGER_H__
+
+#include <telepathy-glib/base-contact-list.h>
+
+G_BEGIN_DECLS
+
+#define TEST_TYPE_CONTACT_LIST_MANAGER \
+ (test_contact_list_manager_get_type ())
+#define TEST_CONTACT_LIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TEST_TYPE_CONTACT_LIST_MANAGER, \
+ TestContactListManager))
+#define TEST_CONTACT_LIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TEST_TYPE_CONTACT_LIST_MANAGER, \
+ TestContactListManagerClass))
+#define TEST_IS_CONTACT_LIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TEST_TYPE_CONTACT_LIST_MANAGER))
+#define TEST_IS_CONTACT_LIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TEST_TYPE_CONTACT_LIST_MANAGER))
+#define TEST_CONTACT_LIST_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_CONTACT_LIST_MANAGER, \
+ TestContactListManagerClass))
+
+typedef struct _TestContactListManager TestContactListManager;
+typedef struct _TestContactListManagerClass TestContactListManagerClass;
+typedef struct _TestContactListManagerPrivate TestContactListManagerPrivate;
+
+struct _TestContactListManagerClass {
+ TpBaseContactListClass parent_class;
+};
+
+struct _TestContactListManager {
+ TpBaseContactList parent;
+
+ TestContactListManagerPrivate *priv;
+};
+
+GType test_contact_list_manager_get_type (void);
+
+void test_contact_list_manager_add_to_group (TestContactListManager *self,
+ const gchar *group_name, TpHandle member);
+void test_contact_list_manager_remove_from_group (TestContactListManager *self,
+ const gchar *group_name, TpHandle member);
+
+void test_contact_list_manager_request_subscription (TestContactListManager *self,
+ guint n_members, TpHandle *members, const gchar *message);
+void test_contact_list_manager_unsubscribe (TestContactListManager *self,
+ guint n_members, TpHandle *members);
+void test_contact_list_manager_authorize_publication (TestContactListManager *self,
+ guint n_members, TpHandle *members);
+void test_contact_list_manager_unpublish (TestContactListManager *self,
+ guint n_members, TpHandle *members);
+void test_contact_list_manager_remove (TestContactListManager *self,
+ guint n_members, TpHandle *members);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contact-search-chan.c b/qt4/tests/lib/glib/contact-search-chan.c
new file mode 100644
index 000000000..0002acacd
--- /dev/null
+++ b/qt4/tests/lib/glib/contact-search-chan.c
@@ -0,0 +1,708 @@
+/*
+ * contact-search-channel.c - an tp_tests contact search channel
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2010 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "contact-search-chan.h"
+
+#include <string.h>
+
+#include <gobject/gvaluecollector.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-properties-interface.h>
+
+static void contact_search_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsContactSearchChannel,
+ tp_tests_contact_search_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_SEARCH,
+ contact_search_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL))
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_CONTACT_SEARCH_STATE,
+ PROP_CONTACT_SEARCH_LIMIT,
+ PROP_CONTACT_SEARCH_AVAILABLE_SEARCH_KEYS,
+ PROP_CONTACT_SEARCH_SERVER,
+ N_PROPS
+};
+
+typedef struct
+{
+ gchar *id;
+ gchar *employer;
+ GPtrArray *contact_info;
+} TpTestsContactSearchContact;
+
+struct _TpTestsContactSearchChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+
+ guint contact_search_state;
+ guint contact_search_limit;
+ gchar **contact_search_available_search_keys;
+ gchar *contact_search_server;
+
+ GSList *contact_search_contacts;
+
+ gboolean disposed;
+ gboolean closed;
+};
+
+static const gchar * tp_tests_contact_search_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+static void
+tp_tests_contact_search_channel_init (TpTestsContactSearchChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL,
+ TpTestsContactSearchChannelPrivate);
+}
+
+static TpTestsContactSearchContact *
+new_contact (const gchar *id, const gchar *employer, const gchar *fn)
+{
+ TpTestsContactSearchContact *contact = g_new (TpTestsContactSearchContact, 1);
+ GPtrArray *contact_info = dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+ const gchar * const field_values[2] = { fn, NULL };
+
+ contact->id = g_strdup (id);
+ contact->employer = g_strdup (employer);
+
+ g_ptr_array_add (contact_info, tp_value_array_build (3,
+ G_TYPE_STRING, "fn",
+ G_TYPE_STRV, NULL,
+ G_TYPE_STRV, field_values,
+ G_TYPE_INVALID));
+
+ contact->contact_info = contact_info;
+
+ return contact;
+}
+
+static void
+free_contact (TpTestsContactSearchContact *contact)
+{
+ g_free (contact->id);
+ g_free (contact->employer);
+ g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact->contact_info);
+ g_free (contact);
+}
+
+static void
+constructed (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) tp_tests_contact_search_channel_parent_class)->constructed;
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpDBusDaemon *bus;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ bus = tp_dbus_daemon_dup (NULL);
+ tp_dbus_daemon_register_object (bus, self->priv->object_path, object);
+
+ tp_group_mixin_init (object,
+ G_STRUCT_OFFSET (TpTestsContactSearchChannel, group),
+ contact_repo, self->priv->conn->self_handle);
+
+ self->priv->contact_search_state = TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED;
+ self->priv->contact_search_limit = 0;
+ self->priv->contact_search_available_search_keys = g_new0 (gchar *, 2);
+ self->priv->contact_search_available_search_keys[0] = g_strdup ("employer");
+ self->priv->contact_search_server = g_strdup ("characters.shakespeare.lit");
+
+ self->priv->contact_search_contacts = g_slist_append (self->priv->contact_search_contacts,
+ new_contact ("oggis", "Collabora", "Olli Salli"));
+ self->priv->contact_search_contacts = g_slist_append (self->priv->contact_search_contacts,
+ new_contact ("andrunko", "Collabora", "Andre Moreira Magalhaes"));
+ self->priv->contact_search_contacts = g_slist_append (self->priv->contact_search_contacts,
+ new_contact ("wjt", "Collabora", "Will Thompson"));
+
+ self->priv->contact_search_contacts = g_slist_append (self->priv->contact_search_contacts,
+ new_contact ("foo", "Other Employer", "Foo"));
+ self->priv->contact_search_contacts = g_slist_append (self->priv->contact_search_contacts,
+ new_contact ("bar", "Other Employer", "Bar"));
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_NONE);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+
+ case PROP_TARGET_ID:
+ g_value_set_string (value, "");
+ break;
+
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, TRUE);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+
+ case PROP_INITIATOR_ID:
+ g_value_set_string (value, "");
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, tp_tests_contact_search_channel_interfaces);
+ break;
+
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "SearchState",
+ TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "Limit",
+ TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "AvailableSearchKeys",
+ TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "Server",
+ NULL));
+ break;
+
+ case PROP_CONTACT_SEARCH_STATE:
+ g_value_set_uint (value, self->priv->contact_search_state);
+ g_assert (G_VALUE_HOLDS (value, G_TYPE_UINT));
+ break;
+
+ case PROP_CONTACT_SEARCH_LIMIT:
+ g_value_set_uint (value, self->priv->contact_search_limit);
+ g_assert (G_VALUE_HOLDS (value, G_TYPE_UINT));
+ break;
+
+ case PROP_CONTACT_SEARCH_AVAILABLE_SEARCH_KEYS:
+ g_value_set_boxed (value, self->priv->contact_search_available_search_keys);
+ g_assert (G_VALUE_HOLDS (value, G_TYPE_STRV));
+ break;
+
+ case PROP_CONTACT_SEARCH_SERVER:
+ g_value_set_string (value, self->priv->contact_search_server);
+ g_assert (G_VALUE_HOLDS (value, G_TYPE_STRING));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_CHANNEL_TYPE:
+ case PROP_HANDLE:
+ case PROP_HANDLE_TYPE:
+ case PROP_TARGET_ID:
+ case PROP_REQUESTED:
+ case PROP_INITIATOR_HANDLE:
+ case PROP_INITIATOR_ID:
+ /* these properties are not actually meaningfully changeable on this
+ * channel, so we do nothing */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (object);
+ GSList *l;
+
+ if (self->priv->disposed)
+ {
+ return;
+ }
+
+ self->priv->disposed = TRUE;
+
+ g_strfreev (self->priv->contact_search_available_search_keys);
+ self->priv->contact_search_available_search_keys = NULL;
+ g_free (self->priv->contact_search_server);
+ self->priv->contact_search_server = NULL;
+
+ for (l = self->priv->contact_search_contacts; l != NULL; l = g_slist_next (l))
+ {
+ free_contact ((TpTestsContactSearchContact *) l->data);
+ }
+ g_slist_free (self->priv->contact_search_contacts);
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) tp_tests_contact_search_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (object);
+
+ g_free (self->priv->object_path);
+
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) tp_tests_contact_search_channel_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_contact_search_channel_class_init (TpTestsContactSearchChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl contact_search_props[] = {
+ { "SearchState", "search-state", NULL },
+ { "Limit", "limit", NULL },
+ { "AvailableSearchKeys", "available-search-keys", NULL },
+ { "Server", "server", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ contact_search_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (TpTestsContactSearchChannelPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE,
+ "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("search-state", "Search state",
+ "The search state",
+ 0, NUM_TP_CHANNEL_CONTACT_SEARCH_STATES, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_SEARCH_STATE,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("limit", "Search limit",
+ "The search limit",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_SEARCH_LIMIT,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("available-search-keys", "Available Search Keys",
+ "The available search keys",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_SEARCH_AVAILABLE_SEARCH_KEYS, param_spec);
+
+ param_spec = g_param_spec_string ("server", "Server",
+ "The search server",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_SEARCH_SERVER,
+ param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsContactSearchChannelClass,
+ dbus_properties_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsContactSearchChannelClass, group_class),
+ NULL, NULL);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (iface);
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_NONE, 0);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ tp_tests_contact_search_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+change_search_state (TpTestsContactSearchChannel *self,
+ guint state,
+ const gchar *debug_message)
+{
+ GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ g_hash_table_insert (details, "debug-message", tp_g_value_slice_new_string (debug_message));
+
+ self->priv->contact_search_state = state;
+ tp_svc_channel_type_contact_search_emit_search_state_changed (self,
+ self->priv->contact_search_state, "", details);
+
+ g_hash_table_destroy (details);
+}
+
+static gboolean
+validate_terms (TpTestsContactSearchChannel *self,
+ GHashTable *terms,
+ GError **error)
+{
+ const gchar * const *asks =
+ (const gchar * const *) self->priv->contact_search_available_search_keys;
+ GHashTableIter iter;
+ gpointer key;
+
+ g_hash_table_iter_init (&iter, terms);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ gchar *field = key;
+
+ if (!tp_strv_contains (asks, field))
+ {
+ g_debug ("%s is not in AvailableSearchKeys", field);
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%s is not in AvailableSearchKeys", field);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+do_search (TpTestsContactSearchChannel *self,
+ GHashTable *terms,
+ GError **error)
+{
+ GHashTable *results = g_hash_table_new (g_str_hash, g_str_equal);
+ GHashTableIter iter;
+ gchar *key, *value;
+
+ if (!validate_terms (self, terms, error))
+ {
+ return FALSE;
+ }
+
+ g_debug ("Doing search");
+ change_search_state (self, TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS, "in progress");
+
+ g_hash_table_iter_init (&iter, terms);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
+ {
+ GSList *l;
+
+ for (l = self->priv->contact_search_contacts; l != NULL; l = g_slist_next (l))
+ {
+ TpTestsContactSearchContact *contact = (TpTestsContactSearchContact *) l->data;
+ if (strcmp (contact->employer, value) == 0)
+ {
+ g_hash_table_insert (results, contact->id, contact->contact_info);
+ }
+ }
+ }
+
+ tp_svc_channel_type_contact_search_emit_search_result_received (self,
+ results);
+
+ change_search_state (self, TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED, "completed");
+
+ g_hash_table_destroy (results);
+
+ return TRUE;
+}
+
+static void
+contact_search_search (TpSvcChannelTypeContactSearch *iface G_GNUC_UNUSED,
+ GHashTable *terms,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (iface);
+ TpTestsContactSearchChannelPrivate *priv = self->priv;
+ GError *error = NULL;
+
+ if (priv->contact_search_state != TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED)
+ {
+ g_debug ("Search state is %d", priv->contact_search_state);
+ error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "SearchState is %d", priv->contact_search_state);
+ goto err;
+ }
+
+ if (do_search (self, terms, &error))
+ {
+ tp_svc_channel_type_contact_search_return_from_search (context);
+ return;
+ }
+
+err:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+contact_search_more (TpSvcChannelTypeContactSearch *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_type_contact_search_return_from_more (context);
+}
+
+static void
+contact_search_stop (TpSvcChannelTypeContactSearch *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactSearchChannel *self = TP_TESTS_CONTACT_SEARCH_CHANNEL (iface);
+ TpTestsContactSearchChannelPrivate *priv = self->priv;
+
+ switch (priv->contact_search_state)
+ {
+ case TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS:
+ change_search_state (self,
+ TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED, "stopped while in progress");
+ case TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED:
+ tp_svc_channel_type_contact_search_return_from_stop (context);
+ break;
+ case TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED:
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Search() hasn't been called yet" };
+
+ g_debug ("%s", e.message);
+ dbus_g_method_return_error (context, &e);
+ break;
+ }
+ case TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED:
+ case TP_CHANNEL_CONTACT_SEARCH_STATE_MORE_AVAILABLE:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+contact_search_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeContactSearchClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_contact_search_implement_##x (klass, contact_search_##x)
+ IMPLEMENT (search);
+ IMPLEMENT (more);
+ IMPLEMENT (stop);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/contact-search-chan.h b/qt4/tests/lib/glib/contact-search-chan.h
new file mode 100644
index 000000000..857ff96f2
--- /dev/null
+++ b/qt4/tests/lib/glib/contact-search-chan.h
@@ -0,0 +1,74 @@
+/*
+ * contact-search-channel.h - header for an tp_tests contact search channel
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2010 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TP_TESTS_CONTACT_SEARCH_CHANNEL_H
+#define TP_TESTS_CONTACT_SEARCH_CHANNEL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsContactSearchChannel TpTestsContactSearchChannel;
+typedef struct _TpTestsContactSearchChannelPrivate TpTestsContactSearchChannelPrivate;
+
+typedef struct _TpTestsContactSearchChannelClass TpTestsContactSearchChannelClass;
+typedef struct _TpTestsContactSearchChannelClassPrivate TpTestsContactSearchChannelClassPrivate;
+
+GType tp_tests_contact_search_channel_get_type (void);
+
+#define TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL \
+ (tp_tests_contact_search_channel_get_type ())
+#define TP_TESTS_CONTACT_SEARCH_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL, \
+ TpTestsContactSearchChannel))
+#define TP_TESTS_CONTACT_SEARCH_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL, \
+ TpTestsContactSearchChannelClass))
+#define TP_TESTS_IS_CONTACT_SEARCH_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL))
+#define TP_TESTS_IS_CONTACT_SEARCH_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL))
+#define TP_TESTS_CONTACT_SEARCH_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONTACT_SEARCH_CHANNEL, \
+ TpTestsContactSearchChannelClass))
+
+struct _TpTestsContactSearchChannelClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_properties_class;
+ TpGroupMixinClass group_class;
+
+ TpTestsContactSearchChannelClassPrivate *priv;
+};
+
+struct _TpTestsContactSearchChannel {
+ GObject parent;
+
+ TpGroupMixin group;
+
+ TpTestsContactSearchChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist/CMakeLists.txt b/qt4/tests/lib/glib/contactlist/CMakeLists.txt
new file mode 100644
index 000000000..77edec1db
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/CMakeLists.txt
@@ -0,0 +1,15 @@
+if(ENABLE_TP_GLIB_TESTS)
+ set(example_cm_contactlist_SRCS
+ conn.c
+ conn.h
+ connection-manager.c
+ connection-manager.h
+ contact-list.c
+ contact-list.h
+ contact-list-manager.c
+ contact-list-manager.h)
+
+ add_library(example-cm-contactlist STATIC ${example_cm_contactlist_SRCS})
+ target_link_libraries(example-cm-contactlist ${TPGLIB_LIBRARIES})
+ tpqt4_generate_manager_file(${CMAKE_CURRENT_SOURCE_DIR}/manager-file.py example_contact_list.manager connection-manager.c)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/contactlist/conn.c b/qt4/tests/lib/glib/contactlist/conn.c
new file mode 100644
index 000000000..b335702f9
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/conn.c
@@ -0,0 +1,606 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo-static.h>
+
+#include "contact-list-manager.h"
+
+static void init_aliasing (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListConnection,
+ example_contact_list_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ init_aliasing);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init))
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleContactListConnectionPrivate
+{
+ gchar *account;
+ guint simulation_delay;
+ ExampleContactListManager *list_manager;
+ gboolean away;
+};
+
+static void
+example_contact_list_connection_init (ExampleContactListConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ ExampleContactListConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_value_dup_string (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (example_contact_list_connection_parent_class)->finalize (
+ object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+
+ return g_strdup_printf ("%s@%p", self->priv->account, self);
+}
+
+gchar *
+example_contact_list_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Contact ID must not be empty");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static gchar *
+example_contact_list_normalize_group (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Contact group name cannot be empty");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_contact_list_normalize_contact, NULL);
+
+ repos[TP_HANDLE_TYPE_LIST] = tp_static_handle_repo_new
+ (TP_HANDLE_TYPE_LIST, example_contact_lists ());
+
+ repos[TP_HANDLE_TYPE_GROUP] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_GROUP, example_contact_list_normalize_group, NULL);
+}
+
+static void
+alias_updated_cb (ExampleContactListManager *manager,
+ TpHandle contact,
+ ExampleContactListConnection *self)
+{
+ GPtrArray *aliases;
+ GValueArray *pair;
+
+ pair = g_value_array_new (2);
+ g_value_array_append (pair, NULL);
+ g_value_array_append (pair, NULL);
+ g_value_init (pair->values + 0, G_TYPE_UINT);
+ g_value_init (pair->values + 1, G_TYPE_STRING);
+ g_value_set_uint (pair->values + 0, contact);
+ g_value_set_string (pair->values + 1,
+ example_contact_list_manager_get_alias (manager, contact));
+
+ aliases = g_ptr_array_sized_new (1);
+ g_ptr_array_add (aliases, pair);
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
+
+ g_ptr_array_free (aliases, TRUE);
+ g_value_array_free (pair);
+}
+
+static void
+presence_updated_cb (ExampleContactListManager *manager,
+ TpHandle contact,
+ ExampleContactListConnection *self)
+{
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpPresenceStatus *status;
+
+ /* we ignore the presence indicated by the contact list for our own handle */
+ if (contact == base->self_handle)
+ return;
+
+ status = tp_presence_status_new (
+ example_contact_list_manager_get_presence (manager, contact),
+ NULL);
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self,
+ contact, status);
+ tp_presence_status_free (status);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ self->priv->list_manager =
+ EXAMPLE_CONTACT_LIST_MANAGER (g_object_new (
+ EXAMPLE_TYPE_CONTACT_LIST_MANAGER,
+ "connection", conn,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL));
+
+ g_signal_connect (self->priv->list_manager, "alias-updated",
+ G_CALLBACK (alias_updated_cb), self);
+ g_signal_connect (self->priv->list_manager, "presence-updated",
+ G_CALLBACK (presence_updated_cb), self);
+
+ g_ptr_array_add (ret, self->priv->list_manager);
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, error);
+
+ if (conn->self_handle == 0)
+ return FALSE;
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static void
+aliasing_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+
+ tp_contacts_mixin_set_contact_attribute (attributes, contact,
+ TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
+ tp_g_value_slice_new_string (
+ example_contact_list_manager_get_alias (self->priv->list_manager,
+ contact)));
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin));
+ tp_base_connection_register_with_contacts_mixin (base);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ aliasing_fill_contact_attributes);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin));
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
+}
+
+static gboolean
+status_available (GObject *object,
+ guint index_)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+
+ if (base->status != TP_CONNECTION_STATUS_CONNECTED)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GHashTable *
+get_contact_statuses (GObject *object,
+ const GArray *contacts,
+ GError **error)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ guint i;
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+ ExampleContactListPresence presence;
+ GHashTable *parameters;
+
+ /* we get our own status from the connection, and everyone else's status
+ * from the contact lists */
+ if (contact == base->self_handle)
+ {
+ presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY
+ : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE);
+ }
+ else
+ {
+ presence = example_contact_list_manager_get_presence (
+ self->priv->list_manager, contact);
+ }
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ tp_presence_status_new (presence, parameters));
+ g_hash_table_destroy (parameters);
+ }
+
+ return result;
+}
+
+static gboolean
+set_own_status (GObject *object,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ GHashTable *presences;
+
+ if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY)
+ {
+ if (self->priv->away)
+ return TRUE;
+
+ self->priv->away = TRUE;
+ }
+ else
+ {
+ if (!self->priv->away)
+ return TRUE;
+
+ self->priv->away = FALSE;
+ }
+
+ presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+ g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle),
+ (gpointer) status);
+ tp_presence_mixin_emit_presence_update (object, presences);
+ g_hash_table_destroy (presences);
+ return TRUE;
+}
+
+static void
+example_contact_list_connection_class_init (
+ ExampleContactListConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ NULL };
+ TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass,
+ sizeof (ExampleContactListConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin));
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin),
+ status_available, get_contact_statuses, set_own_status,
+ example_contact_list_presence_statuses ());
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+}
+
+static void
+get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+ TP_CONNECTION_ALIAS_FLAG_USER_SET);
+}
+
+static void
+get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTable *result;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = example_contact_list_manager_get_alias (
+ self->priv->list_manager, contact);
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ (gchar *) alias);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+ g_hash_table_destroy (result);
+}
+
+static void
+request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GPtrArray *result;
+ gchar **strings;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_ptr_array_sized_new (contacts->len + 1);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = example_contact_list_manager_get_alias (
+ self->priv->list_manager, contact);
+
+ g_ptr_array_add (result, (gchar *) alias);
+ }
+
+ g_ptr_array_add (result, NULL);
+ strings = (gchar **) g_ptr_array_free (result, FALSE);
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+ (const gchar **) strings);
+ g_free (strings);
+}
+
+static void
+set_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ GHashTable *aliases,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, aliases);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key),
+ &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ g_hash_table_iter_init (&iter, aliases);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ example_contact_list_manager_set_alias (self->priv->list_manager,
+ GPOINTER_TO_UINT (key), value);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
+}
+
+static void
+init_aliasing (gpointer iface,
+ gpointer iface_data G_GNUC_UNUSED)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
+ klass, x)
+ IMPLEMENT(get_alias_flags);
+ IMPLEMENT(request_aliases);
+ IMPLEMENT(get_aliases);
+ IMPLEMENT(set_aliases);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/contactlist/conn.h b/qt4/tests/lib/glib/contactlist/conn.h
new file mode 100644
index 000000000..cb460f632
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/conn.h
@@ -0,0 +1,65 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_CONN_H__
+#define __EXAMPLE_CONTACT_LIST_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListConnection ExampleContactListConnection;
+typedef struct _ExampleContactListConnectionClass
+ ExampleContactListConnectionClass;
+typedef struct _ExampleContactListConnectionPrivate
+ ExampleContactListConnectionPrivate;
+
+struct _ExampleContactListConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+};
+
+struct _ExampleContactListConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ ExampleContactListConnectionPrivate *priv;
+};
+
+GType example_contact_list_connection_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION \
+ (example_contact_list_connection_get_type ())
+#define EXAMPLE_CONTACT_LIST_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnection))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnectionClass))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnectionClass))
+
+gchar *example_contact_list_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id, gpointer context, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist/connection-manager.c b/qt4/tests/lib/glib/contactlist/connection-manager.c
new file mode 100644
index 000000000..dda422a0f
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/connection-manager.c
@@ -0,0 +1,117 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleContactListConnectionManager,
+ example_contact_list_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _ExampleContactListConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+example_contact_list_connection_manager_init (
+ ExampleContactListConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER,
+ ExampleContactListConnectionManagerPrivate);
+}
+
+typedef struct {
+ gchar *account;
+ guint simulation_delay;
+} ExampleParams;
+
+static gboolean
+account_param_filter (const TpCMParamSpec *paramspec,
+ GValue *value,
+ GError **error)
+{
+ const gchar *id = g_value_get_string (value);
+
+ g_value_take_string (value,
+ example_contact_list_normalize_contact (NULL, id, NULL, error));
+
+ if (g_value_get_string (value) == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+#include "_gen/param-spec-struct.h"
+
+static gpointer
+alloc_params (void)
+{
+ return g_slice_new0 (ExampleParams);
+}
+
+static void
+free_params (gpointer p)
+{
+ ExampleParams *params = p;
+
+ g_free (params->account);
+
+ g_slice_free (ExampleParams, params);
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+ { "example", example_contact_list_example_params,
+ alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ ExampleParams *params = parsed_params;
+ ExampleContactListConnection *conn;
+
+ conn = EXAMPLE_CONTACT_LIST_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", params->account,
+ "simulation-delay", params->simulation_delay,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_contact_list_connection_manager_class_init (
+ ExampleContactListConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleContactListConnectionManagerPrivate));
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_contact_list";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/qt4/tests/lib/glib/contactlist/connection-manager.h b/qt4/tests/lib/glib/contactlist/connection-manager.h
new file mode 100644
index 000000000..b99d15963
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/connection-manager.h
@@ -0,0 +1,62 @@
+/*
+ * manager.h - header for an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__
+#define __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListConnectionManager
+ ExampleContactListConnectionManager;
+typedef struct _ExampleContactListConnectionManagerClass
+ ExampleContactListConnectionManagerClass;
+typedef struct _ExampleContactListConnectionManagerPrivate
+ ExampleContactListConnectionManagerPrivate;
+
+struct _ExampleContactListConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+};
+
+struct _ExampleContactListConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleContactListConnectionManagerPrivate *priv;
+};
+
+GType example_contact_list_connection_manager_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER \
+ (example_contact_list_connection_manager_get_type ())
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManager))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManagerClass))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist/contact-list-manager.c b/qt4/tests/lib/glib/contactlist/contact-list-manager.c
new file mode 100644
index 000000000..bc10435a6
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/contact-list-manager.c
@@ -0,0 +1,1774 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contact-list-manager.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "contact-list.h"
+
+/* elements 0, 1... of this array must be kept in sync with elements 1, 2...
+ * of the enum ExampleContactList in contact-list-manager.h */
+static const gchar *_contact_lists[NUM_EXAMPLE_CONTACT_LISTS + 1] = {
+ "subscribe",
+ "publish",
+ "stored",
+ "deny",
+ NULL
+};
+
+const gchar **
+example_contact_lists (void)
+{
+ return _contact_lists;
+}
+
+/* this array must be kept in sync with the enum
+ * ExampleContactListPresence in contact-list-manager.h */
+static const TpPresenceStatusSpec _statuses[] = {
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
+ { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL },
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL },
+ { NULL }
+};
+
+const TpPresenceStatusSpec *
+example_contact_list_presence_statuses (void)
+{
+ return _statuses;
+}
+
+typedef struct {
+ gchar *alias;
+
+ guint subscribe:1;
+ guint publish:1;
+ guint subscribe_requested:1;
+ guint publish_requested:1;
+ guint stored:1;
+ guint blocked:1;
+
+ TpHandleSet *tags;
+
+} ExampleContactDetails;
+
+static ExampleContactDetails *
+example_contact_details_new (void)
+{
+ return g_slice_new0 (ExampleContactDetails);
+}
+
+static void
+example_contact_details_destroy (gpointer p)
+{
+ ExampleContactDetails *d = p;
+
+ if (d->tags != NULL)
+ tp_handle_set_destroy (d->tags);
+
+ g_free (d->alias);
+ g_slice_free (ExampleContactDetails, d);
+}
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListManager,
+ example_contact_list_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+enum
+{
+ ALIAS_UPDATED,
+ PRESENCE_UPDATED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleContactListManagerPrivate
+{
+ TpBaseConnection *conn;
+ guint simulation_delay;
+ TpHandleRepoIface *contact_repo;
+ TpHandleRepoIface *group_repo;
+
+ TpHandleSet *contacts;
+ /* GUINT_TO_POINTER (handle borrowed from contacts)
+ * => ExampleContactDetails */
+ GHashTable *contact_details;
+
+ ExampleContactList *lists[NUM_EXAMPLE_CONTACT_LISTS];
+
+ /* GUINT_TO_POINTER (handle borrowed from channel) => ExampleContactGroup */
+ GHashTable *groups;
+
+ /* borrowed TpExportableChannel => GSList of gpointer (request tokens) that
+ * will be satisfied by that channel when the contact list has been
+ * downloaded. The requests are in reverse chronological order */
+ GHashTable *queued_requests;
+
+ gulong status_changed_id;
+};
+
+static void
+example_contact_list_manager_init (ExampleContactListManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_MANAGER, ExampleContactListManagerPrivate);
+
+ self->priv->contact_details = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, example_contact_details_destroy);
+ self->priv->groups = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+ self->priv->queued_requests = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, NULL);
+
+ /* initialized properly in constructed() */
+ self->priv->contact_repo = NULL;
+ self->priv->group_repo = NULL;
+ self->priv->contacts = NULL;
+}
+
+static void
+example_contact_list_manager_close_all (ExampleContactListManager *self)
+{
+ guint i;
+
+ if (self->priv->queued_requests != NULL)
+ {
+ GHashTable *tmp = self->priv->queued_requests;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ self->priv->queued_requests = NULL;
+ g_hash_table_iter_init (&iter, tmp);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GSList *requests = value;
+ GSList *l;
+
+ requests = g_slist_reverse (requests);
+
+ for (l = requests; l != NULL; l = l->next)
+ {
+ tp_channel_manager_emit_request_failed (self,
+ l->data, TP_ERRORS, TP_ERROR_DISCONNECTED,
+ "Unable to complete channel request due to disconnection");
+ }
+
+ g_slist_free (requests);
+ g_hash_table_iter_steal (&iter);
+ }
+
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->contacts != NULL)
+ {
+ tp_handle_set_destroy (self->priv->contacts);
+ self->priv->contacts = NULL;
+ }
+
+ if (self->priv->contact_details != NULL)
+ {
+ GHashTable *tmp = self->priv->contact_details;
+
+ self->priv->contact_details = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->groups != NULL)
+ {
+ GHashTable *tmp = self->priv->groups;
+
+ self->priv->groups = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ for (i = 0; i < NUM_EXAMPLE_CONTACT_LISTS; i++)
+ {
+ if (self->priv->lists[i] != NULL)
+ {
+ ExampleContactList *list = self->priv->lists[i];
+
+ /* set self->priv->lists[i] to NULL here so list_closed_cb does
+ * not try to delete the list again */
+ self->priv->lists[i] = NULL;
+ g_object_unref (list);
+ }
+ }
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+
+ example_contact_list_manager_close_all (self);
+ g_assert (self->priv->groups == NULL);
+ g_assert (self->priv->lists[0] == NULL);
+ g_assert (self->priv->queued_requests == NULL);
+
+ ((GObjectClass *) example_contact_list_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+satisfy_queued_requests (TpExportableChannel *channel,
+ gpointer user_data)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (user_data);
+ GSList *requests = g_hash_table_lookup (self->priv->queued_requests,
+ channel);
+
+ /* this is all fine even if requests is NULL */
+ g_hash_table_steal (self->priv->queued_requests, channel);
+ requests = g_slist_reverse (requests);
+ tp_channel_manager_emit_new_channel (self, channel, requests);
+ g_slist_free (requests);
+}
+
+static ExampleContactDetails *
+lookup_contact (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ return g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (contact));
+}
+
+static ExampleContactDetails *
+ensure_contact (ExampleContactListManager *self,
+ TpHandle contact,
+ gboolean *created)
+{
+ ExampleContactDetails *ret = lookup_contact (self, contact);
+
+ if (ret == NULL)
+ {
+ tp_handle_set_add (self->priv->contacts, contact);
+
+ ret = example_contact_details_new ();
+ ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo,
+ contact));
+
+ g_hash_table_insert (self->priv->contact_details,
+ GUINT_TO_POINTER (contact), ret);
+
+ if (created != NULL)
+ *created = TRUE;
+ }
+ else if (created != NULL)
+ {
+ *created = FALSE;
+ }
+
+ return ret;
+}
+
+static void
+example_contact_list_manager_foreach_channel (TpChannelManager *manager,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (manager);
+ GHashTableIter iter;
+ gpointer handle, channel;
+ guint i;
+
+ for (i = 0; i < NUM_EXAMPLE_CONTACT_LISTS; i++)
+ {
+ if (self->priv->lists[i] != NULL)
+ callback (TP_EXPORTABLE_CHANNEL (self->priv->lists[i]), user_data);
+ }
+
+ g_hash_table_iter_init (&iter, self->priv->groups);
+
+ while (g_hash_table_iter_next (&iter, &handle, &channel))
+ {
+ callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+ }
+}
+
+static ExampleContactGroup *ensure_group (ExampleContactListManager *self,
+ TpHandle handle);
+
+static ExampleContactList *ensure_list (ExampleContactListManager *self,
+ ExampleContactListHandle handle);
+
+static gboolean
+receive_contact_lists (gpointer p)
+{
+ ExampleContactListManager *self = p;
+ TpHandle handle, cambridge, montreal, francophones;
+ ExampleContactDetails *d;
+ TpIntSet *set, *cam_set, *mtl_set, *fr_set;
+ TpIntSetFastIter iter;
+ ExampleContactList *subscribe, *publish, *stored, *deny;
+ ExampleContactGroup *cambridge_group, *montreal_group,
+ *francophones_group;
+
+ if (self->priv->groups == NULL)
+ {
+ /* connection already disconnected, so don't process the
+ * "data from the server" */
+ return FALSE;
+ }
+
+ /* In a real CM we'd have received a contact list from the server at this
+ * point. But this isn't a real CM, so we have to make one up... */
+
+ g_message ("Receiving roster from server");
+
+ subscribe = ensure_list (self, EXAMPLE_CONTACT_LIST_SUBSCRIBE);
+ publish = ensure_list (self, EXAMPLE_CONTACT_LIST_PUBLISH);
+ stored = ensure_list (self, EXAMPLE_CONTACT_LIST_STORED);
+ deny = ensure_list (self, EXAMPLE_CONTACT_LIST_DENY);
+
+ cambridge = tp_handle_ensure (self->priv->group_repo, "Cambridge", NULL,
+ NULL);
+ montreal = tp_handle_ensure (self->priv->group_repo, "Montreal", NULL,
+ NULL);
+ francophones = tp_handle_ensure (self->priv->group_repo, "Francophones",
+ NULL, NULL);
+
+ cambridge_group = ensure_group (self, cambridge);
+ montreal_group = ensure_group (self, montreal);
+ francophones_group = ensure_group (self, francophones);
+
+ /* Add various people who are already subscribing and publishing */
+
+ set = tp_intset_new ();
+ cam_set = tp_intset_new ();
+ mtl_set = tp_intset_new ();
+ fr_set = tp_intset_new ();
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "sjoerd@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Sjoerd");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->stored = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "guillaume@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ tp_intset_add (fr_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Guillaume");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->stored = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_set_add (d->tags, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "olivier@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (mtl_set, handle);
+ tp_intset_add (fr_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Olivier");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->stored = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, montreal);
+ tp_handle_set_add (d->tags, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "travis@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Travis");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->stored = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ tp_group_mixin_change_members ((GObject *) subscribe, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) publish, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_fast_iter_init (&iter, set);
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ {
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+ }
+
+ tp_intset_destroy (set);
+
+ /* Add a couple of people whose presence we've requested. They are
+ * remote-pending in subscribe */
+
+ set = tp_intset_new ();
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "geraldine@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ tp_intset_add (fr_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Géraldine");
+ d->subscribe_requested = TRUE;
+ d->stored = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_set_add (d->tags, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "helen@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ tp_intset_add (cam_set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Helen");
+ d->subscribe_requested = TRUE;
+ d->stored = TRUE;
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+ tp_handle_set_add (d->tags, cambridge);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ tp_group_mixin_change_members ((GObject *) subscribe, "",
+ NULL, NULL, NULL, set,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_fast_iter_init (&iter, set);
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ {
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+ }
+
+ tp_intset_destroy (set);
+
+ /* Receive a couple of authorization requests too. These people are
+ * local-pending in publish */
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "wim@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Wim");
+ d->publish_requested = TRUE;
+ d->stored = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ set = tp_intset_new_containing (handle);
+ tp_group_mixin_change_members ((GObject *) publish,
+ "I'm more metal than you!",
+ NULL, NULL, set, NULL,
+ handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "christian@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Christian");
+ d->publish_requested = TRUE;
+ d->stored = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ set = tp_intset_new_containing (handle);
+ tp_group_mixin_change_members ((GObject *) publish,
+ "I have some fermented herring for you",
+ NULL, NULL, set, NULL,
+ handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+
+ /* Add a couple of people who are blocked */
+
+ set = tp_intset_new ();
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "bill@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Bill");
+ d->blocked = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "steve@example.com",
+ NULL, NULL);
+ tp_intset_add (set, handle);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Steve");
+ d->blocked = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ tp_group_mixin_change_members ((GObject *) deny, "",
+ set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_fast_iter_init (&iter, set);
+
+ while (tp_intset_fast_iter_next (&iter, &handle))
+ {
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+ }
+
+ tp_intset_destroy (set);
+
+ /* Handle groups */
+
+ tp_group_mixin_change_members ((GObject *) cambridge_group, "",
+ cam_set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) montreal_group, "",
+ mtl_set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) francophones_group, "",
+ fr_set, NULL, NULL, NULL,
+ 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (fr_set);
+ tp_intset_destroy (cam_set);
+ tp_intset_destroy (mtl_set);
+
+ tp_handle_unref (self->priv->group_repo, cambridge);
+ tp_handle_unref (self->priv->group_repo, montreal);
+ tp_handle_unref (self->priv->group_repo, francophones);
+
+ /* Now we've received the roster, we can satisfy all the queued requests */
+
+ example_contact_list_manager_foreach_channel ((TpChannelManager *) self,
+ satisfy_queued_requests, self);
+
+ g_assert (g_hash_table_size (self->priv->queued_requests) == 0);
+ g_hash_table_destroy (self->priv->queued_requests);
+ self->priv->queued_requests = NULL;
+
+ return FALSE;
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleContactListManager *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTED:
+ {
+ /* Do network I/O to get the contact list. This connection manager
+ * doesn't really have a server, so simulate a small network delay
+ * then invent a contact list */
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ 2 * self->priv->simulation_delay, receive_contact_lists,
+ g_object_ref (self), g_object_unref);
+ }
+ break;
+
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ example_contact_list_manager_close_all (self);
+ }
+ break;
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleContactListManager *self = EXAMPLE_CONTACT_LIST_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_GROUP);
+ self->priv->contacts = tp_handle_set_new (self->priv->contact_repo);
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_contact_list_manager_class_init (ExampleContactListManagerClass *klass)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ param_spec = g_param_spec_object ("connection", "Connection object",
+ "The connection that owns this channel manager",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListManagerPrivate));
+
+ signals[ALIAS_UPDATED] = g_signal_new ("alias-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+static void
+list_closed_cb (ExampleContactList *chan,
+ ExampleContactListManager *self)
+{
+ TpHandle handle;
+
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ g_object_get (chan,
+ "handle", &handle,
+ NULL);
+
+ if (self->priv->lists[handle] == NULL)
+ return;
+
+ g_assert (chan == self->priv->lists[handle]);
+ g_object_unref (self->priv->lists[handle]);
+ self->priv->lists[handle] = NULL;
+}
+
+static void
+group_closed_cb (ExampleContactGroup *chan,
+ ExampleContactListManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->groups != NULL)
+ {
+ TpHandle handle;
+
+ g_object_get (chan,
+ "handle", &handle,
+ NULL);
+
+ g_hash_table_remove (self->priv->groups, GUINT_TO_POINTER (handle));
+ }
+}
+
+static ExampleContactListBase *
+new_channel (ExampleContactListManager *self,
+ TpHandleType handle_type,
+ TpHandle handle,
+ gpointer request_token)
+{
+ ExampleContactListBase *chan;
+ gchar *object_path;
+ GType type;
+ GSList *requests = NULL;
+
+ if (handle_type == TP_HANDLE_TYPE_LIST)
+ {
+ /* Some Telepathy clients wrongly assume that contact lists of type LIST
+ * have object paths ending with "/subscribe", "/publish" etc. -
+ * telepathy-spec has no such guarantee, so in this example we break
+ * those clients. Please read the spec when implementing it :-) */
+ object_path = g_strdup_printf ("%s/%sContactList",
+ self->priv->conn->object_path, _contact_lists[handle - 1]);
+ type = EXAMPLE_TYPE_CONTACT_LIST;
+ }
+ else
+ {
+ /* Using Group%u (with handle as the value of %u) would be OK here too,
+ * but we'll encode the group name into the object path to be kind
+ * to people reading debug logs. */
+ gchar *id = tp_escape_as_identifier (tp_handle_inspect (
+ self->priv->group_repo, handle));
+
+ g_assert (handle_type == TP_HANDLE_TYPE_GROUP);
+ object_path = g_strdup_printf ("%s/Group/%s",
+ self->priv->conn->object_path, id);
+ type = EXAMPLE_TYPE_CONTACT_GROUP;
+
+ g_free (id);
+ }
+
+ chan = g_object_new (type,
+ "connection", self->priv->conn,
+ "manager", self,
+ "object-path", object_path,
+ "handle-type", handle_type,
+ "handle", handle,
+ NULL);
+
+ g_free (object_path);
+
+ if (handle_type == TP_HANDLE_TYPE_LIST)
+ {
+ g_signal_connect (chan, "closed", (GCallback) list_closed_cb, self);
+ g_assert (self->priv->lists[handle] == NULL);
+ self->priv->lists[handle] = EXAMPLE_CONTACT_LIST (chan);
+ }
+ else
+ {
+ g_signal_connect (chan, "closed", (GCallback) group_closed_cb, self);
+
+ g_assert (g_hash_table_lookup (self->priv->groups,
+ GUINT_TO_POINTER (handle)) == NULL);
+ g_hash_table_insert (self->priv->groups, GUINT_TO_POINTER (handle),
+ EXAMPLE_CONTACT_GROUP (chan));
+ }
+
+ if (self->priv->queued_requests == NULL)
+ {
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+ g_slist_free (requests);
+ }
+ else if (request_token != NULL)
+ {
+ /* initial contact list not received yet, so we have to wait for it */
+ requests = g_hash_table_lookup (self->priv->queued_requests, chan);
+ g_hash_table_steal (self->priv->queued_requests, chan);
+ requests = g_slist_prepend (requests, request_token);
+ g_hash_table_insert (self->priv->queued_requests, chan, requests);
+ }
+
+ return chan;
+}
+
+static ExampleContactList *
+ensure_list (ExampleContactListManager *self,
+ ExampleContactListHandle handle)
+{
+ if (self->priv->lists[handle] == NULL)
+ {
+ new_channel (self, TP_HANDLE_TYPE_LIST, handle, NULL);
+ g_assert (self->priv->lists[handle] != NULL);
+ }
+
+ return self->priv->lists[handle];
+}
+
+static ExampleContactGroup *
+ensure_group (ExampleContactListManager *self,
+ TpHandle handle)
+{
+ ExampleContactGroup *group = g_hash_table_lookup (self->priv->groups,
+ GUINT_TO_POINTER (handle));
+
+ if (group == NULL)
+ {
+ group = EXAMPLE_CONTACT_GROUP (new_channel (self, TP_HANDLE_TYPE_GROUP,
+ handle, NULL));
+ }
+
+ return group;
+}
+
+static const gchar * const fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ NULL
+};
+
+static const gchar * const allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ NULL
+};
+
+static void
+example_contact_list_manager_foreach_channel_class (TpChannelManager *manager,
+ TpChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
+ NULL);
+
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ tp_g_value_slice_new_uint (TP_HANDLE_TYPE_GROUP));
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_contact_list_manager_request (ExampleContactListManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandleType handle_type;
+ TpHandle handle;
+ ExampleContactListBase *chan;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
+ {
+ return FALSE;
+ }
+
+ handle_type = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
+
+ if (handle_type != TP_HANDLE_TYPE_LIST &&
+ handle_type != TP_HANDLE_TYPE_GROUP)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ fixed_properties, allowed_properties, &error))
+ {
+ goto error;
+ }
+
+ if (handle_type == TP_HANDLE_TYPE_LIST)
+ {
+ /* telepathy-glib has already checked that the handle is valid */
+ g_assert (handle < NUM_EXAMPLE_CONTACT_LISTS);
+
+ chan = EXAMPLE_CONTACT_LIST_BASE (self->priv->lists[handle]);
+ }
+ else
+ {
+ chan = g_hash_table_lookup (self->priv->groups,
+ GUINT_TO_POINTER (handle));
+ }
+
+ if (chan == NULL)
+ {
+ new_channel (self, handle_type, handle, request_token);
+ }
+ else if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "A ContactList channel for type #%u, handle #%u already exists",
+ handle_type, handle);
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ }
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+example_contact_list_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_contact_list_manager_request (
+ EXAMPLE_CONTACT_LIST_MANAGER (manager), request_token,
+ request_properties, TRUE);
+}
+
+static gboolean
+example_contact_list_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_contact_list_manager_request (
+ EXAMPLE_CONTACT_LIST_MANAGER (manager), request_token,
+ request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_contact_list_manager_foreach_channel;
+ iface->foreach_channel_class =
+ example_contact_list_manager_foreach_channel_class;
+ iface->create_channel = example_contact_list_manager_create_channel;
+ iface->ensure_channel = example_contact_list_manager_ensure_channel;
+ /* In this channel manager, Request has the same semantics as Ensure */
+ iface->request_channel = example_contact_list_manager_ensure_channel;
+}
+
+static void
+send_updated_roster (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (contact));
+ const gchar *identifier = tp_handle_inspect (self->priv->contact_repo,
+ contact);
+
+ /* In a real connection manager, we'd transmit these new details to the
+ * server, rather than just printing messages. */
+
+ if (d == NULL)
+ {
+ g_message ("Deleting contact %s from server", identifier);
+ }
+ else
+ {
+ g_message ("Transmitting new state of contact %s to server", identifier);
+ g_message ("\talias = %s", d->alias);
+ g_message ("\tcan see our presence = %s",
+ d->publish ? "yes" :
+ (d->publish_requested ? "no, but has requested it" : "no"));
+ g_message ("\tsends us presence = %s",
+ d->subscribe ? "yes" :
+ (d->subscribe_requested ? "no, but we have requested it" : "no"));
+ g_message ("\tstored = %s", d->stored ? "yes" : "no");
+ g_message ("\tblocked = %s", d->blocked ? "yes" : "no");
+
+ if (d->tags == NULL || tp_handle_set_size (d->tags) == 0)
+ {
+ g_message ("\tnot in any groups");
+ }
+ else
+ {
+ TpIntSet *set = tp_handle_set_peek (d->tags);
+ TpIntSetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, set);
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ g_message ("\tin group: %s",
+ tp_handle_inspect (self->priv->group_repo, member));
+ }
+ }
+ }
+}
+
+gboolean
+example_contact_list_manager_add_to_group (ExampleContactListManager *self,
+ GObject *channel,
+ TpHandle group,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ gboolean updated;
+ ExampleContactDetails *d = ensure_contact (self, member, &updated);
+ ExampleContactList *stored = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+
+ if (d->tags == NULL)
+ d->tags = tp_handle_set_new (self->priv->group_repo);
+
+ if (!tp_handle_set_is_member (d->tags, group))
+ {
+ tp_handle_set_add (d->tags, group);
+ updated = TRUE;
+ }
+
+ if (updated)
+ {
+ TpIntSet *added = tp_intset_new_containing (member);
+
+ d->stored = TRUE;
+
+ send_updated_roster (self, member);
+ tp_group_mixin_change_members (channel, "", added, NULL, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ added, NULL, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (added);
+ }
+
+ return TRUE;
+}
+
+gboolean
+example_contact_list_manager_remove_from_group (
+ ExampleContactListManager *self,
+ GObject *channel,
+ TpHandle group,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ /* If not on the roster or not in any groups, we have nothing to do */
+ if (d == NULL || d->tags == NULL)
+ return TRUE;
+
+ if (tp_handle_set_remove (d->tags, group))
+ {
+ TpIntSet *removed = tp_intset_new_containing (member);
+
+ send_updated_roster (self, member);
+ tp_group_mixin_change_members (channel, "", NULL, removed, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (removed);
+ }
+
+ return TRUE;
+}
+
+typedef struct {
+ ExampleContactListManager *self;
+ TpHandle contact;
+} SelfAndContact;
+
+static SelfAndContact *
+self_and_contact_new (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ SelfAndContact *ret = g_slice_new0 (SelfAndContact);
+
+ ret->self = g_object_ref (self);
+ ret->contact = contact;
+ tp_handle_ref (self->priv->contact_repo, contact);
+ return ret;
+}
+
+static void
+self_and_contact_destroy (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ tp_handle_unref (s->self->priv->contact_repo, s->contact);
+ g_object_unref (s->self);
+ g_slice_free (SelfAndContact, s);
+}
+
+static void
+receive_auth_request (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d;
+ TpIntSet *set;
+ ExampleContactList *publish = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_PUBLISH];
+ ExampleContactList *stored = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+
+ /* if shutting down, do nothing */
+ if (publish == NULL)
+ return;
+
+ /* A remote contact has asked to see our presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has sent us a publish request",
+ tp_handle_inspect (self->priv->contact_repo, contact));
+
+ d = ensure_contact (self, contact, NULL);
+
+ if (d->publish)
+ return;
+
+ d->publish_requested = TRUE;
+ d->stored = TRUE;
+
+ set = tp_intset_new_containing (contact);
+ tp_group_mixin_change_members ((GObject *) publish,
+ "May I see your presence, please?",
+ NULL, NULL, set, NULL,
+ contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+}
+
+static gboolean
+receive_authorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ ExampleContactDetails *d;
+ TpIntSet *set;
+ ExampleContactList *subscribe = s->self->priv->lists[
+ EXAMPLE_CONTACT_LIST_SUBSCRIBE];
+ ExampleContactList *stored = s->self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+
+ /* A remote contact has accepted our request to see their presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has accepted our subscribe request",
+ tp_handle_inspect (s->self->priv->contact_repo, s->contact));
+
+ d = ensure_contact (s->self, s->contact, NULL);
+
+ /* if we were already subscribed to them, then nothing really happened */
+ if (d->subscribe)
+ return FALSE;
+
+ d->subscribe_requested = FALSE;
+ d->subscribe = TRUE;
+ d->stored = TRUE;
+
+ set = tp_intset_new_containing (s->contact);
+ tp_group_mixin_change_members ((GObject *) subscribe, "",
+ set, NULL, NULL, NULL,
+ s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* their presence changes to something other than UNKNOWN */
+ g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+
+ /* if we're not publishing to them, also pretend they have asked us to
+ * do so */
+ if (!d->publish)
+ {
+ receive_auth_request (s->self, s->contact);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+receive_unauthorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ ExampleContactDetails *d;
+ TpIntSet *set;
+ ExampleContactList *subscribe = s->self->priv->lists[
+ EXAMPLE_CONTACT_LIST_SUBSCRIBE];
+
+ /* if shutting down, do nothing */
+ if (subscribe == NULL)
+ return FALSE;
+
+ /* A remote contact has rejected our request to see their presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has rejected our subscribe request",
+ tp_handle_inspect (s->self->priv->contact_repo, s->contact));
+
+ d = ensure_contact (s->self, s->contact, NULL);
+
+ if (!d->subscribe && !d->subscribe_requested)
+ return FALSE;
+
+ d->subscribe_requested = FALSE;
+ d->subscribe = FALSE;
+
+ set = tp_intset_new_containing (s->contact);
+ tp_group_mixin_change_members ((GObject *) subscribe, "Say 'please'!",
+ NULL, set, NULL, NULL,
+ s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* their presence changes to UNKNOWN */
+ g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+
+ return FALSE;
+}
+
+gboolean
+example_contact_list_manager_add_to_list (ExampleContactListManager *self,
+ GObject *channel,
+ ExampleContactListHandle list,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ TpIntSet *set;
+ ExampleContactList *stored = self->priv->lists[EXAMPLE_CONTACT_LIST_STORED];
+
+ switch (list)
+ {
+ case EXAMPLE_CONTACT_LIST_SUBSCRIBE:
+ /* we would like to see member's presence */
+ {
+ gboolean created;
+ ExampleContactDetails *d = ensure_contact (self, member, &created);
+ gchar *message_lc;
+
+ /* if they already authorized us, it's a no-op */
+ if (d->subscribe)
+ return TRUE;
+
+ /* In a real connection manager we'd start a network request here */
+ g_message ("Transmitting authorization request to %s: %s",
+ tp_handle_inspect (self->priv->contact_repo, member),
+ message);
+
+ if (created || !d->subscribe_requested)
+ {
+ d->subscribe_requested = TRUE;
+ d->stored = TRUE;
+ send_updated_roster (self, member);
+ }
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, message,
+ NULL, NULL, NULL, set,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ /* subscribing to someone implicitly puts them on Stored, too */
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* Pretend that after a delay, the contact notices the request
+ * and allows or rejects it. In this example connection manager,
+ * empty requests are allowed, as are requests that contain "please"
+ * case-insensitively. All other requests are denied. */
+ message_lc = g_ascii_strdown (message, -1);
+
+ if (message[0] == '\0' || strstr (message_lc, "please") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay, receive_authorized,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+ else
+ {
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay,
+ receive_unauthorized,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+
+ g_free (message_lc);
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_PUBLISH:
+ /* We would like member to see our presence. This is meaningless,
+ * unless they have asked for it. */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d == NULL || !d->publish_requested)
+ {
+ /* the group mixin won't actually allow this to be reached,
+ * because of the flags we set */
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Can't unilaterally send presence to %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ return FALSE;
+ }
+
+ if (!d->publish)
+ {
+ d->publish = TRUE;
+ d->publish_requested = FALSE;
+ d->stored = TRUE;
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ set, NULL, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_STORED:
+ /* we would like member to be on the roster */
+ {
+ ExampleContactDetails *d = ensure_contact (self, member, NULL);
+
+ d->stored = TRUE;
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ set, NULL, NULL, NULL, self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_DENY:
+ /* We would like member to be blocked */
+ {
+ ExampleContactDetails *d = ensure_contact (self, member, NULL);
+
+ g_message ("Blocking %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+
+ d->blocked = TRUE;
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ set, NULL, NULL, NULL, self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ return TRUE;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+}
+
+static gboolean
+auth_request_cb (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ receive_auth_request (s->self, s->contact);
+
+ return FALSE;
+}
+
+gboolean
+example_contact_list_manager_remove_from_list (ExampleContactListManager *self,
+ GObject *channel,
+ ExampleContactListHandle list,
+ TpHandle member,
+ const gchar *message,
+ GError **error)
+{
+ TpIntSet *set;
+
+ switch (list)
+ {
+ case EXAMPLE_CONTACT_LIST_PUBLISH:
+ /* we would like member not to see our presence any more, or we
+ * would like to reject a request from them to see our presence */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL)
+ {
+ if (d->publish_requested)
+ {
+ g_message ("Rejecting authorization request from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->publish_requested = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ else if (d->publish)
+ {
+ g_message ("Removing authorization from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->publish = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* Pretend that after a delay, the contact notices the change
+ * and asks for our presence again */
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ self->priv->simulation_delay, auth_request_cb,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+ else
+ {
+ /* nothing to do, avoid "updating the roster" */
+ return TRUE;
+ }
+
+ send_updated_roster (self, member);
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_SUBSCRIBE:
+ /* we would like to avoid receiving member's presence any more,
+ * or we would like to cancel an outstanding request for their
+ * presence */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL)
+ {
+ if (d->subscribe_requested)
+ {
+ g_message ("Cancelling our authorization request to %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe_requested = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+ }
+ else if (d->subscribe)
+ {
+ g_message ("We no longer want presence from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe = FALSE;
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ /* since they're no longer on the subscribe list, we can't
+ * see their presence, so emit a signal changing it to
+ * UNKNOWN */
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
+ }
+ else
+ {
+ /* nothing to do, avoid "updating the roster" */
+ return TRUE;
+ }
+
+ send_updated_roster (self, member);
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_STORED:
+ /* we would like to remove member from the roster altogether */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL)
+ {
+ /* if the contact is blocked, do not completely delete it */
+ if (d->blocked)
+ {
+ d->publish = FALSE;
+ d->publish_requested = FALSE;
+ d->subscribe = FALSE;
+ d->subscribe_requested = FALSE;
+ d->stored = FALSE;
+ }
+ else
+ {
+ g_hash_table_remove (self->priv->contact_details,
+ GUINT_TO_POINTER (member));
+ }
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members (
+ (GObject *) self->priv->lists[EXAMPLE_CONTACT_LIST_SUBSCRIBE],
+ "", NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_group_mixin_change_members (
+ (GObject *) self->priv->lists[EXAMPLE_CONTACT_LIST_PUBLISH],
+ "", NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ if (!d->blocked)
+ tp_handle_set_remove (self->priv->contacts, member);
+
+ /* since they're no longer on the subscribe list, we can't
+ * see their presence, so emit a signal changing it to
+ * UNKNOWN */
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
+
+ }
+ }
+ return TRUE;
+
+ case EXAMPLE_CONTACT_LIST_DENY:
+ /* we would like to unblock member */
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL)
+ {
+ g_message ("Unblocking %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+
+ /* if the contact is also not stored, we need to delete it */
+ if (!d->stored)
+ {
+ g_hash_table_remove (self->priv->contact_details,
+ GUINT_TO_POINTER (member));
+ }
+ else
+ {
+ d->blocked = FALSE;
+ }
+
+ send_updated_roster (self, member);
+
+ set = tp_intset_new_containing (member);
+ tp_group_mixin_change_members (channel, "",
+ NULL, set, NULL, NULL,
+ self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+
+ if (!d->stored)
+ tp_handle_set_remove (self->priv->contacts, member);
+ }
+ }
+ return TRUE;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+}
+
+ExampleContactListPresence
+example_contact_list_manager_get_presence (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = lookup_contact (self, contact);
+ const gchar *id;
+
+ if (d == NULL || !d->subscribe)
+ {
+ /* we don't know the presence of people not on the subscribe list,
+ * by definition */
+ return EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN;
+ }
+
+ id = tp_handle_inspect (self->priv->contact_repo, contact);
+
+ /* In this example CM, we fake contacts' presence based on their name:
+ * contacts in the first half of the alphabet are available, the rest
+ * (including non-alphabetic and non-ASCII initial letters) are away. */
+ if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm'))
+ {
+ return EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE;
+ }
+
+ return EXAMPLE_CONTACT_LIST_PRESENCE_AWAY;
+}
+
+const gchar *
+example_contact_list_manager_get_alias (ExampleContactListManager *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = lookup_contact (self, contact);
+
+ if (d == NULL)
+ {
+ /* we don't have a user-defined alias for people not on the roster */
+ return tp_handle_inspect (self->priv->contact_repo, contact);
+ }
+
+ return d->alias;
+}
+
+void
+example_contact_list_manager_set_alias (ExampleContactListManager *self,
+ TpHandle contact,
+ const gchar *alias)
+{
+ gboolean created;
+ ExampleContactDetails *d = ensure_contact (self, contact, &created);
+ ExampleContactList *stored = self->priv->lists[
+ EXAMPLE_CONTACT_LIST_STORED];
+ gchar *old = d->alias;
+ TpIntSet *set;
+
+ /* FIXME: if stored list hasn't been retrieved yet, queue the change for
+ * later */
+
+ /* if shutting down, do nothing */
+ if (stored == NULL)
+ return;
+
+ d->alias = g_strdup (alias);
+ d->stored = TRUE;
+
+ if (created || tp_strdiff (old, alias))
+ send_updated_roster (self, contact);
+
+ g_free (old);
+
+ set = tp_intset_new_containing (contact);
+ tp_group_mixin_change_members ((GObject *) stored, "",
+ set, NULL, NULL, NULL, self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (set);
+}
diff --git a/qt4/tests/lib/glib/contactlist/contact-list-manager.h b/qt4/tests/lib/glib/contactlist/contact-list-manager.h
new file mode 100644
index 000000000..b3087e1b9
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/contact-list-manager.h
@@ -0,0 +1,108 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_MANAGER_H__
+#define __EXAMPLE_CONTACT_LIST_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/handle.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListManager ExampleContactListManager;
+typedef struct _ExampleContactListManagerClass ExampleContactListManagerClass;
+typedef struct _ExampleContactListManagerPrivate ExampleContactListManagerPrivate;
+
+struct _ExampleContactListManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _ExampleContactListManager {
+ GObject parent;
+
+ ExampleContactListManagerPrivate *priv;
+};
+
+GType example_contact_list_manager_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_MANAGER \
+ (example_contact_list_manager_get_type ())
+#define EXAMPLE_CONTACT_LIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST_MANAGER, \
+ ExampleContactListManager))
+#define EXAMPLE_CONTACT_LIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST_MANAGER, \
+ ExampleContactListManagerClass))
+#define EXAMPLE_IS_CONTACT_LIST_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST_MANAGER))
+#define EXAMPLE_IS_CONTACT_LIST_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST_MANAGER))
+#define EXAMPLE_CONTACT_LIST_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_MANAGER, \
+ ExampleContactListManagerClass))
+
+gboolean example_contact_list_manager_add_to_group (
+ ExampleContactListManager *self, GObject *channel,
+ TpHandle group, TpHandle member, const gchar *message, GError **error);
+
+gboolean example_contact_list_manager_remove_from_group (
+ ExampleContactListManager *self, GObject *channel,
+ TpHandle group, TpHandle member, const gchar *message, GError **error);
+
+/* elements 1, 2... of this enum must be kept in sync with elements 0, 1...
+ * of the array _contact_lists in contact-list-manager.h */
+typedef enum {
+ INVALID_EXAMPLE_CONTACT_LIST,
+ EXAMPLE_CONTACT_LIST_SUBSCRIBE = 1,
+ EXAMPLE_CONTACT_LIST_PUBLISH,
+ EXAMPLE_CONTACT_LIST_STORED,
+ EXAMPLE_CONTACT_LIST_DENY,
+ NUM_EXAMPLE_CONTACT_LISTS
+} ExampleContactListHandle;
+
+/* this enum must be kept in sync with the array _statuses in
+ * contact-list-manager.c */
+typedef enum {
+ EXAMPLE_CONTACT_LIST_PRESENCE_OFFLINE = 0,
+ EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN,
+ EXAMPLE_CONTACT_LIST_PRESENCE_ERROR,
+ EXAMPLE_CONTACT_LIST_PRESENCE_AWAY,
+ EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE
+} ExampleContactListPresence;
+
+const TpPresenceStatusSpec *example_contact_list_presence_statuses (
+ void);
+
+gboolean example_contact_list_manager_add_to_list (
+ ExampleContactListManager *self, GObject *channel,
+ ExampleContactListHandle list, TpHandle member, const gchar *message,
+ GError **error);
+
+gboolean example_contact_list_manager_remove_from_list (
+ ExampleContactListManager *self, GObject *channel,
+ ExampleContactListHandle list, TpHandle member, const gchar *message,
+ GError **error);
+
+const gchar **example_contact_lists (void);
+
+ExampleContactListPresence example_contact_list_manager_get_presence (
+ ExampleContactListManager *self, TpHandle contact);
+const gchar *example_contact_list_manager_get_alias (
+ ExampleContactListManager *self, TpHandle contact);
+void example_contact_list_manager_set_alias (
+ ExampleContactListManager *self, TpHandle contact, const gchar *alias);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist/contact-list.c b/qt4/tests/lib/glib/contactlist/contact-list.c
new file mode 100644
index 000000000..82aa6e52e
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/contact-list.c
@@ -0,0 +1,636 @@
+/*
+ * An example ContactList channel with handle type LIST or GROUP
+ *
+ * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contact-list.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/exportable-channel.h>
+#include <telepathy-glib/svc-channel.h>
+
+#include "contact-list-manager.h"
+
+static void channel_iface_init (gpointer iface, gpointer data);
+static void list_channel_iface_init (gpointer iface, gpointer data);
+static void group_channel_iface_init (gpointer iface, gpointer data);
+
+/* Abstract base class */
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListBase, example_contact_list_base,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL))
+
+/* Subclass for handle type LIST */
+G_DEFINE_TYPE_WITH_CODE (ExampleContactList, example_contact_list,
+ EXAMPLE_TYPE_CONTACT_LIST_BASE,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, list_channel_iface_init))
+
+/* Subclass for handle type GROUP */
+G_DEFINE_TYPE_WITH_CODE (ExampleContactGroup, example_contact_group,
+ EXAMPLE_TYPE_CONTACT_LIST_BASE,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, group_channel_iface_init))
+
+static const gchar *contact_list_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_MANAGER,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ N_PROPS
+};
+
+struct _ExampleContactListBasePrivate
+{
+ TpBaseConnection *conn;
+ ExampleContactListManager *manager;
+ gchar *object_path;
+ TpHandleType handle_type;
+ TpHandle handle;
+
+ /* These are really booleans, but gboolean is signed. Thanks, GLib */
+ unsigned closed:1;
+ unsigned disposed:1;
+};
+
+struct _ExampleContactListPrivate
+{
+ int dummy:1;
+};
+
+struct _ExampleContactGroupPrivate
+{
+ int dummy:1;
+};
+
+static void
+example_contact_list_base_init (ExampleContactListBase *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_BASE, ExampleContactListBasePrivate);
+}
+
+static void
+example_contact_list_init (ExampleContactList *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_CONTACT_LIST,
+ ExampleContactListPrivate);
+}
+
+static void
+example_contact_group_init (ExampleContactGroup *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_CONTACT_GROUP,
+ ExampleContactGroupPrivate);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_base_parent_class)->constructed;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle self_handle = self->priv->conn->self_handle;
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles
+ (self->priv->conn, self->priv->handle_type);
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (TP_IS_BASE_CONNECTION (self->priv->conn));
+ g_assert (EXAMPLE_IS_CONTACT_LIST_MANAGER (self->priv->manager));
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn),
+ self->priv->object_path, self);
+
+ tp_handle_ref (handle_repo, self->priv->handle);
+ tp_group_mixin_init (object, G_STRUCT_OFFSET (ExampleContactListBase, group),
+ contact_repo, self_handle);
+ /* Both the subclasses have full support for telepathy-spec 0.17.6. */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+}
+
+static void
+list_constructed (GObject *object)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (self->parent.priv->handle_type == TP_HANDLE_TYPE_LIST);
+
+ switch (self->parent.priv->handle)
+ {
+ case EXAMPLE_CONTACT_LIST_PUBLISH:
+ /* We can stop publishing presence to people, but we can't
+ * start sending people our presence unless they ask for it.
+ *
+ * (We can accept people's requests to see our presence - but that's
+ * always allowed, so there's no flag.)
+ */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
+ break;
+ case EXAMPLE_CONTACT_LIST_STORED:
+ case EXAMPLE_CONTACT_LIST_DENY:
+ /* We can add people to our roster (not that that's very useful without
+ * also adding them to subscribe), and we can remove them altogether
+ * (which implicitly removes them from subscribe, publish, and all
+ * user-defined groups).
+ *
+ * Similarly, we can block and unblock people (i.e. add/remove them
+ * to/from the deny list)
+ */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
+ break;
+ case EXAMPLE_CONTACT_LIST_SUBSCRIBE:
+ /* We can ask people to show us their presence, attaching a message.
+ * We can also cancel (rescind) requests that they haven't replied to,
+ * and stop receiving their presence after they allow it.
+ */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD |
+ TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
+ TP_CHANNEL_GROUP_FLAG_CAN_RESCIND,
+ 0);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+group_constructed (GObject *object)
+{
+ ExampleContactGroup *self = EXAMPLE_CONTACT_GROUP (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_group_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ g_assert (self->parent.priv->handle_type == TP_HANDLE_TYPE_GROUP);
+
+ /* We can add people to user-defined groups, and also remove them. */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0);
+}
+
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, self->priv->handle_type);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ self->priv->conn, self->priv->handle_type);
+
+ g_value_set_string (value,
+ tp_handle_inspect (handle_repo, self->priv->handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, FALSE);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+ case PROP_INITIATOR_ID:
+ g_value_set_static_string (value, "");
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ case PROP_MANAGER:
+ g_value_set_object (value, self->priv->manager);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, contact_list_interfaces);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * repository (or even type) yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ self->priv->handle_type = g_value_get_uint (value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+ case PROP_MANAGER:
+ self->priv->manager = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) example_contact_list_base_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles
+ (self->priv->conn, self->priv->handle_type);
+
+ tp_handle_unref (handle_repo, self->priv->handle);
+ g_free (self->priv->object_path);
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) example_contact_list_base_parent_class)->finalize (object);
+}
+
+static gboolean
+group_add_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_add_to_group (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static gboolean
+group_remove_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_remove_from_group (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static gboolean
+list_add_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_add_to_list (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static gboolean
+list_remove_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (object);
+
+ return example_contact_list_manager_remove_from_list (self->priv->manager,
+ object, self->priv->handle, handle, message, error);
+}
+
+static void
+example_contact_list_base_class_init (ExampleContactListBaseClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListBasePrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_object ("manager", "ExampleContactListManager",
+ "ExampleContactListManager object that owns this channel",
+ EXAMPLE_TYPE_CONTACT_LIST_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MANAGER, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Chatroom's ID",
+ "The string obtained by inspecting the MUC's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListBaseClass, dbus_properties_class));
+
+ /* Group mixin is initialized separately for each subclass - they have
+ * different callbacks */
+}
+
+static void
+example_contact_list_class_init (ExampleContactListClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListPrivate));
+
+ object_class->constructed = list_constructed;
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListBaseClass, group_class),
+ list_add_member,
+ list_remove_member);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+example_contact_group_class_init (ExampleContactGroupClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactGroupPrivate));
+
+ object_class->constructed = group_constructed;
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListBaseClass, group_class),
+ group_add_member,
+ group_remove_member);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+list_channel_close (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "ContactList channels with handle type LIST may not be closed" };
+
+ dbus_g_method_return_error (context, &e);
+}
+
+static void
+group_channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactGroup *self = EXAMPLE_CONTACT_GROUP (iface);
+ ExampleContactListBase *base = EXAMPLE_CONTACT_LIST_BASE (iface);
+
+ if (tp_handle_set_size (base->group.members) > 0)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Non-empty groups may not be deleted (closed)" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (!base->priv->closed)
+ {
+ /* If this was a real connection manager we'd delete the group here,
+ * if such a concept existed in the protocol (in XMPP, it doesn't).
+ *
+ * Afterwards, close the channel:
+ */
+ base->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListBase *self = EXAMPLE_CONTACT_LIST_BASE (iface);
+
+ tp_svc_channel_return_from_get_handle (context, self->priv->handle_type,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ contact_list_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ /* close is implemented in subclasses, so don't IMPLEMENT (close); */
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+list_channel_iface_init (gpointer iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, list_channel_##x)
+ IMPLEMENT (close);
+#undef IMPLEMENT
+}
+
+static void
+group_channel_iface_init (gpointer iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, group_channel_##x)
+ IMPLEMENT (close);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/contactlist/contact-list.h b/qt4/tests/lib/glib/contactlist/contact-list.h
new file mode 100644
index 000000000..e7d2fa3a3
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/contact-list.h
@@ -0,0 +1,118 @@
+/*
+ * Example ContactList channels with handle type LIST or GROUP
+ *
+ * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_CONTACT_LIST_H
+#define EXAMPLE_CONTACT_LIST_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListBase ExampleContactListBase;
+typedef struct _ExampleContactListBaseClass ExampleContactListBaseClass;
+typedef struct _ExampleContactListBasePrivate ExampleContactListBasePrivate;
+
+typedef struct _ExampleContactList ExampleContactList;
+typedef struct _ExampleContactListClass ExampleContactListClass;
+typedef struct _ExampleContactListPrivate ExampleContactListPrivate;
+
+typedef struct _ExampleContactGroup ExampleContactGroup;
+typedef struct _ExampleContactGroupClass ExampleContactGroupClass;
+typedef struct _ExampleContactGroupPrivate ExampleContactGroupPrivate;
+
+GType example_contact_list_base_get_type (void);
+GType example_contact_list_get_type (void);
+GType example_contact_group_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_BASE \
+ (example_contact_list_base_get_type ())
+#define EXAMPLE_CONTACT_LIST_BASE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CONTACT_LIST_BASE, \
+ ExampleContactListBase))
+#define EXAMPLE_CONTACT_LIST_BASE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CONTACT_LIST_BASE, \
+ ExampleContactListBaseClass))
+#define EXAMPLE_IS_CONTACT_LIST_BASE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CONTACT_LIST_BASE))
+#define EXAMPLE_IS_CONTACT_LIST_BASE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CONTACT_LIST_BASE))
+#define EXAMPLE_CONTACT_LIST_BASE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_BASE, \
+ ExampleContactListBaseClass))
+
+#define EXAMPLE_TYPE_CONTACT_LIST \
+ (example_contact_list_get_type ())
+#define EXAMPLE_CONTACT_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactList))
+#define EXAMPLE_CONTACT_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactListClass))
+#define EXAMPLE_IS_CONTACT_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CONTACT_LIST))
+#define EXAMPLE_IS_CONTACT_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CONTACT_LIST))
+#define EXAMPLE_CONTACT_LIST_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactListClass))
+
+#define EXAMPLE_TYPE_CONTACT_GROUP \
+ (example_contact_group_get_type ())
+#define EXAMPLE_CONTACT_GROUP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CONTACT_GROUP, \
+ ExampleContactGroup))
+#define EXAMPLE_CONTACT_GROUP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CONTACT_GROUP, \
+ ExampleContactGroupClass))
+#define EXAMPLE_IS_CONTACT_GROUP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CONTACT_GROUP))
+#define EXAMPLE_IS_CONTACT_GROUP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CONTACT_GROUP))
+#define EXAMPLE_CONTACT_GROUP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_GROUP, \
+ ExampleContactGroupClass))
+
+struct _ExampleContactListBaseClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleContactListClass {
+ ExampleContactListBaseClass parent_class;
+};
+
+struct _ExampleContactGroupClass {
+ ExampleContactListBaseClass parent_class;
+};
+
+struct _ExampleContactListBase {
+ GObject parent;
+ TpGroupMixin group;
+ ExampleContactListBasePrivate *priv;
+};
+
+struct _ExampleContactList {
+ ExampleContactListBase parent;
+ ExampleContactListPrivate *priv;
+};
+
+struct _ExampleContactGroup {
+ ExampleContactListBase parent;
+ ExampleContactGroupPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist/manager-file.py b/qt4/tests/lib/glib/contactlist/manager-file.py
new file mode 100644
index 000000000..499de6296
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist/manager-file.py
@@ -0,0 +1,23 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_contact_list'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'account_param_filter',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ 'simulation-delay': {
+ 'dtype': 'u',
+ 'default': 1000,
+ },
+ },
+ }
+STRUCTS = {
+ 'example': 'ExampleParams'
+ }
diff --git a/qt4/tests/lib/glib/contactlist2/CMakeLists.txt b/qt4/tests/lib/glib/contactlist2/CMakeLists.txt
new file mode 100644
index 000000000..c920d8ed5
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/CMakeLists.txt
@@ -0,0 +1,14 @@
+if(ENABLE_TP_GLIB_TESTS)
+ set(example_cm_contactlist2_SRCS
+ conn.c
+ conn.h
+ connection-manager.c
+ connection-manager.h
+ contact-list.c
+ contact-list.h
+ protocol.c
+ protocol.h)
+
+ add_library(example-cm-contactlist2 STATIC ${example_cm_contactlist2_SRCS})
+ target_link_libraries(example-cm-contactlist2 ${TPGLIB_LIBRARIES})
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/contactlist2/conn.c b/qt4/tests/lib/glib/contactlist2/conn.c
new file mode 100644
index 000000000..354e7fd5a
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/conn.c
@@ -0,0 +1,604 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo-static.h>
+
+#include "contact-list.h"
+#include "protocol.h"
+
+static void init_aliasing (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactListConnection,
+ example_contact_list_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ init_aliasing);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST,
+ tp_base_contact_list_mixin_list_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS,
+ tp_base_contact_list_mixin_groups_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING,
+ tp_base_contact_list_mixin_blocking_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init))
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleContactListConnectionPrivate
+{
+ gchar *account;
+ guint simulation_delay;
+ ExampleContactList *contact_list;
+ gboolean away;
+};
+
+static void
+example_contact_list_connection_init (ExampleContactListConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ ExampleContactListConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_value_dup_string (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (example_contact_list_connection_parent_class)->finalize (
+ object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+
+ return g_strdup_printf ("%s@%p", self->priv->account, self);
+}
+
+gchar *
+example_contact_list_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ gchar *normal = NULL;
+
+ if (example_contact_list_protocol_check_contact_id (id, &normal, error))
+ return normal;
+ else
+ return NULL;
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_contact_list_normalize_contact, NULL);
+}
+
+static void
+alias_updated_cb (ExampleContactList *contact_list,
+ TpHandle contact,
+ ExampleContactListConnection *self)
+{
+ GPtrArray *aliases;
+ GValueArray *pair;
+
+ pair = g_value_array_new (2);
+ g_value_array_append (pair, NULL);
+ g_value_array_append (pair, NULL);
+ g_value_init (pair->values + 0, G_TYPE_UINT);
+ g_value_init (pair->values + 1, G_TYPE_STRING);
+ g_value_set_uint (pair->values + 0, contact);
+ g_value_set_string (pair->values + 1,
+ example_contact_list_get_alias (contact_list, contact));
+
+ aliases = g_ptr_array_sized_new (1);
+ g_ptr_array_add (aliases, pair);
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
+
+ g_ptr_array_free (aliases, TRUE);
+ g_value_array_free (pair);
+}
+
+static void
+presence_updated_cb (ExampleContactList *contact_list,
+ TpHandle contact,
+ ExampleContactListConnection *self)
+{
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpPresenceStatus *status;
+
+ /* we ignore the presence indicated by the contact list for our own handle */
+ if (contact == base->self_handle)
+ return;
+
+ status = tp_presence_status_new (
+ example_contact_list_get_presence (contact_list, contact),
+ NULL);
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self,
+ contact, status);
+ tp_presence_status_free (status);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ self->priv->contact_list = EXAMPLE_CONTACT_LIST (g_object_new (
+ EXAMPLE_TYPE_CONTACT_LIST,
+ "connection", conn,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL));
+
+ g_signal_connect (self->priv->contact_list, "alias-updated",
+ G_CALLBACK (alias_updated_cb), self);
+ g_signal_connect (self->priv->contact_list, "presence-updated",
+ G_CALLBACK (presence_updated_cb), self);
+
+ g_ptr_array_add (ret, self->priv->contact_list);
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleContactListConnection *self = EXAMPLE_CONTACT_LIST_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, error);
+
+ if (conn->self_handle == 0)
+ return FALSE;
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static void
+aliasing_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+
+ tp_contacts_mixin_set_contact_attribute (attributes, contact,
+ TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS,
+ tp_g_value_slice_new_string (
+ example_contact_list_get_alias (self->priv->contact_list,
+ contact)));
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ void (*chain_up) (GObject *) =
+ G_OBJECT_CLASS (example_contact_list_connection_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleContactListConnection, contacts_mixin));
+
+ tp_base_connection_register_with_contacts_mixin (base);
+ tp_base_contact_list_mixin_register_with_contacts_mixin (base);
+
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ aliasing_fill_contact_attributes);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleContactListConnection, presence_mixin));
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
+}
+
+static gboolean
+status_available (GObject *object,
+ guint index_)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+
+ if (base->status != TP_CONNECTION_STATUS_CONNECTED)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GHashTable *
+get_contact_statuses (GObject *object,
+ const GArray *contacts,
+ GError **error)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ guint i;
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+ ExampleContactListPresence presence;
+ GHashTable *parameters;
+
+ /* we get our own status from the connection, and everyone else's status
+ * from the contact lists */
+ if (contact == base->self_handle)
+ {
+ presence = (self->priv->away ? EXAMPLE_CONTACT_LIST_PRESENCE_AWAY
+ : EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE);
+ }
+ else
+ {
+ presence = example_contact_list_get_presence (
+ self->priv->contact_list, contact);
+ }
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ tp_presence_status_new (presence, parameters));
+ g_hash_table_destroy (parameters);
+ }
+
+ return result;
+}
+
+static gboolean
+set_own_status (GObject *object,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ GHashTable *presences;
+
+ if (status->index == EXAMPLE_CONTACT_LIST_PRESENCE_AWAY)
+ {
+ if (self->priv->away)
+ return TRUE;
+
+ self->priv->away = TRUE;
+ }
+ else
+ {
+ if (!self->priv->away)
+ return TRUE;
+
+ self->priv->away = FALSE;
+ }
+
+ presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+ g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle),
+ (gpointer) status);
+ tp_presence_mixin_emit_presence_update (object, presences);
+ g_hash_table_destroy (presences);
+ return TRUE;
+}
+
+static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ NULL };
+
+const gchar * const *
+example_contact_list_connection_get_possible_interfaces (void)
+{
+ /* in this example CM we don't have any extra interfaces that are sometimes,
+ * but not always, present */
+ return interfaces_always_present;
+}
+
+static void
+example_contact_list_connection_class_init (
+ ExampleContactListConnectionClass *klass)
+{
+ TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass,
+ sizeof (ExampleContactListConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListConnectionClass, contacts_mixin));
+
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleContactListConnectionClass, presence_mixin),
+ status_available, get_contact_statuses, set_own_status,
+ example_contact_list_presence_statuses ());
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+
+ tp_base_contact_list_mixin_class_init (base_class);
+}
+
+static void
+get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+ TP_CONNECTION_ALIAS_FLAG_USER_SET);
+}
+
+static void
+get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTable *result;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = example_contact_list_get_alias (
+ self->priv->contact_list, contact);
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (contact),
+ (gchar *) alias);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+ g_hash_table_destroy (result);
+}
+
+static void
+request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GPtrArray *result;
+ gchar **strings;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_ptr_array_sized_new (contacts->len + 1);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = example_contact_list_get_alias (
+ self->priv->contact_list, contact);
+
+ g_ptr_array_add (result, (gchar *) alias);
+ }
+
+ g_ptr_array_add (result, NULL);
+ strings = (gchar **) g_ptr_array_free (result, FALSE);
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+ (const gchar **) strings);
+ g_free (strings);
+}
+
+static void
+set_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ GHashTable *aliases,
+ DBusGMethodInvocation *context)
+{
+ ExampleContactListConnection *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, aliases);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key),
+ &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ g_hash_table_iter_init (&iter, aliases);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ example_contact_list_set_alias (self->priv->contact_list,
+ GPOINTER_TO_UINT (key), value);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
+}
+
+static void
+init_aliasing (gpointer iface,
+ gpointer iface_data G_GNUC_UNUSED)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
+ klass, x)
+ IMPLEMENT(get_alias_flags);
+ IMPLEMENT(request_aliases);
+ IMPLEMENT(get_aliases);
+ IMPLEMENT(set_aliases);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/contactlist2/conn.h b/qt4/tests/lib/glib/contactlist2/conn.h
new file mode 100644
index 000000000..73311d3c5
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/conn.h
@@ -0,0 +1,68 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_CONN_H__
+#define __EXAMPLE_CONTACT_LIST_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListConnection ExampleContactListConnection;
+typedef struct _ExampleContactListConnectionClass
+ ExampleContactListConnectionClass;
+typedef struct _ExampleContactListConnectionPrivate
+ ExampleContactListConnectionPrivate;
+
+struct _ExampleContactListConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+};
+
+struct _ExampleContactListConnection {
+ TpBaseConnection parent;
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ ExampleContactListConnectionPrivate *priv;
+};
+
+GType example_contact_list_connection_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION \
+ (example_contact_list_connection_get_type ())
+#define EXAMPLE_CONTACT_LIST_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnection))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnectionClass))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST_CONNECTION, \
+ ExampleContactListConnectionClass))
+
+gchar *example_contact_list_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id, gpointer context, GError **error);
+
+const gchar * const * example_contact_list_connection_get_possible_interfaces (
+ void);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist2/connection-manager.c b/qt4/tests/lib/glib/contactlist2/connection-manager.c
new file mode 100644
index 000000000..386f61f0b
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/connection-manager.c
@@ -0,0 +1,73 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "conn.h"
+#include "protocol.h"
+
+G_DEFINE_TYPE (ExampleContactListConnectionManager,
+ example_contact_list_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _ExampleContactListConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+example_contact_list_connection_manager_init (
+ ExampleContactListConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER,
+ ExampleContactListConnectionManagerPrivate);
+}
+
+static void
+example_contact_list_connection_manager_constructed (GObject *object)
+{
+ ExampleContactListConnectionManager *self =
+ EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER (object);
+ TpBaseConnectionManager *base = (TpBaseConnectionManager *) self;
+ void (*constructed) (GObject *) =
+ ((GObjectClass *) example_contact_list_connection_manager_parent_class)->constructed;
+ TpBaseProtocol *protocol;
+
+ if (constructed != NULL)
+ constructed (object);
+
+ protocol = g_object_new (EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL,
+ "name", "example",
+ NULL);
+ tp_base_connection_manager_add_protocol (base, protocol);
+ g_object_unref (protocol);
+}
+
+static void
+example_contact_list_connection_manager_class_init (
+ ExampleContactListConnectionManagerClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (ExampleContactListConnectionManagerPrivate));
+
+ object_class->constructed = example_contact_list_connection_manager_constructed;
+ base_class->cm_dbus_name = "example_contact_list";
+}
diff --git a/qt4/tests/lib/glib/contactlist2/connection-manager.h b/qt4/tests/lib/glib/contactlist2/connection-manager.h
new file mode 100644
index 000000000..b99d15963
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/connection-manager.h
@@ -0,0 +1,62 @@
+/*
+ * manager.h - header for an example connection manager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__
+#define __EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListConnectionManager
+ ExampleContactListConnectionManager;
+typedef struct _ExampleContactListConnectionManagerClass
+ ExampleContactListConnectionManagerClass;
+typedef struct _ExampleContactListConnectionManagerPrivate
+ ExampleContactListConnectionManagerPrivate;
+
+struct _ExampleContactListConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+};
+
+struct _ExampleContactListConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleContactListConnectionManagerPrivate *priv;
+};
+
+GType example_contact_list_connection_manager_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER \
+ (example_contact_list_connection_manager_get_type ())
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManager))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManagerClass))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CONTACT_LIST_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER))
+#define EXAMPLE_CONTACT_LIST_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_CONNECTION_MANAGER, \
+ ExampleContactListConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist2/contact-list.c b/qt4/tests/lib/glib/contactlist2/contact-list.c
new file mode 100644
index 000000000..bdefc149f
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/contact-list.c
@@ -0,0 +1,1735 @@
+/*
+ * Example implementation of TpBaseContactList.
+ *
+ * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contact-list.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+/* this array must be kept in sync with the enum
+ * ExampleContactListPresence in contact-list.h */
+static const TpPresenceStatusSpec _statuses[] = {
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
+ { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL },
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL },
+ { NULL }
+};
+
+const TpPresenceStatusSpec *
+example_contact_list_presence_statuses (void)
+{
+ return _statuses;
+}
+
+typedef struct {
+ gchar *alias;
+
+ guint subscribe:1;
+ guint publish:1;
+ guint pre_approved:1;
+ guint subscribe_requested:1;
+ guint subscribe_rejected:1;
+
+ /* string borrowed from priv->all_tags => the same pointer */
+ GHashTable *tags;
+} ExampleContactDetails;
+
+static ExampleContactDetails *
+example_contact_details_new (void)
+{
+ return g_slice_new0 (ExampleContactDetails);
+}
+
+static void
+example_contact_details_destroy (gpointer p)
+{
+ ExampleContactDetails *d = p;
+
+ tp_clear_pointer (&d->tags, g_hash_table_unref);
+
+ g_free (d->alias);
+ g_slice_free (ExampleContactDetails, d);
+}
+
+static void mutable_contact_list_iface_init (TpMutableContactListInterface *);
+static void blockable_contact_list_iface_init (
+ TpBlockableContactListInterface *);
+static void contact_group_list_iface_init (TpContactGroupListInterface *);
+static void mutable_contact_group_list_iface_init (
+ TpMutableContactGroupListInterface *);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleContactList,
+ example_contact_list,
+ TP_TYPE_BASE_CONTACT_LIST,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_BLOCKABLE_CONTACT_LIST,
+ blockable_contact_list_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CONTACT_GROUP_LIST,
+ contact_group_list_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_GROUP_LIST,
+ mutable_contact_group_list_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_LIST,
+ mutable_contact_list_iface_init))
+
+enum
+{
+ ALIAS_UPDATED,
+ PRESENCE_UPDATED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+
+enum
+{
+ PROP_SIMULATION_DELAY = 1,
+ N_PROPS
+};
+
+struct _ExampleContactListPrivate
+{
+ TpBaseConnection *conn;
+ guint simulation_delay;
+ TpHandleRepoIface *contact_repo;
+
+ /* g_strdup (group name) => the same pointer */
+ GHashTable *all_tags;
+
+ /* All contacts on our (fake) protocol-level contact list,
+ * plus all contacts in publish_requests or cancelled_publish_requests */
+ TpHandleSet *contacts;
+
+ /* All contacts on our (fake) protocol-level contact list
+ * GUINT_TO_POINTER (handle borrowed from contacts)
+ * => ExampleContactDetails */
+ GHashTable *contact_details;
+
+ /* Contacts with an outstanding request for presence publication
+ * (may or may not be in contact_details)
+ * handle borrowed from contacts => g_strdup (message) */
+ GHashTable *publish_requests;
+
+ /* Contacts who have requested presence but then cancelled their request
+ * (may or may not be in contact_details) */
+ TpHandleSet *cancelled_publish_requests;
+
+ TpHandleSet *blocked_contacts;
+
+ gulong status_changed_id;
+};
+
+static void
+example_contact_list_init (ExampleContactList *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CONTACT_LIST, ExampleContactListPrivate);
+
+ self->priv->contact_details = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, example_contact_details_destroy);
+ self->priv->publish_requests = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, g_free);
+ self->priv->all_tags = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ /* initialized properly in constructed() */
+ self->priv->contact_repo = NULL;
+ self->priv->contacts = NULL;
+ self->priv->blocked_contacts = NULL;
+ self->priv->cancelled_publish_requests = NULL;
+}
+
+static void
+example_contact_list_close_all (ExampleContactList *self)
+{
+ tp_clear_pointer (&self->priv->contacts, tp_handle_set_destroy);
+ tp_clear_pointer (&self->priv->blocked_contacts, tp_handle_set_destroy);
+ tp_clear_pointer (&self->priv->cancelled_publish_requests,
+ tp_handle_set_destroy);
+ tp_clear_pointer (&self->priv->publish_requests, g_hash_table_unref);
+ tp_clear_pointer (&self->priv->contact_details, g_hash_table_unref);
+ /* this must come after freeing contact_details, because the strings are
+ * borrowed */
+ tp_clear_pointer (&self->priv->all_tags, g_hash_table_unref);
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (object);
+
+ example_contact_list_close_all (self);
+
+ ((GObjectClass *) example_contact_list_parent_class)->dispose (
+ object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (object);
+
+ switch (property_id)
+ {
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (object);
+
+ switch (property_id)
+ {
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static ExampleContactDetails *
+lookup_contact (ExampleContactList *self,
+ TpHandle contact)
+{
+ return g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (contact));
+}
+
+static ExampleContactDetails *
+ensure_contact (ExampleContactList *self,
+ TpHandle contact,
+ gboolean *created)
+{
+ ExampleContactDetails *ret = lookup_contact (self, contact);
+
+ if (ret == NULL)
+ {
+ tp_handle_set_add (self->priv->contacts, contact);
+
+ ret = example_contact_details_new ();
+ ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo,
+ contact));
+
+ g_hash_table_insert (self->priv->contact_details,
+ GUINT_TO_POINTER (contact), ret);
+
+ /* if we already had a publish request from them, then adding them to
+ * the protocol-level contact list doesn't alter the Telepathy contact
+ * list */
+ if (created != NULL)
+ {
+ *created = (g_hash_table_lookup (self->priv->publish_requests,
+ GUINT_TO_POINTER (contact)) == NULL);
+ }
+ }
+ else if (created != NULL)
+ {
+ *created = FALSE;
+ }
+
+ return ret;
+}
+
+static gchar *
+ensure_tag (ExampleContactList *self,
+ const gchar *s,
+ gboolean emit_signal)
+{
+ gchar *r = g_hash_table_lookup (self->priv->all_tags, s);
+
+ if (r == NULL)
+ {
+ g_message ("creating group %s", s);
+ r = g_strdup (s);
+ g_hash_table_insert (self->priv->all_tags, r, r);
+
+ if (emit_signal)
+ tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+ &s, 1);
+ }
+
+ return r;
+}
+
+static void
+example_contact_list_set_contact_groups_async (TpBaseContactList *contact_list,
+ TpHandle contact,
+ const gchar * const *names,
+ gsize n,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ gboolean created;
+ gsize i;
+ ExampleContactDetails *d;
+ GPtrArray *old_names, *new_names;
+ GHashTableIter iter;
+ gpointer k;
+
+ for (i = 0; i < n; i++)
+ ensure_tag (self, names[i], FALSE);
+
+ tp_base_contact_list_groups_created (contact_list, names, n);
+
+ d = ensure_contact (self, contact, &created);
+
+ if (created)
+ tp_base_contact_list_one_contact_changed (contact_list, contact);
+
+ if (d->tags == NULL)
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+
+ old_names = g_ptr_array_sized_new (g_hash_table_size (d->tags));
+ new_names = g_ptr_array_sized_new (n);
+
+ for (i = 0; i < n; i++)
+ {
+ if (g_hash_table_lookup (d->tags, names[i]) == NULL)
+ {
+ gchar *tag = g_hash_table_lookup (self->priv->all_tags, names[i]);
+
+ g_assert (tag != NULL); /* already ensured to exist, above */
+ g_hash_table_insert (d->tags, tag, tag);
+ g_ptr_array_add (new_names, tag);
+ }
+ }
+
+ g_hash_table_iter_init (&iter, d->tags);
+
+ while (g_hash_table_iter_next (&iter, &k, NULL))
+ {
+ for (i = 0; i < n; i++)
+ {
+ if (!tp_strdiff (names[i], k))
+ goto next_hash_element;
+ }
+
+ /* not found in @names, so remove it */
+ g_ptr_array_add (old_names, k);
+ g_hash_table_iter_remove (&iter);
+
+next_hash_element:
+ continue;
+ }
+
+ tp_base_contact_list_one_contact_groups_changed (contact_list, contact,
+ (const gchar * const *) new_names->pdata, new_names->len,
+ (const gchar * const *) old_names->pdata, old_names->len);
+ g_ptr_array_unref (old_names);
+ g_ptr_array_unref (new_names);
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_set_contact_groups_async);
+}
+
+static gboolean
+receive_contact_lists (gpointer p)
+{
+ TpBaseContactList *contact_list = p;
+ ExampleContactList *self = p;
+ TpHandle handle;
+ gchar *cambridge, *montreal, *francophones;
+ ExampleContactDetails *d;
+ GHashTableIter iter;
+ gpointer handle_p;
+
+ if (self->priv->all_tags == NULL)
+ {
+ /* connection already disconnected, so don't process the
+ * "data from the server" */
+ return FALSE;
+ }
+
+ /* In a real CM we'd have received a contact list from the server at this
+ * point. But this isn't a real CM, so we have to make one up... */
+
+ g_message ("Receiving roster from server");
+
+ cambridge = ensure_tag (self, "Cambridge", FALSE);
+ montreal = ensure_tag (self, "Montreal", FALSE);
+ francophones = ensure_tag (self, "Francophones", FALSE);
+
+ /* Add various people who are already subscribing and publishing */
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "sjoerd@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Sjoerd");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (d->tags, cambridge, cambridge);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "guillaume@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Guillaume");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (d->tags, cambridge, cambridge);
+ g_hash_table_insert (d->tags, francophones, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "olivier@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Olivier");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (d->tags, montreal, montreal);
+ g_hash_table_insert (d->tags, francophones, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "travis@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Travis");
+ d->subscribe = TRUE;
+ d->publish = TRUE;
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ /* Add a couple of people whose presence we've requested. They are
+ * remote-pending in subscribe */
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "geraldine@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Géraldine");
+ d->subscribe_requested = TRUE;
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (d->tags, cambridge, cambridge);
+ g_hash_table_insert (d->tags, francophones, francophones);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "helen@example.com",
+ NULL, NULL);
+ d = ensure_contact (self, handle, NULL);
+ g_free (d->alias);
+ d->alias = g_strdup ("Helen");
+ d->subscribe_requested = TRUE;
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (d->tags, cambridge, cambridge);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ /* Receive a couple of authorization requests too. These people are
+ * local-pending in publish; they're not actually on our protocol-level
+ * contact list */
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "wim@example.com",
+ NULL, NULL);
+ tp_handle_set_add (self->priv->contacts, handle);
+ g_hash_table_insert (self->priv->publish_requests,
+ GUINT_TO_POINTER (handle), g_strdup ("I'm more metal than you!"));
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ handle = tp_handle_ensure (self->priv->contact_repo, "christian@example.com",
+ NULL, NULL);
+ tp_handle_set_add (self->priv->contacts, handle);
+ g_hash_table_insert (self->priv->publish_requests,
+ GUINT_TO_POINTER (handle),
+ g_strdup ("I have some fermented herring for you"));
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ /* Add a couple of blocked contacts. */
+ handle = tp_handle_ensure (self->priv->contact_repo, "bill@example.com",
+ NULL, NULL);
+ tp_handle_set_add (self->priv->blocked_contacts, handle);
+ tp_handle_unref (self->priv->contact_repo, handle);
+ handle = tp_handle_ensure (self->priv->contact_repo, "steve@example.com",
+ NULL, NULL);
+ tp_handle_set_add (self->priv->blocked_contacts, handle);
+ tp_handle_unref (self->priv->contact_repo, handle);
+
+ g_hash_table_iter_init (&iter, self->priv->contact_details);
+
+ /* emit initial aliases, presences */
+ while (g_hash_table_iter_next (&iter, &handle_p, NULL))
+ {
+ handle = GPOINTER_TO_UINT (handle_p);
+
+ g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
+ }
+
+ /* ... and off we go */
+ tp_base_contact_list_set_list_received (contact_list);
+
+ return FALSE;
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleContactList *self)
+{
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTED:
+ {
+ /* Do network I/O to get the contact list. This connection manager
+ * doesn't really have a server, so simulate a small network delay
+ * then invent a contact list */
+ tp_base_contact_list_set_list_pending ((TpBaseContactList *) self);
+
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ 2 * self->priv->simulation_delay, receive_contact_lists,
+ g_object_ref (self), g_object_unref);
+ }
+ break;
+
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ {
+ example_contact_list_close_all (self);
+
+ tp_clear_object (&self->priv->conn);
+ }
+ break;
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_contact_list_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ g_object_get (self,
+ "connection", &self->priv->conn,
+ NULL);
+ g_assert (self->priv->conn != NULL);
+
+ self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
+ TP_HANDLE_TYPE_CONTACT);
+ self->priv->contacts = tp_handle_set_new (self->priv->contact_repo);
+ self->priv->blocked_contacts = tp_handle_set_new (self->priv->contact_repo);
+ self->priv->cancelled_publish_requests = tp_handle_set_new (
+ self->priv->contact_repo);
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+send_updated_roster (ExampleContactList *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = g_hash_table_lookup (self->priv->contact_details,
+ GUINT_TO_POINTER (contact));
+ const gchar *request = g_hash_table_lookup (self->priv->publish_requests,
+ GUINT_TO_POINTER (contact));
+ const gchar *identifier = tp_handle_inspect (self->priv->contact_repo,
+ contact);
+
+ /* In a real connection manager, we'd transmit these new details to the
+ * server, rather than just printing messages. */
+
+ if (d == NULL)
+ {
+ g_message ("Deleting contact %s from server", identifier);
+ }
+ else
+ {
+ g_message ("Transmitting new state of contact %s to server", identifier);
+ g_message ("\talias = %s", d->alias);
+ g_message ("\tcan see our presence = %s",
+ d->publish ? "yes" :
+ (request != NULL ? "no, but has requested it" : "no"));
+ g_message ("\tsends us presence = %s",
+ d->subscribe ? "yes" :
+ (d->subscribe_requested ? "no, but we have requested it" :
+ (d->subscribe_rejected ? "no, request refused" : "no")));
+
+ if (d->tags == NULL || g_hash_table_size (d->tags) == 0)
+ {
+ g_message ("\tnot in any groups");
+ }
+ else
+ {
+ GHashTableIter iter;
+ gpointer k;
+
+ g_hash_table_iter_init (&iter, d->tags);
+
+ while (g_hash_table_iter_next (&iter, &k, NULL))
+ {
+ g_message ("\tin group: %s", (gchar *) k);
+ }
+ }
+ }
+}
+
+static void
+example_contact_list_set_group_members_async (TpBaseContactList *contact_list,
+ const gchar *group,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *new_contacts = tp_handle_set_new (self->priv->contact_repo);
+ TpHandleSet *added = tp_handle_set_new (self->priv->contact_repo);
+ TpHandleSet *removed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+ gchar *tag = ensure_tag (self, group, TRUE);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ gboolean created = FALSE, updated = FALSE;
+ ExampleContactDetails *d = ensure_contact (self, member, &created);
+
+ if (created)
+ tp_handle_set_add (new_contacts, member);
+
+ if (d->tags == NULL)
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+
+ if (g_hash_table_lookup (d->tags, tag) == NULL)
+ {
+ g_hash_table_insert (d->tags, tag, tag);
+ updated = TRUE;
+ }
+
+ if (created || updated)
+ {
+ send_updated_roster (self, member);
+ tp_handle_set_add (added, member);
+ }
+ }
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d;
+
+ if (tp_handle_set_is_member (contacts, member))
+ continue;
+
+ d = lookup_contact (self, member);
+
+ if (d != NULL && d->tags != NULL && g_hash_table_remove (d->tags, group))
+ tp_handle_set_add (removed, member);
+ }
+
+ if (!tp_handle_set_is_empty (new_contacts))
+ tp_base_contact_list_contacts_changed (contact_list, new_contacts, NULL);
+
+ if (!tp_handle_set_is_empty (added))
+ tp_base_contact_list_groups_changed (contact_list, added, &group, 1,
+ NULL, 0);
+
+ if (!tp_handle_set_is_empty (removed))
+ tp_base_contact_list_groups_changed (contact_list, removed, NULL, 0,
+ &group, 1);
+
+ tp_handle_set_destroy (added);
+ tp_handle_set_destroy (removed);
+ tp_handle_set_destroy (new_contacts);
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_set_group_members_async);
+}
+
+static void
+example_contact_list_add_to_group_async (TpBaseContactList *contact_list,
+ const gchar *group,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *new_contacts = tp_handle_set_new (self->priv->contact_repo);
+ TpHandleSet *new_to_group = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+ gchar *tag = ensure_tag (self, group, TRUE);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ gboolean created = FALSE, updated = FALSE;
+ ExampleContactDetails *d = ensure_contact (self, member, &created);
+
+ if (created)
+ tp_handle_set_add (new_contacts, member);
+
+ if (d->tags == NULL)
+ d->tags = g_hash_table_new (g_str_hash, g_str_equal);
+
+ if (g_hash_table_lookup (d->tags, tag) == NULL)
+ {
+ g_hash_table_insert (d->tags, tag, tag);
+ updated = TRUE;
+ }
+
+ if (created || updated)
+ {
+ send_updated_roster (self, member);
+ tp_handle_set_add (new_to_group, member);
+ }
+ }
+
+ if (!tp_handle_set_is_empty (new_contacts))
+ tp_base_contact_list_contacts_changed (contact_list, new_contacts, NULL);
+
+ if (!tp_handle_set_is_empty (new_to_group))
+ tp_base_contact_list_groups_changed (contact_list, new_to_group, &group, 1,
+ NULL, 0);
+
+ tp_handle_set_destroy (new_to_group);
+ tp_handle_set_destroy (new_contacts);
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_add_to_group_async);
+}
+
+static void
+example_contact_list_remove_from_group_async (TpBaseContactList *contact_list,
+ const gchar *group,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ /* If not on the roster or not in any groups, we have nothing to do */
+ if (d != NULL && d->tags != NULL && g_hash_table_remove (d->tags, group))
+ {
+ send_updated_roster (self, member);
+ tp_handle_set_add (changed, member);
+ }
+ }
+
+ if (!tp_handle_set_is_empty (changed))
+ tp_base_contact_list_groups_changed (contact_list, changed, NULL, 0,
+ &group, 1);
+
+ tp_handle_set_destroy (changed);
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_remove_from_group_async);
+}
+
+typedef struct {
+ ExampleContactList *self;
+ TpHandle contact;
+} SelfAndContact;
+
+static SelfAndContact *
+self_and_contact_new (ExampleContactList *self,
+ TpHandle contact)
+{
+ SelfAndContact *ret = g_slice_new0 (SelfAndContact);
+
+ ret->self = g_object_ref (self);
+ ret->contact = tp_handle_ref (self->priv->contact_repo, contact);
+ return ret;
+}
+
+static void
+self_and_contact_destroy (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ tp_handle_unref (s->self->priv->contact_repo, s->contact);
+ g_object_unref (s->self);
+ g_slice_free (SelfAndContact, s);
+}
+
+static void
+receive_auth_request (ExampleContactList *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d;
+
+ /* if shutting down, do nothing */
+ if (self->priv->conn == NULL)
+ return;
+
+ /* A remote contact has asked to see our presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has sent us a publish request",
+ tp_handle_inspect (self->priv->contact_repo, contact));
+
+ d = lookup_contact (self, contact);
+
+ if (d != NULL && d->publish)
+ return;
+
+ if (d != NULL && d->pre_approved)
+ {
+ /* the user already said yes, no need to signal anything */
+ g_message ("... this publish request was already approved");
+ d->pre_approved = FALSE;
+ d->publish = TRUE;
+ g_hash_table_remove (self->priv->publish_requests,
+ GUINT_TO_POINTER (contact));
+ tp_handle_set_remove (self->priv->cancelled_publish_requests, contact);
+ send_updated_roster (self, contact);
+ }
+ else
+ {
+ tp_handle_set_add (self->priv->contacts, contact);
+ g_hash_table_insert (self->priv->publish_requests,
+ GUINT_TO_POINTER (contact),
+ g_strdup ("May I see your presence, please?"));
+ }
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ contact);
+
+ /* If the contact has a name ending with "@cancel.something", they
+ * immediately take it back; this is mainly for the regression test. */
+ if (strstr (tp_handle_inspect (self->priv->contact_repo, contact),
+ "@cancel.") != NULL)
+ {
+ g_message ("From server: %s has cancelled their publish request",
+ tp_handle_inspect (self->priv->contact_repo, contact));
+
+ d->publish = FALSE;
+ d->pre_approved = FALSE;
+ g_hash_table_remove (self->priv->publish_requests,
+ GUINT_TO_POINTER (contact));
+ tp_handle_set_add (self->priv->cancelled_publish_requests, contact);
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) self,
+ contact);
+ }
+}
+
+static gboolean
+receive_authorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ ExampleContactDetails *d;
+
+ /* if shutting down, do nothing */
+ if (s->self->priv->conn == NULL)
+ return FALSE;
+
+ /* A remote contact has accepted our request to see their presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has accepted our subscribe request",
+ tp_handle_inspect (s->self->priv->contact_repo, s->contact));
+
+ d = ensure_contact (s->self, s->contact, NULL);
+
+ /* if we were already subscribed to them, then nothing really happened */
+ if (d->subscribe)
+ return FALSE;
+
+ /* DITTO, if our subscription request was cancelled in the meantime */
+ if (!d->subscribe_requested)
+ return FALSE;
+
+ d->subscribe_requested = FALSE;
+ d->subscribe_rejected = FALSE;
+ d->subscribe = TRUE;
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) s->self,
+ s->contact);
+
+ /* their presence changes to something other than UNKNOWN */
+ g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+
+ /* if we're not publishing to them, also pretend they have asked us to
+ * do so */
+ if (!d->publish)
+ {
+ receive_auth_request (s->self, s->contact);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+receive_unauthorized (gpointer p)
+{
+ SelfAndContact *s = p;
+ ExampleContactDetails *d;
+
+ /* if shutting down, do nothing */
+ if (s->self->priv->conn == NULL)
+ return FALSE;
+
+ /* A remote contact has rejected our request to see their presence.
+ *
+ * In a real connection manager this would be the result of incoming
+ * data from the server. */
+
+ g_message ("From server: %s has rejected our subscribe request",
+ tp_handle_inspect (s->self->priv->contact_repo, s->contact));
+
+ d = ensure_contact (s->self, s->contact, NULL);
+
+ if (!d->subscribe && !d->subscribe_requested)
+ return FALSE;
+
+ d->subscribe_requested = FALSE;
+ d->subscribe_rejected = TRUE;
+ d->subscribe = FALSE;
+
+ tp_base_contact_list_one_contact_changed ((TpBaseContactList *) s->self,
+ s->contact);
+
+ /* their presence changes to UNKNOWN */
+ g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+
+ return FALSE;
+}
+
+static gboolean
+auth_request_cb (gpointer p)
+{
+ SelfAndContact *s = p;
+
+ receive_auth_request (s->self, s->contact);
+
+ return FALSE;
+}
+
+ExampleContactListPresence
+example_contact_list_get_presence (ExampleContactList *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = lookup_contact (self, contact);
+ const gchar *id;
+
+ if (d == NULL || !d->subscribe)
+ {
+ /* we don't know the presence of people not on the subscribe list,
+ * by definition */
+ return EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN;
+ }
+
+ id = tp_handle_inspect (self->priv->contact_repo, contact);
+
+ /* In this example CM, we fake contacts' presence based on their name:
+ * contacts in the first half of the alphabet are available, the rest
+ * (including non-alphabetic and non-ASCII initial letters) are away. */
+ if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm'))
+ {
+ return EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE;
+ }
+
+ return EXAMPLE_CONTACT_LIST_PRESENCE_AWAY;
+}
+
+const gchar *
+example_contact_list_get_alias (ExampleContactList *self,
+ TpHandle contact)
+{
+ ExampleContactDetails *d = lookup_contact (self, contact);
+
+ if (d == NULL)
+ {
+ /* we don't have a user-defined alias for people not on the roster */
+ return tp_handle_inspect (self->priv->contact_repo, contact);
+ }
+
+ return d->alias;
+}
+
+void
+example_contact_list_set_alias (ExampleContactList *self,
+ TpHandle contact,
+ const gchar *alias)
+{
+ gboolean created;
+ ExampleContactDetails *d;
+ gchar *old;
+
+ /* if shutting down, do nothing */
+ if (self->priv->conn == NULL)
+ return;
+
+ d = ensure_contact (self, contact, &created);
+
+ if (created)
+ {
+ tp_base_contact_list_one_contact_changed (
+ (TpBaseContactList *) self, contact);
+ }
+
+ /* FIXME: if stored list hasn't been retrieved yet, queue the change for
+ * later */
+
+ old = d->alias;
+ d->alias = g_strdup (alias);
+
+ if (created || tp_strdiff (old, alias))
+ send_updated_roster (self, contact);
+
+ g_free (old);
+}
+
+static TpHandleSet *
+example_contact_list_dup_contacts (TpBaseContactList *contact_list)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+
+ return tp_handle_set_copy (self->priv->contacts);
+}
+
+static TpHandleSet *
+example_contact_list_dup_group_members (TpBaseContactList *contact_list,
+ const gchar *group)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpIntsetFastIter iter;
+ TpHandle member;
+ TpHandleSet *members = tp_handle_set_new (self->priv->contact_repo);
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL && d->tags != NULL &&
+ g_hash_table_lookup_extended (d->tags, group, NULL, NULL))
+ tp_handle_set_add (members, member);
+ }
+
+ return members;
+}
+
+static const ExampleContactDetails no_details = {
+ NULL,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ NULL
+};
+
+static inline TpSubscriptionState
+compose_presence (gboolean full,
+ gboolean ask,
+ gboolean removed_remotely)
+{
+ if (full)
+ return TP_SUBSCRIPTION_STATE_YES;
+ else if (ask)
+ return TP_SUBSCRIPTION_STATE_ASK;
+ else if (removed_remotely)
+ return TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY;
+ else
+ return TP_SUBSCRIPTION_STATE_NO;
+}
+
+static void
+example_contact_list_dup_states (TpBaseContactList *contact_list,
+ TpHandle contact,
+ TpSubscriptionState *subscribe,
+ TpSubscriptionState *publish,
+ gchar **publish_request)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ const ExampleContactDetails *details = lookup_contact (self, contact);
+ const gchar *request = g_hash_table_lookup (self->priv->publish_requests,
+ GUINT_TO_POINTER (contact));
+
+ if (details == NULL)
+ details = &no_details;
+
+ if (subscribe != NULL)
+ *subscribe = compose_presence (details->subscribe,
+ details->subscribe_requested, details->subscribe_rejected);
+
+ if (publish != NULL)
+ *publish = compose_presence (details->publish, (request != NULL),
+ tp_handle_set_is_member (self->priv->cancelled_publish_requests,
+ contact));
+
+ if (publish_request != NULL)
+ *publish_request = g_strdup (request);
+}
+
+static void
+example_contact_list_request_subscription_async (
+ TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ const gchar *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ gboolean created;
+ ExampleContactDetails *d = ensure_contact (self, member, &created);
+ gchar *message_lc;
+
+ /* if they already authorized us, it's a no-op */
+ if (d->subscribe)
+ continue;
+
+ /* In a real connection manager we'd start a network request here */
+ g_message ("Transmitting authorization request to %s: %s",
+ tp_handle_inspect (self->priv->contact_repo, member),
+ message);
+
+ tp_handle_set_add (changed, member);
+ d->subscribe_rejected = FALSE;
+ d->subscribe_requested = TRUE;
+ send_updated_roster (self, member);
+
+ /* Pretend that after a delay, the contact notices the request
+ * and allows or rejects it. In this example connection manager,
+ * empty requests are allowed, as are requests that contain "please"
+ * case-insensitively. All other requests are denied. */
+ message_lc = g_ascii_strdown (message, -1);
+
+ if (message[0] == '\0' || strstr (message_lc, "please") != NULL)
+ {
+ g_timeout_add_full (G_PRIORITY_LOW,
+ self->priv->simulation_delay, receive_authorized,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+ else
+ {
+ g_timeout_add_full (G_PRIORITY_LOW,
+ self->priv->simulation_delay,
+ receive_unauthorized,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+
+ g_free (message_lc);
+ }
+
+ tp_base_contact_list_contacts_changed (contact_list, changed, NULL);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_request_subscription_async);
+}
+
+static void
+example_contact_list_authorize_publication_async (
+ TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d = ensure_contact (self, member, NULL);
+ const gchar *request = g_hash_table_lookup (self->priv->publish_requests,
+ GUINT_TO_POINTER (member));
+
+ if (tp_handle_set_remove (self->priv->cancelled_publish_requests,
+ member))
+ tp_handle_set_add (changed, member);
+
+ /* We would like member to see our presence. In this simulated protocol,
+ * this is meaningless, unless they have asked for it; but we can still
+ * remember the pre-authorization in case they ask later. */
+ if (request == NULL)
+ {
+ d->pre_approved = TRUE;
+ }
+ else if (!d->publish)
+ {
+ d->publish = TRUE;
+ g_hash_table_remove (self->priv->publish_requests,
+ GUINT_TO_POINTER (member));
+ send_updated_roster (self, member);
+ tp_handle_set_add (changed, member);
+ }
+ }
+
+ tp_base_contact_list_contacts_changed (contact_list, changed, NULL);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_authorize_publication_async);
+}
+
+static void
+example_contact_list_store_contacts_async (
+ TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ /* We would like member to be on the roster, but nothing more. */
+
+ if (lookup_contact (self, member) == NULL)
+ {
+ gboolean created;
+
+ ensure_contact (self, member, &created);
+ send_updated_roster (self, member);
+
+ /* If we'd had a publish request from this member, then adding them
+ * to the protocol-level contact list doesn't actually cause a
+ * state change visible on Telepathy. */
+ if (created)
+ tp_handle_set_add (changed, member);
+ }
+ }
+
+ tp_base_contact_list_contacts_changed (contact_list, changed, NULL);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_store_contacts_async);
+}
+
+static void
+example_contact_list_remove_contacts_async (TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *removed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ /* we would like to remove member from the roster altogether */
+ if (lookup_contact (self, member) != NULL
+ || g_hash_table_lookup (self->priv->publish_requests,
+ GUINT_TO_POINTER (member)) != NULL
+ || tp_handle_set_is_member (self->priv->cancelled_publish_requests,
+ member))
+ {
+ tp_handle_set_add (removed, member);
+
+ g_hash_table_remove (self->priv->contact_details,
+ GUINT_TO_POINTER (member));
+ g_hash_table_remove (self->priv->publish_requests,
+ GUINT_TO_POINTER (member));
+ tp_handle_set_remove (self->priv->contacts, member);
+ tp_handle_set_remove (self->priv->cancelled_publish_requests,
+ member);
+
+ send_updated_roster (self, member);
+
+ /* since they're no longer on the subscribe list, we can't
+ * see their presence, so emit a signal changing it to
+ * UNKNOWN */
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
+ }
+ }
+
+ tp_base_contact_list_contacts_changed (contact_list, NULL, removed);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_remove_contacts_async);
+}
+
+static void
+example_contact_list_unsubscribe_async (TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ /* we would like to avoid receiving member's presence any more,
+ * or we would like to cancel an outstanding request for their
+ * presence */
+
+ if (d != NULL)
+ {
+ if (d->subscribe_requested)
+ {
+ g_message ("Cancelling our authorization request to %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe_requested = FALSE;
+
+ tp_handle_set_add (changed, member);
+ send_updated_roster (self, member);
+ }
+ else if (d->subscribe_rejected)
+ {
+ g_message ("Forgetting rejected authorization request to %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe_rejected = FALSE;
+
+ tp_handle_set_add (changed, member);
+ send_updated_roster (self, member);
+ }
+ else if (d->subscribe)
+ {
+ g_message ("We no longer want presence from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->subscribe = FALSE;
+
+ /* since they're no longer on the subscribe list, we can't
+ * see their presence, so emit a signal changing it to
+ * UNKNOWN */
+ g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
+
+ tp_handle_set_add (changed, member);
+ send_updated_roster (self, member);
+ }
+ }
+ }
+
+ tp_base_contact_list_contacts_changed (contact_list, changed, NULL);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_unsubscribe_async);
+}
+
+static void
+example_contact_list_unpublish_async (TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpHandleSet *removed = tp_handle_set_new (self->priv->contact_repo);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+ const gchar *request = g_hash_table_lookup (self->priv->publish_requests,
+ GUINT_TO_POINTER (member));
+
+ /* we would like member not to see our presence any more, or we
+ * would like to reject a request from them to see our presence */
+
+ if (request != NULL)
+ {
+ g_message ("Rejecting authorization request from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ g_hash_table_remove (self->priv->publish_requests,
+ GUINT_TO_POINTER (member));
+
+ if (d == NULL)
+ {
+ /* the contact wasn't actually on our protocol-level contact
+ * list, only on the Telepathy-level contact list, so rejecting
+ * authorization makes them disappear */
+ tp_handle_set_add (removed, member);
+ }
+ else
+ {
+ tp_handle_set_add (changed, member);
+ }
+ }
+
+ if (tp_handle_set_remove (self->priv->cancelled_publish_requests,
+ member))
+ {
+ g_message ("Acknowledging remotely-cancelled publish request");
+ tp_handle_set_add (changed, member);
+ }
+
+ if (d != NULL)
+ {
+ d->pre_approved = FALSE;
+
+ if (d->publish)
+ {
+ g_message ("Removing authorization from %s",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ d->publish = FALSE;
+ tp_handle_set_add (changed, member);
+ send_updated_roster (self, member);
+
+ /* Pretend that after a delay, the contact notices the change
+ * and asks for our presence again */
+ g_timeout_add_full (G_PRIORITY_LOW,
+ self->priv->simulation_delay, auth_request_cb,
+ self_and_contact_new (self, member),
+ self_and_contact_destroy);
+ }
+ }
+ }
+
+ tp_base_contact_list_contacts_changed (contact_list, changed, removed);
+
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_unpublish_async);
+}
+
+static TpHandleSet *
+example_contact_list_dup_blocked_contacts (TpBaseContactList *contact_list)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+
+ return tp_handle_set_copy (self->priv->blocked_contacts);
+}
+
+static void
+example_contact_list_block_contacts_async (TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpIntsetFastIter iter;
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ if (!tp_handle_set_is_member (self->priv->blocked_contacts, member))
+ {
+ g_message ("Adding contact %s to blocked list",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ tp_handle_set_add (self->priv->blocked_contacts, member);
+ tp_handle_set_add (changed, member);
+ }
+ }
+
+ tp_base_contact_list_contact_blocking_changed (contact_list, changed);
+ tp_handle_set_destroy (changed);
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_block_contacts_async);
+}
+
+static void
+example_contact_list_unblock_contacts_async (TpBaseContactList *contact_list,
+ TpHandleSet *contacts,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpIntsetFastIter iter;
+ TpHandleSet *changed = tp_handle_set_new (self->priv->contact_repo);
+ TpHandle member;
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ if (tp_handle_set_remove (self->priv->blocked_contacts, member))
+ {
+ g_message ("Removing contact %s from blocked list",
+ tp_handle_inspect (self->priv->contact_repo, member));
+ tp_handle_set_add (changed, member);
+ }
+ }
+
+ tp_base_contact_list_contact_blocking_changed (contact_list, changed);
+ tp_handle_set_destroy (changed);
+ tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+ user_data, example_contact_list_unblock_contacts_async);
+}
+
+static guint
+example_contact_list_get_group_storage (
+ TpBaseContactList *contact_list G_GNUC_UNUSED)
+{
+ return TP_CONTACT_METADATA_STORAGE_TYPE_ANYONE;
+}
+
+static GStrv
+example_contact_list_dup_groups (TpBaseContactList *contact_list)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ GPtrArray *tags = g_ptr_array_sized_new (
+ g_hash_table_size (self->priv->all_tags) + 1);
+ GHashTableIter iter;
+ gpointer tag;
+
+ g_hash_table_iter_init (&iter, self->priv->all_tags);
+
+ while (g_hash_table_iter_next (&iter, &tag, NULL))
+ g_ptr_array_add (tags, g_strdup (tag));
+
+ g_ptr_array_add (tags, NULL);
+ return (GStrv) g_ptr_array_free (tags, FALSE);
+}
+
+static GStrv
+example_contact_list_dup_contact_groups (TpBaseContactList *contact_list,
+ TpHandle contact)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ GPtrArray *tags = g_ptr_array_sized_new (
+ g_hash_table_size (self->priv->all_tags) + 1);
+ ExampleContactDetails *d = lookup_contact (self, contact);
+
+ if (d != NULL && d->tags != NULL)
+ {
+ GHashTableIter iter;
+ gpointer tag;
+
+ g_hash_table_iter_init (&iter, d->tags);
+
+ while (g_hash_table_iter_next (&iter, &tag, NULL))
+ g_ptr_array_add (tags, g_strdup (tag));
+ }
+
+ g_ptr_array_add (tags, NULL);
+ return (GStrv) g_ptr_array_free (tags, FALSE);
+}
+
+static void
+example_contact_list_remove_group_async (TpBaseContactList *contact_list,
+ const gchar *group,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ TpIntsetFastIter iter;
+ TpHandle member;
+
+ /* signal the deletion */
+ g_message ("deleting group %s", group);
+ tp_base_contact_list_groups_removed (contact_list, &group, 1);
+
+ /* apply the change to our model of the contacts too; we don't need to signal
+ * the change, because TpBaseContactList already did */
+
+ tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->contacts));
+
+ while (tp_intset_fast_iter_next (&iter, &member))
+ {
+ ExampleContactDetails *d = lookup_contact (self, member);
+
+ if (d != NULL && d->tags != NULL)
+ g_hash_table_remove (d->tags, group);
+ }
+
+ tp_simple_async_report_success_in_idle ((GObject *) contact_list, callback,
+ user_data, example_contact_list_remove_group_async);
+}
+
+static gchar *
+example_contact_list_normalize_group (TpBaseContactList *contact_list,
+ const gchar *id)
+{
+ if (id[0] == '\0')
+ return NULL;
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static void
+example_contact_list_rename_group_async (TpBaseContactList *contact_list,
+ const gchar *old_name,
+ const gchar *new_name,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ExampleContactList *self = EXAMPLE_CONTACT_LIST (contact_list);
+ gchar *tag = ensure_tag (self, new_name, FALSE);
+ GHashTableIter iter;
+ gpointer v;
+
+ /* signal the rename */
+ g_print ("renaming group %s to %s", old_name, new_name);
+ tp_base_contact_list_group_renamed (contact_list, old_name, new_name);
+
+ /* update our model (this doesn't need to signal anything because
+ * TpBaseContactList already did) */
+
+ g_hash_table_iter_init (&iter, self->priv->contact_details);
+
+ while (g_hash_table_iter_next (&iter, NULL, &v))
+ {
+ ExampleContactDetails *d = v;
+
+ if (d->tags != NULL && g_hash_table_remove (d->tags, old_name))
+ g_hash_table_insert (d->tags, tag, tag);
+ }
+
+ tp_simple_async_report_success_in_idle ((GObject *) contact_list, callback,
+ user_data, example_contact_list_rename_group_async);
+}
+
+static void
+example_contact_list_class_init (ExampleContactListClass *klass)
+{
+ TpBaseContactListClass *contact_list_class =
+ (TpBaseContactListClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 1000,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ contact_list_class->dup_contacts = example_contact_list_dup_contacts;
+ contact_list_class->dup_states = example_contact_list_dup_states;
+ /* for this example CM we pretend there is a server-stored contact list,
+ * like in XMPP, even though there obviously isn't really */
+ contact_list_class->get_contact_list_persists =
+ tp_base_contact_list_true_func;
+
+ g_type_class_add_private (klass, sizeof (ExampleContactListPrivate));
+
+ signals[ALIAS_UPDATED] = g_signal_new ("alias-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+}
+
+static void
+mutable_contact_list_iface_init (TpMutableContactListInterface *iface)
+{
+ iface->can_change_contact_list = tp_base_contact_list_true_func;
+ iface->get_request_uses_message = tp_base_contact_list_true_func;
+ iface->request_subscription_async =
+ example_contact_list_request_subscription_async;
+ iface->authorize_publication_async =
+ example_contact_list_authorize_publication_async;
+ iface->store_contacts_async = example_contact_list_store_contacts_async;
+ iface->remove_contacts_async = example_contact_list_remove_contacts_async;
+ iface->unsubscribe_async = example_contact_list_unsubscribe_async;
+ iface->unpublish_async = example_contact_list_unpublish_async;
+}
+
+static void
+blockable_contact_list_iface_init (TpBlockableContactListInterface *iface)
+{
+ iface->can_block = tp_base_contact_list_true_func;
+ iface->dup_blocked_contacts = example_contact_list_dup_blocked_contacts;
+ iface->block_contacts_async = example_contact_list_block_contacts_async;
+ iface->unblock_contacts_async = example_contact_list_unblock_contacts_async;
+}
+
+static void
+contact_group_list_iface_init (TpContactGroupListInterface *iface)
+{
+ iface->dup_groups = example_contact_list_dup_groups;
+ iface->dup_group_members = example_contact_list_dup_group_members;
+ iface->dup_contact_groups = example_contact_list_dup_contact_groups;
+ iface->normalize_group = example_contact_list_normalize_group;
+}
+
+static void
+mutable_contact_group_list_iface_init (
+ TpMutableContactGroupListInterface *iface)
+{
+ iface->set_group_members_async =
+ example_contact_list_set_group_members_async;
+ iface->add_to_group_async = example_contact_list_add_to_group_async;
+ iface->remove_from_group_async =
+ example_contact_list_remove_from_group_async;
+ iface->remove_group_async = example_contact_list_remove_group_async;
+ iface->rename_group_async = example_contact_list_rename_group_async;
+ iface->set_contact_groups_async =
+ example_contact_list_set_contact_groups_async;
+ iface->get_group_storage = example_contact_list_get_group_storage;
+}
diff --git a/qt4/tests/lib/glib/contactlist2/contact-list.h b/qt4/tests/lib/glib/contactlist2/contact-list.h
new file mode 100644
index 000000000..06f83bd84
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/contact-list.h
@@ -0,0 +1,78 @@
+/*
+ * Example channel manager for contact lists
+ *
+ * Copyright © 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CONTACT_LIST_H__
+#define __EXAMPLE_CONTACT_LIST_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-contact-list.h>
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/handle.h>
+#include <telepathy-glib/presence-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactList ExampleContactList;
+typedef struct _ExampleContactListClass ExampleContactListClass;
+typedef struct _ExampleContactListPrivate ExampleContactListPrivate;
+
+struct _ExampleContactListClass {
+ TpBaseContactListClass parent_class;
+};
+
+struct _ExampleContactList {
+ TpBaseContactList parent;
+
+ ExampleContactListPrivate *priv;
+};
+
+GType example_contact_list_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST \
+ (example_contact_list_get_type ())
+#define EXAMPLE_CONTACT_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactList))
+#define EXAMPLE_CONTACT_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactListClass))
+#define EXAMPLE_IS_CONTACT_LIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CONTACT_LIST))
+#define EXAMPLE_IS_CONTACT_LIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CONTACT_LIST))
+#define EXAMPLE_CONTACT_LIST_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CONTACT_LIST, \
+ ExampleContactListClass))
+
+/* this enum must be kept in sync with the array _statuses in
+ * contact-list.c */
+typedef enum {
+ EXAMPLE_CONTACT_LIST_PRESENCE_OFFLINE = 0,
+ EXAMPLE_CONTACT_LIST_PRESENCE_UNKNOWN,
+ EXAMPLE_CONTACT_LIST_PRESENCE_ERROR,
+ EXAMPLE_CONTACT_LIST_PRESENCE_AWAY,
+ EXAMPLE_CONTACT_LIST_PRESENCE_AVAILABLE
+} ExampleContactListPresence;
+
+const TpPresenceStatusSpec *example_contact_list_presence_statuses (
+ void);
+
+ExampleContactListPresence example_contact_list_get_presence (
+ ExampleContactList *self, TpHandle contact);
+const gchar *example_contact_list_get_alias (
+ ExampleContactList *self, TpHandle contact);
+void example_contact_list_set_alias (
+ ExampleContactList *self, TpHandle contact, const gchar *alias);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contactlist2/example_contact_list.manager b/qt4/tests/lib/glib/contactlist2/example_contact_list.manager
new file mode 100644
index 000000000..379d822b4
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/example_contact_list.manager
@@ -0,0 +1,23 @@
+[ConnectionManager]
+Interfaces=
+
+[Protocol example]
+Interfaces=
+ConnectionInterfaces=org.freedesktop.Telepathy.Connection.Interface.Requests;org.freedesktop.Telepathy.Connection.Interface.Contacts;org.freedesktop.Telepathy.Connection.Interface.Presence;org.freedesktop.Telepathy.Connection.Interface.SimplePresence;
+param-account=s required register
+param-simulation-delay=u
+default-simulation-delay=1000
+RequestableChannelClasses=contactlist;contactgroup;
+VCardField=x-telepathy-example
+EnglishName=Example with a contact list
+Icon=face-smile
+
+[contactlist]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.ContactList
+org.freedesktop.Telepathy.Channel.TargetHandleType u=3
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
+
+[contactgroup]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.ContactList
+org.freedesktop.Telepathy.Channel.TargetHandleType u=4
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
diff --git a/qt4/tests/lib/glib/contactlist2/protocol.c b/qt4/tests/lib/glib/contactlist2/protocol.c
new file mode 100644
index 000000000..66fc200f9
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/protocol.c
@@ -0,0 +1,186 @@
+/*
+ * protocol.c - an example Protocol
+ *
+ * Copyright © 2007-2010 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "protocol.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "conn.h"
+#include "contact-list.h"
+
+G_DEFINE_TYPE (ExampleContactListProtocol,
+ example_contact_list_protocol,
+ TP_TYPE_BASE_PROTOCOL)
+
+static void
+example_contact_list_protocol_init (
+ ExampleContactListProtocol *self)
+{
+}
+
+gboolean
+example_contact_list_protocol_check_contact_id (const gchar *id,
+ gchar **normal,
+ GError **error)
+{
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must not be empty");
+ return FALSE;
+ }
+
+ if (normal != NULL)
+ *normal = g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+
+ return TRUE;
+}
+
+static gboolean
+account_param_filter (const TpCMParamSpec *paramspec,
+ GValue *value,
+ GError **error)
+{
+ const gchar *id = g_value_get_string (value);
+
+ return example_contact_list_protocol_check_contact_id (id, NULL, error);
+}
+
+static const TpCMParamSpec example_contact_list_example_params[] = {
+ { "account", "s", G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER,
+ NULL, /* no default */
+ 0, /* unused, formerly struct offset */
+ account_param_filter,
+ NULL, /* filter data, unused here */
+ NULL }, /* setter data, now unused */
+ { "simulation-delay", "u", G_TYPE_UINT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT,
+ GUINT_TO_POINTER (1000), /* default */
+ 0, /* unused, formerly struct offset */
+ NULL, /* no filter */
+ NULL, /* filter data, unused here */
+ NULL }, /* setter data, now unused */
+ { NULL }
+};
+
+static const TpCMParamSpec *
+get_parameters (TpBaseProtocol *self)
+{
+ return example_contact_list_example_params;
+}
+
+static TpBaseConnection *
+new_connection (TpBaseProtocol *protocol,
+ GHashTable *asv,
+ GError **error)
+{
+ ExampleContactListConnection *conn;
+ const gchar *account;
+ guint sim_delay;
+
+ account = tp_asv_get_string (asv, "account");
+ /* telepathy-glib checked this for us */
+ g_assert (account != NULL);
+
+ sim_delay = tp_asv_get_uint32 (asv, "simulation-delay", NULL);
+
+ conn = EXAMPLE_CONTACT_LIST_CONNECTION (
+ g_object_new (EXAMPLE_TYPE_CONTACT_LIST_CONNECTION,
+ "account", account,
+ "protocol", tp_base_protocol_get_name (protocol),
+ "simulation-delay", sim_delay,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static gchar *
+normalize_contact (TpBaseProtocol *self G_GNUC_UNUSED,
+ const gchar *contact,
+ GError **error)
+{
+ gchar *normal;
+
+ if (example_contact_list_protocol_check_contact_id (contact, &normal, error))
+ return normal;
+ else
+ return NULL;
+}
+
+static gchar *
+identify_account (TpBaseProtocol *self G_GNUC_UNUSED,
+ GHashTable *asv,
+ GError **error)
+{
+ const gchar *account = tp_asv_get_string (asv, "account");
+
+ if (account != NULL)
+ return normalize_contact (self, account, error);
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "'account' parameter not given");
+ return NULL;
+}
+
+static GStrv
+get_interfaces (TpBaseProtocol *self)
+{
+ return NULL;
+}
+
+static void
+get_connection_details (TpBaseProtocol *self G_GNUC_UNUSED,
+ GStrv *connection_interfaces,
+ GType **channel_managers,
+ gchar **icon_name,
+ gchar **english_name,
+ gchar **vcard_field)
+{
+ if (connection_interfaces != NULL)
+ {
+ *connection_interfaces = g_strdupv (
+ (GStrv) example_contact_list_connection_get_possible_interfaces ());
+ }
+
+ if (channel_managers != NULL)
+ {
+ GType types[] = { EXAMPLE_TYPE_CONTACT_LIST, G_TYPE_INVALID };
+
+ *channel_managers = g_memdup (types, sizeof (types));
+ }
+
+ if (icon_name != NULL)
+ *icon_name = g_strdup ("face-smile");
+
+ if (english_name != NULL)
+ *english_name = g_strdup ("Example with a contact list");
+
+ if (vcard_field != NULL)
+ *vcard_field = g_strdup ("x-telepathy-example");
+}
+
+static void
+example_contact_list_protocol_class_init (
+ ExampleContactListProtocolClass *klass)
+{
+ TpBaseProtocolClass *base_class =
+ (TpBaseProtocolClass *) klass;
+
+ base_class->get_parameters = get_parameters;
+ base_class->new_connection = new_connection;
+
+ base_class->normalize_contact = normalize_contact;
+ base_class->identify_account = identify_account;
+ base_class->get_interfaces = get_interfaces;
+ base_class->get_connection_details = get_connection_details;
+}
diff --git a/qt4/tests/lib/glib/contactlist2/protocol.h b/qt4/tests/lib/glib/contactlist2/protocol.h
new file mode 100644
index 000000000..cfa07f79e
--- /dev/null
+++ b/qt4/tests/lib/glib/contactlist2/protocol.h
@@ -0,0 +1,68 @@
+/*
+ * protocol.h - header for an example Protocol
+ * Copyright © 2007-2010 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_CONTACT_LIST_PROTOCOL_H
+#define EXAMPLE_CONTACT_LIST_PROTOCOL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-protocol.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleContactListProtocol
+ ExampleContactListProtocol;
+typedef struct _ExampleContactListProtocolPrivate
+ ExampleContactListProtocolPrivate;
+typedef struct _ExampleContactListProtocolClass
+ ExampleContactListProtocolClass;
+typedef struct _ExampleContactListProtocolClassPrivate
+ ExampleContactListProtocolClassPrivate;
+
+struct _ExampleContactListProtocolClass {
+ TpBaseProtocolClass parent_class;
+
+ ExampleContactListProtocolClassPrivate *priv;
+};
+
+struct _ExampleContactListProtocol {
+ TpBaseProtocol parent;
+
+ ExampleContactListProtocolPrivate *priv;
+};
+
+GType example_contact_list_protocol_get_type (void);
+
+#define EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL \
+ (example_contact_list_protocol_get_type ())
+#define EXAMPLE_CONTACT_LIST_PROTOCOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, \
+ ExampleContactListProtocol))
+#define EXAMPLE_CONTACT_LIST_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, \
+ ExampleContactListProtocolClass))
+#define EXAMPLE_IS_CONTACT_LIST_PROTOCOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL))
+#define EXAMPLE_IS_CONTACT_LIST_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL))
+#define EXAMPLE_CONTACT_LIST_PROTOCOL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EXAMPLE_TYPE_CONTACT_LIST_PROTOCOL, \
+ ExampleContactListProtocolClass))
+
+gboolean example_contact_list_protocol_check_contact_id (const gchar *id,
+ gchar **normal,
+ GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/contacts-conn.c b/qt4/tests/lib/glib/contacts-conn.c
new file mode 100644
index 000000000..8991da3d8
--- /dev/null
+++ b/qt4/tests/lib/glib/contacts-conn.c
@@ -0,0 +1,1335 @@
+/*
+ * contacts-conn.c - connection with contact info
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+#include "contacts-conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/util.h>
+
+#include "debug.h"
+
+static void init_aliasing (gpointer, gpointer);
+static void init_avatars (gpointer, gpointer);
+static void init_location (gpointer, gpointer);
+static void init_contact_caps (gpointer, gpointer);
+static void init_contact_info (gpointer, gpointer);
+static void conn_avatars_properties_getter (GObject *object, GQuark interface,
+ GQuark name, GValue *value, gpointer getter_data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsContactsConnection,
+ tp_tests_contacts_connection,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ init_aliasing);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS,
+ init_avatars);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init)
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_LOCATION,
+ init_location)
+ G_IMPLEMENT_INTERFACE (
+ TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ init_contact_caps)
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_INFO,
+ init_contact_info)
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST,
+ tp_base_contact_list_mixin_list_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS,
+ tp_base_contact_list_mixin_groups_iface_init);
+ );
+
+/* type definition stuff */
+
+static const char *mime_types[] = { "image/png", NULL };
+static TpDBusPropertiesMixinPropImpl conn_avatars_properties[] = {
+ { "MinimumAvatarWidth", GUINT_TO_POINTER (1), NULL },
+ { "MinimumAvatarHeight", GUINT_TO_POINTER (2), NULL },
+ { "RecommendedAvatarWidth", GUINT_TO_POINTER (3), NULL },
+ { "RecommendedAvatarHeight", GUINT_TO_POINTER (4), NULL },
+ { "MaximumAvatarWidth", GUINT_TO_POINTER (5), NULL },
+ { "MaximumAvatarHeight", GUINT_TO_POINTER (6), NULL },
+ { "MaximumAvatarBytes", GUINT_TO_POINTER (7), NULL },
+ /* special-cased - it's the only one with a non-guint value */
+ { "SupportedAvatarMIMETypes", NULL, NULL },
+ { NULL }
+};
+
+enum
+{
+ N_SIGNALS
+};
+
+struct _TpTestsContactsConnectionPrivate
+{
+ /* TpHandle => gchar * */
+ GHashTable *aliases;
+ /* TpHandle => AvatarData */
+ GHashTable *avatars;
+ /* TpHandle => ContactsConnectionPresenceStatusIndex */
+ GHashTable *presence_statuses;
+ /* TpHandle => gchar * */
+ GHashTable *presence_messages;
+ /* TpHandle => GHashTable * */
+ GHashTable *locations;
+ /* TpHandle => GPtrArray * */
+ GHashTable *capabilities;
+ /* TpHandle => GPtrArray * */
+ GHashTable *contact_info;
+ GPtrArray *default_contact_info;
+
+ TestContactListManager *list_manager;
+};
+
+typedef struct
+{
+ GArray *data;
+ gchar *mime_type;
+ gchar *token;
+} AvatarData;
+
+static AvatarData *
+avatar_data_new (GArray *data,
+ const gchar *mime_type,
+ const gchar *token)
+{
+ AvatarData *a;
+
+ a = g_slice_new (AvatarData);
+ a->data = data ? g_array_ref (data) : NULL;
+ a->mime_type = g_strdup (mime_type);
+ a->token = g_strdup (token);
+
+ return a;
+}
+
+static void
+avatar_data_free (gpointer data)
+{
+ AvatarData *a = data;
+
+ if (a != NULL)
+ {
+ if (a->data != NULL)
+ g_array_unref (a->data);
+ g_free (a->mime_type);
+ g_free (a->token);
+ g_slice_free (AvatarData, a);
+ }
+}
+
+static void
+free_rcc_list (GPtrArray *rccs)
+{
+ g_boxed_free (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs);
+}
+
+static void
+tp_tests_contacts_connection_init (TpTestsContactsConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TESTS_TYPE_CONTACTS_CONNECTION,
+ TpTestsContactsConnectionPrivate);
+ self->priv->aliases = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_free);
+ self->priv->avatars = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, avatar_data_free);
+ self->priv->presence_statuses = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, NULL);
+ self->priv->presence_messages = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, g_free);
+ self->priv->locations = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) g_hash_table_unref);
+ self->priv->capabilities = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify) free_rcc_list);
+ self->priv->contact_info = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify) g_ptr_array_unref);
+ self->priv->default_contact_info = (GPtrArray *) dbus_g_type_specialized_construct (
+ TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
+}
+
+static void
+finalize (GObject *object)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+ tp_contacts_mixin_finalize (object);
+ g_hash_table_destroy (self->priv->aliases);
+ g_hash_table_destroy (self->priv->avatars);
+ g_hash_table_destroy (self->priv->presence_statuses);
+ g_hash_table_destroy (self->priv->presence_messages);
+ g_hash_table_destroy (self->priv->locations);
+ g_hash_table_destroy (self->priv->capabilities);
+ g_hash_table_destroy (self->priv->contact_info);
+
+ if (self->priv->default_contact_info != NULL)
+ g_ptr_array_unref (self->priv->default_contact_info);
+
+ G_OBJECT_CLASS (tp_tests_contacts_connection_parent_class)->finalize (object);
+}
+
+static void
+aliasing_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ guint i;
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, guint, i);
+ const gchar *alias = g_hash_table_lookup (self->priv->aliases,
+ GUINT_TO_POINTER (handle));
+
+ if (alias == NULL)
+ {
+ alias = tp_handle_inspect (contact_repo, handle);
+ }
+
+ tp_contacts_mixin_set_contact_attribute (attributes, handle,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING "/alias",
+ tp_g_value_slice_new_string (alias));
+ }
+}
+
+static void
+avatars_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ guint i;
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, guint, i);
+ AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+ GUINT_TO_POINTER (handle));
+
+ if (a != NULL && a->token != NULL)
+ {
+ tp_contacts_mixin_set_contact_attribute (attributes, handle,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS "/token",
+ tp_g_value_slice_new_string (a->token));
+ }
+ }
+}
+
+static void
+location_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ guint i;
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, guint, i);
+ GHashTable *location = g_hash_table_lookup (self->priv->locations,
+ GUINT_TO_POINTER (handle));
+
+ if (location != NULL)
+ {
+ tp_contacts_mixin_set_contact_attribute (attributes, handle,
+ TP_IFACE_CONNECTION_INTERFACE_LOCATION "/location",
+ tp_g_value_slice_new_boxed (TP_HASH_TYPE_LOCATION, location));
+ }
+ }
+}
+
+static void
+contact_caps_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ guint i;
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, guint, i);
+ GPtrArray *caps = g_hash_table_lookup (self->priv->capabilities,
+ GUINT_TO_POINTER (handle));
+
+ if (caps != NULL)
+ {
+ tp_contacts_mixin_set_contact_attribute (attributes, handle,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES "/capabilities",
+ tp_g_value_slice_new_boxed (
+ TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, caps));
+ }
+ }
+}
+
+static void
+contact_info_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ guint i;
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, guint, i);
+ GPtrArray *info = g_hash_table_lookup (self->priv->contact_info,
+ GUINT_TO_POINTER (handle));
+
+ if (info != NULL)
+ {
+ tp_contacts_mixin_set_contact_attribute (attributes, handle,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO "/info",
+ tp_g_value_slice_new_boxed (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST,
+ info));
+ }
+ }
+}
+
+static TpDBusPropertiesMixinPropImpl conn_contact_info_properties[] = {
+ { "ContactInfoFlags", GUINT_TO_POINTER (TP_CONTACT_INFO_FLAG_PUSH |
+ TP_CONTACT_INFO_FLAG_CAN_SET), NULL },
+ { "SupportedFields", NULL, NULL },
+ { NULL }
+};
+
+static void
+conn_contact_info_properties_getter (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ GQuark q_supported_fields = g_quark_from_static_string ("SupportedFields");
+ static GPtrArray *supported_fields = NULL;
+
+ if (name == q_supported_fields)
+ {
+ if (supported_fields == NULL)
+ {
+ supported_fields = g_ptr_array_new ();
+ g_ptr_array_add (supported_fields, tp_value_array_build (4,
+ G_TYPE_STRING, "n",
+ G_TYPE_STRV, NULL,
+ G_TYPE_UINT, 0,
+ G_TYPE_UINT, 0,
+ G_TYPE_INVALID));
+ }
+ g_value_set_boxed (value, supported_fields);
+ }
+ else
+ {
+ g_value_set_uint (value, GPOINTER_TO_UINT (getter_data));
+ }
+}
+
+static void
+constructed (GObject *object)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+ void (*parent_impl) (GObject *) =
+ G_OBJECT_CLASS (tp_tests_contacts_connection_parent_class)->constructed;
+
+ if (parent_impl != NULL)
+ parent_impl (object);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (TpTestsContactsConnection, contacts_mixin));
+ tp_base_connection_register_with_contacts_mixin (base);
+ tp_base_contact_list_mixin_register_with_contacts_mixin (base);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ aliasing_fill_contact_attributes);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ avatars_fill_contact_attributes);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+ location_fill_contact_attributes);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ contact_caps_fill_contact_attributes);
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+ contact_info_fill_contact_attributes);
+
+ tp_presence_mixin_init (object,
+ G_STRUCT_OFFSET (TpTestsContactsConnection, presence_mixin));
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
+}
+
+static const TpPresenceStatusOptionalArgumentSpec can_have_message[] = {
+ { "message", "s", NULL, NULL },
+ { NULL }
+};
+
+/* Must match TpTestsContactsConnectionPresenceStatusIndex in the .h */
+static const TpPresenceStatusSpec my_statuses[] = {
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE,
+ can_have_message },
+ { "busy", TP_CONNECTION_PRESENCE_TYPE_BUSY, TRUE, can_have_message },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, can_have_message },
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
+ { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
+ { NULL }
+};
+
+static gboolean
+my_status_available (GObject *object,
+ guint index)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (object);
+
+ if (base->status != TP_CONNECTION_STATUS_CONNECTED)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GHashTable *
+my_get_contact_statuses (GObject *object,
+ const GArray *contacts,
+ GError **error)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ gpointer key = GUINT_TO_POINTER (handle);
+ TpTestsContactsConnectionPresenceStatusIndex index;
+ const gchar *presence_message;
+ GHashTable *parameters;
+
+ index = GPOINTER_TO_UINT (g_hash_table_lookup (
+ self->priv->presence_statuses, key));
+ presence_message = g_hash_table_lookup (
+ self->priv->presence_messages, key);
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ if (presence_message != NULL)
+ g_hash_table_insert (parameters, "message",
+ tp_g_value_slice_new_string (presence_message));
+
+ g_hash_table_insert (result, key,
+ tp_presence_status_new (index, parameters));
+ g_hash_table_destroy (parameters);
+ }
+
+ return result;
+}
+
+static gboolean
+my_set_own_status (GObject *object,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (object);
+ TpTestsContactsConnectionPresenceStatusIndex index = status->index;
+ const gchar *message = "";
+
+ if (status->optional_arguments != NULL)
+ {
+ message = g_hash_table_lookup (status->optional_arguments, "message");
+
+ if (message == NULL)
+ message = "";
+ }
+
+ tp_tests_contacts_connection_change_presences (TP_TESTS_CONTACTS_CONNECTION (object),
+ 1, &(base_conn->self_handle), &index, &message);
+
+ return TRUE;
+}
+
+static guint
+my_get_maximum_status_message_length_cb (GObject *obj)
+{
+ return 512;
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ self->priv->list_manager = g_object_new (TEST_TYPE_CONTACT_LIST_MANAGER,
+ "connection", conn, NULL);
+
+ g_ptr_array_add (ret, self->priv->list_manager);
+
+ return ret;
+}
+
+static void
+tp_tests_contacts_connection_class_init (TpTestsContactsConnectionClass *klass)
+{
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ TpPresenceMixinClass *mixin_class;
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ NULL };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ conn_avatars_properties_getter,
+ NULL,
+ conn_avatars_properties,
+ },
+ { TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+ conn_contact_info_properties_getter,
+ NULL,
+ conn_contact_info_properties,
+ },
+ { NULL }
+ };
+
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass, sizeof (TpTestsContactsConnectionPrivate));
+
+ base_class->interfaces_always_present = interfaces_always_present;
+ base_class->create_channel_managers = create_channel_managers;
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsContactsConnectionClass, contacts_mixin));
+
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsContactsConnectionClass, presence_mixin),
+ my_status_available, my_get_contact_statuses,
+ my_set_own_status, my_statuses);
+ mixin_class = TP_PRESENCE_MIXIN_CLASS(klass);
+ mixin_class->get_maximum_status_message_length =
+ my_get_maximum_status_message_length_cb;
+
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+
+ klass->properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsContactsConnectionClass, properties_class));
+
+ tp_base_contact_list_mixin_class_init (base_class);
+}
+
+TestContactListManager *
+tp_tests_contacts_connection_get_contact_list_manager (
+ TpTestsContactsConnection *self)
+{
+ return self->priv->list_manager;
+}
+
+void
+tp_tests_contacts_connection_change_aliases (TpTestsContactsConnection *self,
+ guint n,
+ const TpHandle *handles,
+ const gchar * const *aliases)
+{
+ GPtrArray *structs = g_ptr_array_sized_new (n);
+ guint i;
+
+ for (i = 0; i < n; i++)
+ {
+ GValueArray *pair = g_value_array_new (2);
+
+ DEBUG ("contact#%u -> %s", handles[i], aliases[i]);
+
+ g_hash_table_insert (self->priv->aliases,
+ GUINT_TO_POINTER (handles[i]), g_strdup (aliases[i]));
+
+ g_value_array_append (pair, NULL);
+ g_value_init (pair->values + 0, G_TYPE_UINT);
+ g_value_set_uint (pair->values + 0, handles[i]);
+
+ g_value_array_append (pair, NULL);
+ g_value_init (pair->values + 1, G_TYPE_STRING);
+ g_value_set_string (pair->values + 1, aliases[i]);
+
+ g_ptr_array_add (structs, pair);
+ }
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (self,
+ structs);
+
+ g_ptr_array_foreach (structs, (GFunc) g_value_array_free, NULL);
+ g_ptr_array_free (structs, TRUE);
+}
+
+void
+tp_tests_contacts_connection_change_presences (
+ TpTestsContactsConnection *self,
+ guint n,
+ const TpHandle *handles,
+ const TpTestsContactsConnectionPresenceStatusIndex *indexes,
+ const gchar * const *messages)
+{
+ GHashTable *presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+ guint i;
+
+ for (i = 0; i < n; i++)
+ {
+ GHashTable *parameters;
+ gpointer key = GUINT_TO_POINTER (handles[i]);
+
+ DEBUG ("contact#%u -> %s \"%s\"", handles[i],
+ my_statuses[indexes[i]].name, messages[i]);
+
+ g_hash_table_insert (self->priv->presence_statuses, key,
+ GUINT_TO_POINTER (indexes[i]));
+ g_hash_table_insert (self->priv->presence_messages, key,
+ g_strdup (messages[i]));
+
+ parameters = g_hash_table_new_full (g_str_hash,
+ g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ if (messages[i] != NULL && messages[i][0] != '\0')
+ g_hash_table_insert (parameters, "message",
+ tp_g_value_slice_new_string (messages[i]));
+
+ g_hash_table_insert (presences, key, tp_presence_status_new (indexes[i],
+ parameters));
+ g_hash_table_destroy (parameters);
+ }
+
+ tp_presence_mixin_emit_presence_update ((GObject *) self,
+ presences);
+ g_hash_table_destroy (presences);
+}
+
+void
+tp_tests_contacts_connection_change_avatar_tokens (TpTestsContactsConnection *self,
+ guint n,
+ const TpHandle *handles,
+ const gchar * const *tokens)
+{
+ guint i;
+
+ for (i = 0; i < n; i++)
+ {
+ DEBUG ("contact#%u -> %s", handles[i], tokens[i]);
+ g_hash_table_insert (self->priv->avatars,
+ GUINT_TO_POINTER (handles[i]), avatar_data_new (NULL, NULL, tokens[i]));
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ handles[i], tokens[i]);
+ }
+}
+
+void
+tp_tests_contacts_connection_change_avatar_data (
+ TpTestsContactsConnection *self,
+ TpHandle handle,
+ GArray *data,
+ const gchar *mime_type,
+ const gchar *token,
+ gboolean emit_avatar_updated)
+{
+ g_hash_table_insert (self->priv->avatars,
+ GUINT_TO_POINTER (handle), avatar_data_new (data, mime_type, token));
+
+ if (emit_avatar_updated)
+ {
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ handle, token);
+ }
+}
+
+void
+tp_tests_contacts_connection_change_locations (TpTestsContactsConnection *self,
+ guint n,
+ const TpHandle *handles,
+ GHashTable **locations)
+{
+ guint i;
+
+ for (i = 0; i < n; i++)
+ {
+ DEBUG ("contact#%u ->", handles[i]);
+ tp_asv_dump (locations[i]);
+ g_hash_table_insert (self->priv->locations,
+ GUINT_TO_POINTER (handles[i]), g_hash_table_ref (locations[i]));
+
+ tp_svc_connection_interface_location_emit_location_updated (self,
+ handles[i], locations[i]);
+ }
+}
+
+void
+tp_tests_contacts_connection_change_capabilities (
+ TpTestsContactsConnection *self,
+ GHashTable *capabilities)
+{
+ GHashTableIter iter;
+ gpointer handle, caps;
+
+ g_hash_table_iter_init (&iter, capabilities);
+ while (g_hash_table_iter_next (&iter, &handle, &caps))
+ {
+ g_hash_table_insert (self->priv->capabilities,
+ handle,
+ g_boxed_copy (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST,
+ caps));
+ }
+
+ tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed (
+ self, capabilities);
+}
+
+void
+tp_tests_contacts_connection_change_contact_info (
+ TpTestsContactsConnection *self,
+ TpHandle handle,
+ GPtrArray *info)
+{
+ g_hash_table_insert (self->priv->contact_info, GUINT_TO_POINTER (handle),
+ g_ptr_array_ref (info));
+
+ tp_svc_connection_interface_contact_info_emit_contact_info_changed (self,
+ handle, info);
+}
+
+void
+tp_tests_contacts_connection_set_default_contact_info (
+ TpTestsContactsConnection *self,
+ GPtrArray *info)
+{
+ if (self->priv->default_contact_info != NULL)
+ g_ptr_array_unref (self->priv->default_contact_info);
+ self->priv->default_contact_info = g_ptr_array_ref (info);
+}
+
+static void
+my_get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+ 0);
+}
+
+static void
+my_get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTable *result;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = g_hash_table_lookup (self->priv->aliases,
+ GUINT_TO_POINTER (handle));
+
+ if (alias == NULL)
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+ (gchar *) tp_handle_inspect (contact_repo, handle));
+ else
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+ (gchar *) alias);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+ g_hash_table_destroy (result);
+}
+
+static void
+my_request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (aliasing);
+ TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GPtrArray *result;
+ gchar **strings;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_ptr_array_sized_new (contacts->len + 1);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ const gchar *alias = g_hash_table_lookup (self->priv->aliases,
+ GUINT_TO_POINTER (handle));
+
+ if (alias == NULL)
+ g_ptr_array_add (result,
+ (gchar *) tp_handle_inspect (contact_repo, handle));
+ else
+ g_ptr_array_add (result, (gchar *) alias);
+ }
+
+ g_ptr_array_add (result, NULL);
+ strings = (gchar **) g_ptr_array_free (result, FALSE);
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+ (const gchar **) strings);
+ g_free (strings);
+}
+
+static void
+init_aliasing (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
+ klass, my_##x)
+ IMPLEMENT(get_alias_flags);
+ IMPLEMENT(request_aliases);
+ IMPLEMENT(get_aliases);
+ /* IMPLEMENT(set_aliases); */
+#undef IMPLEMENT
+}
+
+static void
+my_get_avatar_tokens (TpSvcConnectionInterfaceAvatars *avatars,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+ TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ GHashTable *result;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+ GUINT_TO_POINTER (handle));
+
+ if (a == NULL || a->token == NULL)
+ {
+ /* we're expected to do a round-trip to the server to find out
+ * their token, so we have to give some sort of result. Assume
+ * no avatar, here */
+ a = avatar_data_new (NULL, NULL, "");
+ g_hash_table_insert (self->priv->avatars,
+ GUINT_TO_POINTER (handle), a);
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ handle, a->token);
+ }
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+ a->token);
+ }
+
+ tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens (
+ context, result);
+ g_hash_table_destroy (result);
+}
+
+static void
+my_get_known_avatar_tokens (TpSvcConnectionInterfaceAvatars *avatars,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+ TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ GHashTable *result;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+ GUINT_TO_POINTER (handle));
+ const gchar *token = a ? a->token : NULL;
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+ (gchar *) (token != NULL ? token : ""));
+ }
+
+ tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens (
+ context, result);
+ g_hash_table_destroy (result);
+}
+
+static void
+my_request_avatars (TpSvcConnectionInterfaceAvatars *avatars,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+ TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+ GUINT_TO_POINTER (handle));
+
+ if (a != NULL)
+ tp_svc_connection_interface_avatars_emit_avatar_retrieved (self, handle,
+ a->token, a->data, a->mime_type);
+ }
+
+ tp_svc_connection_interface_avatars_return_from_request_avatars (context);
+}
+
+static void
+conn_avatars_properties_getter (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ GQuark q_mime_types = g_quark_from_static_string (
+ "SupportedAvatarMIMETypes");
+
+ if (name == q_mime_types)
+ {
+ g_value_set_static_boxed (value, mime_types);
+ }
+ else
+ {
+ g_value_set_uint (value, GPOINTER_TO_UINT (getter_data));
+ }
+}
+
+static void
+init_avatars (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAvatarsClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_avatars_implement_##x (\
+ klass, my_##x)
+ /* IMPLEMENT(get_avatar_requirements); */
+ IMPLEMENT(get_avatar_tokens);
+ IMPLEMENT(get_known_avatar_tokens);
+ /* IMPLEMENT(request_avatar); */
+ IMPLEMENT(request_avatars);
+ /* IMPLEMENT(set_avatar); */
+ /* IMPLEMENT(clear_avatar); */
+#undef IMPLEMENT
+}
+
+static void
+my_get_locations (TpSvcConnectionInterfaceLocation *avatars,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+ TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ GHashTable *result;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ GHashTable *location = g_hash_table_lookup (self->priv->locations,
+ GUINT_TO_POINTER (handle));
+
+ if (location != NULL)
+ {
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle), location);
+ }
+ }
+
+ tp_svc_connection_interface_location_return_from_get_locations (
+ context, result);
+ g_hash_table_destroy (result);
+}
+
+static void
+init_location (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceLocationClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_location_implement_##x (\
+ klass, my_##x)
+ IMPLEMENT(get_locations);
+#undef IMPLEMENT
+}
+
+static void
+my_get_contact_capabilities (TpSvcConnectionInterfaceContactCapabilities *obj,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ GHashTable *result;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ GPtrArray *arr = g_hash_table_lookup (self->priv->capabilities,
+ GUINT_TO_POINTER (handle));
+
+ if (arr != NULL)
+ {
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle), arr);
+ }
+ }
+
+ tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities (
+ context, result);
+
+ g_hash_table_destroy (result);
+}
+
+static void
+init_contact_caps (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceContactCapabilitiesClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_contact_capabilities_implement_##x (\
+ klass, my_##x)
+ IMPLEMENT(get_contact_capabilities);
+#undef IMPLEMENT
+}
+
+static GPtrArray *
+lookup_contact_info (TpTestsContactsConnection *self,
+ TpHandle handle)
+{
+ GPtrArray *ret = g_hash_table_lookup (self->priv->contact_info,
+ GUINT_TO_POINTER (handle));
+
+ if (ret == NULL && self->priv->default_contact_info != NULL)
+ {
+ ret = self->priv->default_contact_info;
+ g_hash_table_insert (self->priv->contact_info, GUINT_TO_POINTER (handle),
+ g_ptr_array_ref (ret));
+ }
+
+ if (ret == NULL)
+ return g_ptr_array_new ();
+
+ return g_ptr_array_ref (ret);
+}
+
+static void
+my_refresh_contact_info (TpSvcConnectionInterfaceContactInfo *obj,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ self->refresh_contact_info_called++;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, guint, i);
+
+ // actually update the info (if not using the default info) so there is an actual change
+ g_hash_table_insert (self->priv->contact_info, GUINT_TO_POINTER (handle),
+ g_ptr_array_ref (self->priv->default_contact_info));
+ tp_svc_connection_interface_contact_info_emit_contact_info_changed (self,
+ handle, self->priv->default_contact_info);
+ }
+
+ tp_svc_connection_interface_contact_info_return_from_refresh_contact_info (
+ context);
+}
+
+static void
+my_request_contact_info (TpSvcConnectionInterfaceContactInfo *obj,
+ guint handle,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+ GPtrArray *ret;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handle_is_valid (contact_repo, handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ ret = lookup_contact_info (self, handle);
+
+ tp_svc_connection_interface_contact_info_return_from_request_contact_info (
+ context, ret);
+
+ g_ptr_array_unref (ret);
+}
+
+static void
+my_set_contact_info (TpSvcConnectionInterfaceContactInfo *obj,
+ const GPtrArray *info,
+ DBusGMethodInvocation *context)
+{
+ TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+ GPtrArray *copy;
+ guint i;
+ TpHandle self_handle;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ /* Deep copy info */
+ copy = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free);
+ for (i = 0; i < info->len; i++)
+ g_ptr_array_add (copy, g_value_array_copy (g_ptr_array_index (info, i)));
+
+ self_handle = tp_base_connection_get_self_handle (base);
+ g_hash_table_insert (self->priv->contact_info, GUINT_TO_POINTER (self_handle),
+ copy);
+
+ tp_svc_connection_interface_contact_info_return_from_set_contact_info (
+ context);
+}
+
+static void
+init_contact_info (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceContactInfoClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x (\
+ klass, my_##x)
+ IMPLEMENT (refresh_contact_info);
+ IMPLEMENT (request_contact_info);
+ IMPLEMENT (set_contact_info);
+#undef IMPLEMENT
+}
+
+/* =============== Legacy version (no Contacts interface) ================= */
+
+G_DEFINE_TYPE (TpTestsLegacyContactsConnection,
+ tp_tests_legacy_contacts_connection, TP_TESTS_TYPE_CONTACTS_CONNECTION);
+
+enum
+{
+ LEGACY_PROP_HAS_IMMORTAL_HANDLES = 1
+};
+
+static void
+legacy_contacts_connection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case LEGACY_PROP_HAS_IMMORTAL_HANDLES:
+ /* Pretend we don't. */
+ g_value_set_boolean (value, FALSE);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tp_tests_legacy_contacts_connection_init (TpTestsLegacyContactsConnection *self)
+{
+}
+
+static void
+tp_tests_legacy_contacts_connection_class_init (
+ TpTestsLegacyContactsConnectionClass *klass)
+{
+ /* Leave Contacts out of the interfaces we say are present, so clients
+ * won't use it */
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ NULL };
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->get_property = legacy_contacts_connection_get_property;
+
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ g_object_class_override_property (object_class,
+ LEGACY_PROP_HAS_IMMORTAL_HANDLES, "has-immortal-handles");
+}
+
+/* =============== No Requests and no ContactCapabilities ================= */
+
+G_DEFINE_TYPE (TpTestsNoRequestsConnection, tp_tests_no_requests_connection,
+ TP_TESTS_TYPE_CONTACTS_CONNECTION);
+
+static void
+tp_tests_no_requests_connection_init (TpTestsNoRequestsConnection *self)
+{
+}
+
+static void
+tp_tests_no_requests_connection_class_init (
+ TpTestsNoRequestsConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+ NULL };
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+
+ base_class->interfaces_always_present = interfaces_always_present;
+}
diff --git a/qt4/tests/lib/glib/contacts-conn.h b/qt4/tests/lib/glib/contacts-conn.h
new file mode 100644
index 000000000..9cf1114c1
--- /dev/null
+++ b/qt4/tests/lib/glib/contacts-conn.h
@@ -0,0 +1,191 @@
+/*
+ * contacts-conn.h - header for a connection with contact info
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_CONTACTS_CONN_H__
+#define __TP_TESTS_CONTACTS_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+
+#include "simple-conn.h"
+#include "contact-list-manager.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsContactsConnection TpTestsContactsConnection;
+typedef struct _TpTestsContactsConnectionClass TpTestsContactsConnectionClass;
+typedef struct _TpTestsContactsConnectionPrivate TpTestsContactsConnectionPrivate;
+
+struct _TpTestsContactsConnectionClass {
+ TpTestsSimpleConnectionClass parent_class;
+
+ TpPresenceMixinClass presence_mixin;
+ TpContactsMixinClass contacts_mixin;
+ TpDBusPropertiesMixinClass properties_class;
+};
+
+struct _TpTestsContactsConnection {
+ TpTestsSimpleConnection parent;
+
+ TpPresenceMixin presence_mixin;
+ TpContactsMixin contacts_mixin;
+
+ guint refresh_contact_info_called;
+
+ TpTestsContactsConnectionPrivate *priv;
+};
+
+GType tp_tests_contacts_connection_get_type (void);
+
+/* Must match my_statuses in the .c */
+typedef enum {
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_BUSY,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_OFFLINE,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_UNKNOWN,
+ TP_TESTS_CONTACTS_CONNECTION_STATUS_ERROR
+} TpTestsContactsConnectionPresenceStatusIndex;
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_CONTACTS_CONNECTION \
+ (tp_tests_contacts_connection_get_type ())
+#define TP_TESTS_CONTACTS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_CONTACTS_CONNECTION, \
+ TpTestsContactsConnection))
+#define TP_TESTS_CONTACTS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_CONTACTS_CONNECTION, \
+ TpTestsContactsConnectionClass))
+#define TP_TESTS_IS_CONTACTS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_CONTACTS_CONNECTION))
+#define TP_TESTS_IS_CONTACTS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_CONTACTS_CONNECTION))
+#define TP_TESTS_CONTACTS_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONTACTS_CONNECTION, \
+ TpTestsContactsConnectionClass))
+
+TestContactListManager *tp_tests_contacts_connection_get_contact_list_manager (
+ TpTestsContactsConnection *self);
+
+void tp_tests_contacts_connection_change_aliases (
+ TpTestsContactsConnection *self, guint n,
+ const TpHandle *handles, const gchar * const *aliases);
+
+void tp_tests_contacts_connection_change_presences (
+ TpTestsContactsConnection *self, guint n, const TpHandle *handles,
+ const TpTestsContactsConnectionPresenceStatusIndex *indexes,
+ const gchar * const *messages);
+
+void tp_tests_contacts_connection_change_avatar_tokens (
+ TpTestsContactsConnection *self, guint n, const TpHandle *handles,
+ const gchar * const *tokens);
+
+void tp_tests_contacts_connection_change_avatar_data (
+ TpTestsContactsConnection *self,
+ TpHandle handle,
+ GArray *data,
+ const gchar *mime_type,
+ const gchar *token,
+ gboolean emit_avatar_updated);
+
+void tp_tests_contacts_connection_change_locations (
+ TpTestsContactsConnection *self,
+ guint n,
+ const TpHandle *handles,
+ GHashTable **locations);
+
+void tp_tests_contacts_connection_change_capabilities (
+ TpTestsContactsConnection *self, GHashTable *capabilities);
+
+void tp_tests_contacts_connection_change_contact_info (
+ TpTestsContactsConnection *self, TpHandle handle, GPtrArray *info);
+
+void tp_tests_contacts_connection_set_default_contact_info (
+ TpTestsContactsConnection *self,
+ GPtrArray *info);
+
+/* Legacy version (no Contacts interface, and no immortal handles) */
+
+typedef struct _TpTestsLegacyContactsConnection TpTestsLegacyContactsConnection;
+typedef struct _TpTestsLegacyContactsConnectionClass TpTestsLegacyContactsConnectionClass;
+typedef struct _TpTestsLegacyContactsConnectionPrivate
+ TpTestsLegacyContactsConnectionPrivate;
+
+struct _TpTestsLegacyContactsConnectionClass {
+ TpTestsContactsConnectionClass parent_class;
+};
+
+struct _TpTestsLegacyContactsConnection {
+ TpTestsContactsConnection parent;
+
+ TpTestsLegacyContactsConnectionPrivate *priv;
+};
+
+GType tp_tests_legacy_contacts_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION \
+ (tp_tests_legacy_contacts_connection_get_type ())
+#define LEGACY_TP_TESTS_CONTACTS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, \
+ TpTestsLegacyContactsConnection))
+#define LEGACY_TP_TESTS_CONTACTS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, \
+ TpTestsLegacyContactsConnectionClass))
+#define TP_TESTS_LEGACY_CONTACTS_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION))
+#define TP_TESTS_LEGACY_CONTACTS_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION))
+#define LEGACY_TP_TESTS_CONTACTS_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, \
+ TpTestsLegacyContactsConnectionClass))
+
+/* No Requests version */
+
+typedef struct _TpTestsNoRequestsConnection TpTestsNoRequestsConnection;
+typedef struct _TpTestsNoRequestsConnectionClass TpTestsNoRequestsConnectionClass;
+typedef struct _TpTestsNoRequestsConnectionPrivate
+ TpTestsNoRequestsConnectionPrivate;
+
+struct _TpTestsNoRequestsConnectionClass {
+ TpTestsContactsConnectionClass parent_class;
+};
+
+struct _TpTestsNoRequestsConnection {
+ TpTestsContactsConnection parent;
+
+ TpTestsNoRequestsConnectionPrivate *priv;
+};
+
+GType tp_tests_no_requests_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_NO_REQUESTS_CONNECTION \
+ (tp_tests_no_requests_connection_get_type ())
+#define TP_TESTS_NO_REQUESTS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, \
+ TpTestsNoRequestsConnection))
+#define TP_TESTS_NO_REQUESTS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, \
+ TpTestsNoRequestsConnectionClass))
+#define TP_TESTS_NO_REQUESTS_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION))
+#define TP_TESTS_NO_REQUESTS_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION))
+#define TP_TESTS_NO_REQUESTS_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, \
+ TpTestsNoRequestsConnectionClass))
+
+G_END_DECLS
+
+#endif /* ifndef __TP_TESTS_CONTACTS_CONN_H__ */
diff --git a/qt4/tests/lib/glib/contacts-noroster-conn.c b/qt4/tests/lib/glib/contacts-noroster-conn.c
new file mode 100644
index 000000000..64db41085
--- /dev/null
+++ b/qt4/tests/lib/glib/contacts-noroster-conn.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "contacts-noroster-conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsContactsNorosterConnection,
+ tp_tests_contacts_noroster_connection,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ NULL);
+ );
+
+static void
+tp_tests_contacts_noroster_connection_init (TpTestsContactsNorosterConnection *self)
+{
+}
+
+static void
+finalize (GObject *object)
+{
+ G_OBJECT_CLASS (tp_tests_contacts_noroster_connection_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_contacts_noroster_connection_class_init (TpTestsContactsNorosterConnectionClass *klass)
+{
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ NULL };
+
+ object_class->finalize = finalize;
+
+ base_class->interfaces_always_present = interfaces_always_present;
+}
diff --git a/qt4/tests/lib/glib/contacts-noroster-conn.h b/qt4/tests/lib/glib/contacts-noroster-conn.h
new file mode 100644
index 000000000..792fe7e35
--- /dev/null
+++ b/qt4/tests/lib/glib/contacts-noroster-conn.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_CONTACTS_NOROSTER_CONN_H__
+#define __TP_TESTS_CONTACTS_NOROSTER_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+#include "simple-conn.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsContactsNorosterConnection TpTestsContactsNorosterConnection;
+typedef struct _TpTestsContactsNorosterConnectionClass TpTestsContactsNorosterConnectionClass;
+
+struct _TpTestsContactsNorosterConnectionClass {
+ TpTestsSimpleConnectionClass parent_class;
+};
+
+struct _TpTestsContactsNorosterConnection {
+ TpTestsSimpleConnection parent;
+};
+
+GType tp_tests_contacts_noroster_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION \
+ (tp_tests_contacts_noroster_connection_get_type ())
+#define TP_TESTS_CONTACTS_NOROSTER_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION, \
+ TpTestsContactsNorosterConnection))
+#define TP_TESTS_CONTACTS_NOROSTER_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION, \
+ TpTestsContactsNorosterConnectionClass))
+#define TP_TESTS_CONTACTS_NOROSTER_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION))
+#define TP_TESTS_CONTACTS_NOROSTER_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION))
+#define TP_TESTS_CONTACTS_NOROSTER_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONTACTS_NOROSTER_CONNECTION, \
+ TpTestsContactsNorosterConnectionClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_CONTACTS_NOROSTER_CONN_H__ */
diff --git a/qt4/tests/lib/glib/csh/CMakeLists.txt b/qt4/tests/lib/glib/csh/CMakeLists.txt
new file mode 100644
index 000000000..59f258f50
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/CMakeLists.txt
@@ -0,0 +1,15 @@
+if(ENABLE_TP_GLIB_TESTS)
+ set(example_cm_csh_SRCS
+ conn.c
+ conn.h
+ connection-manager.c
+ connection-manager.h
+ room.c
+ room.h
+ room-manager.c
+ room-manager.h)
+
+ add_library(example-cm-csh STATIC ${example_cm_csh_SRCS})
+ target_link_libraries(example-cm-csh ${TPGLIB_LIBRARIES})
+ tpqt4_generate_manager_file(${CMAKE_CURRENT_SOURCE_DIR}/manager-file.py example_csh.manager connection-manager.c)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/csh/conn.c b/qt4/tests/lib/glib/csh/conn.c
new file mode 100644
index 000000000..6cf024178
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/conn.c
@@ -0,0 +1,292 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+
+#include "room-manager.h"
+
+G_DEFINE_TYPE (ExampleCSHConnection,
+ example_csh_connection,
+ TP_TYPE_BASE_CONNECTION)
+
+/* type definition stuff */
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleCSHConnectionPrivate
+{
+ gchar *account;
+ guint simulation_delay;
+};
+
+static void
+example_csh_connection_init (ExampleCSHConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CSH_CONNECTION, ExampleCSHConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_utf8_strdown (g_value_get_string (value), -1);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (object);
+
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (example_csh_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (conn);
+
+ return g_strdup (self->priv->account);
+}
+
+gchar *
+example_csh_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ const gchar *at;
+ /* For this example, we imagine that global handles look like
+ * username@realm and channel-specific handles look like nickname@#chatroom,
+ * where username and nickname contain any UTF-8 except "@", and realm
+ * and chatroom contain any UTF-8 except "@" and "#".
+ *
+ * Additionally, we imagine that everything is case-sensitive but is
+ * required to be in NFKC.
+ */
+
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must not be empty");
+ return NULL;
+ }
+
+ at = strchr (id, '@');
+
+ if (at == NULL || at == id || at[1] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must look like aaa@bbb");
+ return NULL;
+ }
+
+ if (strchr (at + 1, '@') != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID cannot contain more than one '@'");
+ return NULL;
+ }
+
+ if (at[1] == '#' && at[2] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "chatroom name cannot be empty");
+ return NULL;
+ }
+
+ if (strchr (at + 2, '#') != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "realm/chatroom cannot contain '#' except at the beginning");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static gchar *
+example_csh_normalize_room (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ /* See example_csh_normalize_contact(). */
+
+ if (id[0] != '#')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Chatroom names in this protocol start with #");
+ }
+
+ if (id[1] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Chatroom name cannot be empty");
+ return NULL;
+ }
+
+ if (strchr (id, '@') != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Chatroom names in this protocol cannot contain '@'");
+ return NULL;
+ }
+
+ return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_csh_normalize_contact, NULL);
+
+ repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_ROOM, example_csh_normalize_room, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (conn);
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ g_ptr_array_add (ret, g_object_new (EXAMPLE_TYPE_CSH_ROOM_MANAGER,
+ "connection", conn,
+ "simulation-delay", self->priv->simulation_delay,
+ NULL));
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, error);
+
+ if (conn->self_handle == 0)
+ return FALSE;
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static void
+example_csh_connection_class_init (ExampleCSHConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ NULL };
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass, sizeof (ExampleCSHConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 500,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+}
diff --git a/qt4/tests/lib/glib/csh/conn.h b/qt4/tests/lib/glib/csh/conn.h
new file mode 100644
index 000000000..00b44bfef
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/conn.h
@@ -0,0 +1,58 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CSH_CONN_H__
+#define __EXAMPLE_CSH_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHConnection ExampleCSHConnection;
+typedef struct _ExampleCSHConnectionClass ExampleCSHConnectionClass;
+typedef struct _ExampleCSHConnectionPrivate ExampleCSHConnectionPrivate;
+
+struct _ExampleCSHConnectionClass {
+ TpBaseConnectionClass parent_class;
+};
+
+struct _ExampleCSHConnection {
+ TpBaseConnection parent;
+
+ ExampleCSHConnectionPrivate *priv;
+};
+
+GType example_csh_connection_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CSH_CONNECTION \
+ (example_csh_connection_get_type ())
+#define EXAMPLE_CSH_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CSH_CONNECTION, \
+ ExampleCSHConnection))
+#define EXAMPLE_CSH_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CSH_CONNECTION, \
+ ExampleCSHConnectionClass))
+#define EXAMPLE_IS_CSH_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CSH_CONNECTION))
+#define EXAMPLE_IS_CSH_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CSH_CONNECTION))
+#define EXAMPLE_CSH_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_CONNECTION, \
+ ExampleCSHConnectionClass))
+
+gchar *example_csh_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id, gpointer context, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/csh/connection-manager.c b/qt4/tests/lib/glib/csh/connection-manager.c
new file mode 100644
index 000000000..c90161ba9
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/connection-manager.c
@@ -0,0 +1,133 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "connection-manager.h"
+
+#include <string.h>
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleCSHConnectionManager,
+ example_csh_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+/* type definition stuff */
+
+static void
+example_csh_connection_manager_init (ExampleCSHConnectionManager *self)
+{
+}
+
+/* private data */
+
+typedef struct {
+ gchar *account;
+ guint simulation_delay;
+} ExampleParams;
+
+
+/* See example_csh_normalize_contact in conn.c. */
+static gboolean
+account_param_filter (const TpCMParamSpec *paramspec,
+ GValue *value,
+ GError **error)
+{
+ const gchar *id = g_value_get_string (value);
+ const gchar *at;
+
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "account must not be empty");
+ return FALSE;
+ }
+
+ at = strchr (id, '@');
+
+ if (at == NULL || at == id || at[1] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "account must look like aaa@bbb");
+ return FALSE;
+ }
+
+ if (strchr (at, '#') != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "realm cannot contain '#' except at the beginning");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#include "_gen/param-spec-struct.h"
+
+static gpointer
+alloc_params (void)
+{
+ ExampleParams *params = g_slice_new0 (ExampleParams);
+
+ params->simulation_delay = 500;
+ return params;
+}
+
+static void
+free_params (gpointer p)
+{
+ ExampleParams *params = p;
+
+ g_free (params->account);
+
+ g_slice_free (ExampleParams, params);
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+ { "example", example_csh_example_params, alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ ExampleParams *params = parsed_params;
+ ExampleCSHConnection *conn;
+
+ conn = EXAMPLE_CSH_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_CSH_CONNECTION,
+ "account", params->account,
+ "protocol", proto,
+ "simulation-delay", params->simulation_delay,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_csh_connection_manager_class_init (
+ ExampleCSHConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_csh";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/qt4/tests/lib/glib/csh/connection-manager.h b/qt4/tests/lib/glib/csh/connection-manager.h
new file mode 100644
index 000000000..b30105cd8
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/connection-manager.h
@@ -0,0 +1,61 @@
+/*
+ * manager.h - header for an example connection manager
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CSH_CONNECTION_MANAGER_H__
+#define __EXAMPLE_CSH_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHConnectionManager ExampleCSHConnectionManager;
+typedef struct _ExampleCSHConnectionManagerPrivate
+ ExampleCSHConnectionManagerPrivate;
+typedef struct _ExampleCSHConnectionManagerClass
+ ExampleCSHConnectionManagerClass;
+typedef struct _ExampleCSHConnectionManagerClassPrivate
+ ExampleCSHConnectionManagerClassPrivate;
+
+struct _ExampleCSHConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ ExampleCSHConnectionManagerClassPrivate *priv;
+};
+
+struct _ExampleCSHConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleCSHConnectionManagerPrivate *priv;
+};
+
+GType example_csh_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CSH_CONNECTION_MANAGER \
+ (example_csh_connection_manager_get_type ())
+#define EXAMPLE_CSH_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CSH_CONNECTION_MANAGER, \
+ ExampleCSHConnectionManager))
+#define EXAMPLE_CSH_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CSH_CONNECTION_MANAGER, \
+ ExampleCSHConnectionManagerClass))
+#define EXAMPLE_IS_CSH_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CSH_CONNECTION_MANAGER))
+#define EXAMPLE_IS_CSH_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CSH_CONNECTION_MANAGER))
+#define EXAMPLE_CSH_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_CONNECTION_MANAGER, \
+ ExampleCSHConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/csh/manager-file.py b/qt4/tests/lib/glib/csh/manager-file.py
new file mode 100644
index 000000000..3f22c21fc
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/manager-file.py
@@ -0,0 +1,23 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_csh'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'account_param_filter',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ 'simulation-delay': {
+ 'dtype': 'u',
+ 'default': 500,
+ },
+ },
+ }
+STRUCTS = {
+ 'example': 'ExampleParams'
+ }
diff --git a/qt4/tests/lib/glib/csh/room-manager.c b/qt4/tests/lib/glib/csh/room-manager.c
new file mode 100644
index 000000000..16163fcc1
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/room-manager.c
@@ -0,0 +1,384 @@
+/*
+ * room-manager.c: example channel manager for chatrooms
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "room-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "room.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCSHRoomManager,
+ example_csh_room_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleCSHRoomManagerPrivate
+{
+ TpBaseConnection *conn;
+ guint simulation_delay;
+
+ /* GUINT_TO_POINTER (room handle) => ExampleCSHRoomChannel */
+ GHashTable *channels;
+ gulong status_changed_id;
+};
+
+static void
+example_csh_room_manager_init (ExampleCSHRoomManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_CSH_ROOM_MANAGER, ExampleCSHRoomManagerPrivate);
+
+ self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+}
+
+static void example_csh_room_manager_close_all (ExampleCSHRoomManager *self);
+
+static void
+dispose (GObject *object)
+{
+ ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+
+ example_csh_room_manager_close_all (self);
+ g_assert (self->priv->channels == NULL);
+
+ ((GObjectClass *) example_csh_room_manager_parent_class)->dispose (object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleCSHRoomManager *self)
+{
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ example_csh_room_manager_close_all (self);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_csh_room_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_csh_room_manager_class_init (ExampleCSHRoomManagerClass *klass)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ param_spec = g_param_spec_object ("connection", "Connection object",
+ "The connection that owns this channel manager",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 500,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ g_type_class_add_private (klass, sizeof (ExampleCSHRoomManagerPrivate));
+}
+
+static void
+example_csh_room_manager_close_all (ExampleCSHRoomManager *self)
+{
+ if (self->priv->channels != NULL)
+ {
+ GHashTable *tmp = self->priv->channels;
+
+ self->priv->channels = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+example_csh_room_manager_foreach_channel (TpChannelManager *manager,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (manager);
+ GHashTableIter iter;
+ gpointer handle, channel;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &handle, &channel))
+ {
+ callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+ }
+}
+
+static void
+channel_closed_cb (ExampleCSHRoomChannel *chan,
+ ExampleCSHRoomManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->channels != NULL)
+ {
+ TpHandle handle;
+
+ g_object_get (chan,
+ "handle", &handle,
+ NULL);
+
+ g_hash_table_remove (self->priv->channels, GUINT_TO_POINTER (handle));
+ }
+}
+
+static void
+new_channel (ExampleCSHRoomManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token)
+{
+ ExampleCSHRoomChannel *chan;
+ gchar *object_path;
+ GSList *requests = NULL;
+
+ object_path = g_strdup_printf ("%s/CSHRoomChannel%u",
+ self->priv->conn->object_path, handle);
+
+ chan = g_object_new (EXAMPLE_TYPE_CSH_ROOM_CHANNEL,
+ "connection", self->priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ /* FIXME: initiator */
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", (GCallback) channel_closed_cb, self);
+
+ g_hash_table_insert (self->priv->channels, GUINT_TO_POINTER (handle), chan);
+
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+ g_slist_free (requests);
+}
+
+static const gchar * const fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ NULL
+};
+
+static const gchar * const allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ NULL
+};
+
+static void
+example_csh_room_manager_foreach_channel_class (TpChannelManager *manager,
+ TpChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
+ NULL);
+
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_csh_room_manager_request (ExampleCSHRoomManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandle handle;
+ ExampleCSHRoomChannel *chan;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ TP_IFACE_CHANNEL_TYPE_TEXT))
+ {
+ return FALSE;
+ }
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_ROOM)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ fixed_properties, allowed_properties, &error))
+ {
+ goto error;
+ }
+
+ chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+
+ if (chan == NULL)
+ {
+ new_channel (self, handle, self->priv->conn->self_handle,
+ request_token);
+ }
+ else if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "A Text channel for room #%u already exists", handle);
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ }
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+example_csh_room_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_csh_room_manager_request (
+ EXAMPLE_CSH_ROOM_MANAGER (manager), request_token,
+ request_properties, TRUE);
+}
+
+static gboolean
+example_csh_room_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_csh_room_manager_request (
+ EXAMPLE_CSH_ROOM_MANAGER (manager), request_token,
+ request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_csh_room_manager_foreach_channel;
+ iface->foreach_channel_class =
+ example_csh_room_manager_foreach_channel_class;
+ iface->create_channel = example_csh_room_manager_create_channel;
+ iface->ensure_channel = example_csh_room_manager_ensure_channel;
+ /* In this channel manager, Request has the same semantics as Ensure */
+ iface->request_channel = example_csh_room_manager_ensure_channel;
+}
diff --git a/qt4/tests/lib/glib/csh/room-manager.h b/qt4/tests/lib/glib/csh/room-manager.h
new file mode 100644
index 000000000..6e9eb27b7
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/room-manager.h
@@ -0,0 +1,55 @@
+/*
+ * manager.h - header for an example channel manager
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CSH_ROOM_MANAGER_H__
+#define __EXAMPLE_CSH_ROOM_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/channel-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHRoomManager ExampleCSHRoomManager;
+typedef struct _ExampleCSHRoomManagerClass ExampleCSHRoomManagerClass;
+typedef struct _ExampleCSHRoomManagerPrivate ExampleCSHRoomManagerPrivate;
+
+struct _ExampleCSHRoomManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _ExampleCSHRoomManager {
+ GObject parent;
+
+ ExampleCSHRoomManagerPrivate *priv;
+};
+
+GType example_csh_room_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CSH_ROOM_MANAGER \
+ (example_csh_room_manager_get_type ())
+#define EXAMPLE_CSH_ROOM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CSH_ROOM_MANAGER, \
+ ExampleCSHRoomManager))
+#define EXAMPLE_CSH_ROOM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CSH_ROOM_MANAGER, \
+ ExampleCSHRoomManagerClass))
+#define EXAMPLE_IS_CSH_ROOM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CSH_ROOM_MANAGER))
+#define EXAMPLE_IS_CSH_ROOM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CSH_ROOM_MANAGER))
+#define EXAMPLE_CSH_ROOM_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_ROOM_MANAGER, \
+ ExampleCSHRoomManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/csh/room.c b/qt4/tests/lib/glib/csh/room.c
new file mode 100644
index 000000000..20485aa93
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/room.c
@@ -0,0 +1,696 @@
+/*
+ * room.c - a chatroom channel
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "room.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+
+static void text_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCSHRoomChannel,
+ example_csh_room_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_SIMULATION_DELAY,
+ N_PROPS
+};
+
+struct _ExampleCSHRoomChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+ guint simulation_delay;
+
+ /* These are really booleans, but gboolean is signed. Thanks, GLib */
+ unsigned closed:1;
+ unsigned disposed:1;
+};
+
+
+static const char * example_csh_room_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+
+static void
+example_csh_room_channel_init (ExampleCSHRoomChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_CSH_ROOM_CHANNEL,
+ ExampleCSHRoomChannelPrivate);
+}
+
+static TpHandle
+suggest_room_identity (ExampleCSHRoomChannel *self)
+{
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+ gchar *nick, *id;
+ TpHandle ret;
+
+ nick = g_strdup (tp_handle_inspect (contact_repo,
+ self->priv->conn->self_handle));
+ g_strdelimit (nick, "@", '\0');
+ id = g_strdup_printf ("%s@%s", nick, tp_handle_inspect (room_repo,
+ self->priv->handle));
+ g_free (nick);
+
+ ret = tp_handle_ensure (contact_repo, id, NULL, NULL);
+ g_free (id);
+
+ g_assert (ret != 0);
+ return ret;
+}
+
+
+/* This timeout callback represents a successful join. In a real CM it'd
+ * happen in response to network events, rather than just a timer */
+static void
+complete_join (ExampleCSHRoomChannel *self)
+{
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+ const gchar *room_name = tp_handle_inspect (room_repo, self->priv->handle);
+ gchar *str;
+ TpHandle alice_local, bob_local, chris_local, anon_local;
+ TpHandle alice_global, bob_global, chris_global;
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ TpIntSet *added;
+
+ /* For this example, we assume that all chatrooms initially contain
+ * Alice, Bob and Chris (and that their global IDs are also known),
+ * and they also contain one anonymous user. */
+
+ str = g_strdup_printf ("alice@%s", room_name);
+ alice_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+ g_free (str);
+ alice_global = tp_handle_ensure (contact_repo, "alice@alpha", NULL, NULL);
+
+ str = g_strdup_printf ("bob@%s", room_name);
+ bob_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+ g_free (str);
+ bob_global = tp_handle_ensure (contact_repo, "bob@beta", NULL, NULL);
+
+ str = g_strdup_printf ("chris@%s", room_name);
+ chris_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+ g_free (str);
+ chris_global = tp_handle_ensure (contact_repo, "chris@chi", NULL, NULL);
+
+ str = g_strdup_printf ("anonymous coward@%s", room_name);
+ anon_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+ g_free (str);
+
+ /* If our chosen nick is not available, pretend the server would
+ * automatically rename us on entry. */
+ if (mixin->self_handle == alice_local ||
+ mixin->self_handle == bob_local ||
+ mixin->self_handle == chris_local ||
+ mixin->self_handle == anon_local)
+ {
+ TpHandle new_self;
+ TpIntSet *rp = tp_intset_new ();
+ TpIntSet *removed = tp_intset_new ();
+
+ str = g_strdup_printf ("renamed by server@%s", room_name);
+ new_self = tp_handle_ensure (contact_repo, str, NULL, NULL);
+ g_free (str);
+
+ tp_intset_add (rp, new_self);
+ tp_intset_add (removed, mixin->self_handle);
+
+ tp_group_mixin_add_handle_owner ((GObject *) self, new_self,
+ self->priv->conn->self_handle);
+ tp_group_mixin_change_self_handle ((GObject *) self, new_self);
+
+ tp_group_mixin_change_members ((GObject *) self, "", NULL, removed, NULL,
+ rp, 0, TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED);
+
+ tp_handle_unref (contact_repo, new_self);
+ tp_intset_destroy (removed);
+ tp_intset_destroy (rp);
+ }
+
+ tp_group_mixin_add_handle_owner ((GObject *) self, alice_local,
+ alice_global);
+ tp_group_mixin_add_handle_owner ((GObject *) self, bob_local,
+ bob_global);
+ tp_group_mixin_add_handle_owner ((GObject *) self, chris_local,
+ chris_global);
+ /* we know that anon_local is channel-specific, but not whose it is,
+ * hence 0 */
+ tp_group_mixin_add_handle_owner ((GObject *) self, anon_local, 0);
+
+ /* everyone in! */
+ added = tp_intset_new();
+ tp_intset_add (added, alice_local);
+ tp_intset_add (added, bob_local);
+ tp_intset_add (added, chris_local);
+ tp_intset_add (added, anon_local);
+ tp_intset_add (added, mixin->self_handle);
+
+ tp_group_mixin_change_members ((GObject *) self, "", added, NULL, NULL,
+ NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_handle_unref (contact_repo, alice_local);
+ tp_handle_unref (contact_repo, bob_local);
+ tp_handle_unref (contact_repo, chris_local);
+ tp_handle_unref (contact_repo, anon_local);
+
+ tp_handle_unref (contact_repo, alice_global);
+ tp_handle_unref (contact_repo, bob_global);
+ tp_handle_unref (contact_repo, chris_global);
+
+ /* now that the dust has settled, we can also invite people */
+ tp_group_mixin_change_flags ((GObject *) self,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD,
+ 0);
+}
+
+
+static void
+join_room (ExampleCSHRoomChannel *self)
+{
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ GObject *object = (GObject *) self;
+ TpIntSet *add_remote_pending;
+
+ g_assert (!tp_handle_set_is_member (mixin->members, mixin->self_handle));
+ g_assert (!tp_handle_set_is_member (mixin->remote_pending,
+ mixin->self_handle));
+
+ /* Indicate in the Group interface that a join is in progress */
+
+ add_remote_pending = tp_intset_new ();
+ tp_intset_add (add_remote_pending, mixin->self_handle);
+
+ tp_group_mixin_add_handle_owner (object, mixin->self_handle,
+ self->priv->conn->self_handle);
+ tp_group_mixin_change_members (object, "", NULL, NULL, NULL,
+ add_remote_pending, self->priv->conn->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (add_remote_pending);
+
+ /* Actually join the room. In a real implementation this would be a network
+ * round-trip - we don't have a network, so pretend that joining takes
+ * a short time */
+ g_timeout_add (self->priv->simulation_delay, (GSourceFunc) complete_join,
+ self);
+}
+
+
+static GObject *
+constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *object =
+ G_OBJECT_CLASS (example_csh_room_channel_parent_class)->constructor (type,
+ n_props, props);
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+ TpHandle self_handle;
+
+ tp_handle_ref (room_repo, self->priv->handle);
+
+ if (self->priv->initiator != 0)
+ tp_handle_ref (contact_repo, self->priv->initiator);
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn),
+ self->priv->object_path, self);
+
+ tp_text_mixin_init (object, G_STRUCT_OFFSET (ExampleCSHRoomChannel, text),
+ contact_repo);
+
+ tp_text_mixin_set_message_types (object,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ G_MAXUINT);
+
+ /* We start off remote-pending (if this CM supported other people inviting
+ * us, we'd start off local-pending in that case instead - but it doesn't),
+ * with this self-handle. */
+ self_handle = suggest_room_identity (self);
+
+ tp_group_mixin_init (object,
+ G_STRUCT_OFFSET (ExampleCSHRoomChannel, group),
+ contact_repo, self_handle);
+
+ /* Initially, we can't do anything. */
+ tp_group_mixin_change_flags (object,
+ TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES |
+ TP_CHANNEL_GROUP_FLAG_PROPERTIES,
+ 0);
+
+ /* Immediately attempt to join the group */
+ join_room (self);
+
+ return object;
+}
+
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_ROOM);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_ROOM);
+
+ g_value_set_string (value,
+ tp_handle_inspect (room_repo, self->priv->handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ /* this example CM doesn't yet support being invited into a chatroom,
+ * so the only way a channel can exist is if the user asked for it */
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ self->priv->initiator == 0
+ ? ""
+ : tp_handle_inspect (contact_repo, self->priv->initiator));
+ }
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, example_csh_room_channel_interfaces);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+ case PROP_SIMULATION_DELAY:
+ g_value_set_uint (value, self->priv->simulation_delay);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * room repo yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ /* similarly, we don't yet have the contact repo */
+ self->priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+ case PROP_SIMULATION_DELAY:
+ self->priv->simulation_delay = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+example_csh_room_channel_close (ExampleCSHRoomChannel *self)
+{
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ example_csh_room_channel_close (self);
+
+ ((GObjectClass *) example_csh_room_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+
+ if (self->priv->initiator != 0)
+ tp_handle_unref (contact_handles, self->priv->initiator);
+
+ tp_handle_unref (room_handles, self->priv->handle);
+ g_free (self->priv->object_path);
+
+ tp_text_mixin_finalize (object);
+
+ ((GObjectClass *) example_csh_room_channel_parent_class)->finalize (object);
+}
+
+
+static gboolean
+add_member (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ /* In a real implementation, if handle was mixin->self_handle we'd accept
+ * an invitation here; otherwise we'd invite the given contact.
+ * Here, we do nothing for now. */
+ return TRUE;
+}
+
+static gboolean
+remove_member_with_reason (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ guint reason,
+ GError **error)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+
+ if (handle == self->group.self_handle)
+ {
+ /* TODO: if simulating a channel where the user is an operator, let them
+ * kick themselves (like in IRC), resulting in different "network"
+ * messages */
+
+ example_csh_room_channel_close (self);
+ return TRUE;
+ }
+ else
+ {
+ /* TODO: also simulate some channels where the user is an operator and
+ * can kick people */
+ g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
+ "You can't eject other users from this channel");
+ return FALSE;
+ }
+}
+
+static void
+example_csh_room_channel_class_init (ExampleCSHRoomChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (ExampleCSHRoomChannelPrivate));
+
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Chatroom's ID",
+ "The string obtained by inspecting the MUC's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
+ "Delay between simulated network events",
+ 0, G_MAXUINT32, 500,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
+ param_spec);
+
+ tp_text_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCSHRoomChannelClass, text_class));
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCSHRoomChannelClass, dbus_properties_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleCSHRoomChannelClass, group_class),
+ add_member,
+ NULL);
+ tp_group_mixin_class_allow_self_removal (object_class);
+ tp_group_mixin_class_set_remove_with_reason_func (object_class,
+ remove_member_with_reason);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (iface);
+
+ example_csh_room_channel_close (self);
+
+ tp_svc_channel_return_from_close (context);
+}
+
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_ROOM,
+ self->priv->handle);
+}
+
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ example_csh_room_channel_interfaces);
+}
+
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+
+static void
+text_send (TpSvcChannelTypeText *iface,
+ guint type,
+ const gchar *text,
+ DBusGMethodInvocation *context)
+{
+ ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (iface);
+ time_t timestamp = time (NULL);
+
+ /* The /dev/null of text channels - we claim to have sent the message,
+ * but nothing more happens */
+ tp_svc_channel_type_text_emit_sent ((GObject *) self, timestamp, type, text);
+ tp_svc_channel_type_text_return_from_send (context);
+}
+
+
+static void
+text_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeTextClass *klass = iface;
+
+ tp_text_mixin_iface_init (iface, data);
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass, text_##x)
+ IMPLEMENT (send);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/csh/room.h b/qt4/tests/lib/glib/csh/room.h
new file mode 100644
index 000000000..1945543de
--- /dev/null
+++ b/qt4/tests/lib/glib/csh/room.h
@@ -0,0 +1,64 @@
+/*
+ * room.h - header for an example chatroom channel
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_CSH_ROOM_H
+#define EXAMPLE_CSH_ROOM_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+#include <telepathy-glib/text-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHRoomChannel ExampleCSHRoomChannel;
+typedef struct _ExampleCSHRoomChannelClass ExampleCSHRoomChannelClass;
+typedef struct _ExampleCSHRoomChannelPrivate ExampleCSHRoomChannelPrivate;
+
+GType example_csh_room_channel_get_type (void);
+
+#define EXAMPLE_TYPE_CSH_ROOM_CHANNEL \
+ (example_csh_room_channel_get_type ())
+#define EXAMPLE_CSH_ROOM_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CSH_ROOM_CHANNEL, \
+ ExampleCSHRoomChannel))
+#define EXAMPLE_CSH_ROOM_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CSH_ROOM_CHANNEL, \
+ ExampleCSHRoomChannelClass))
+#define EXAMPLE_IS_CSH_ROOM_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CSH_ROOM_CHANNEL))
+#define EXAMPLE_IS_CSH_ROOM_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CSH_ROOM_CHANNEL))
+#define EXAMPLE_CSH_ROOM_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_ROOM_CHANNEL, \
+ ExampleCSHRoomChannelClass))
+
+struct _ExampleCSHRoomChannelClass {
+ GObjectClass parent_class;
+
+ TpTextMixinClass text_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleCSHRoomChannel {
+ GObject parent;
+
+ TpTextMixin text;
+ TpGroupMixin group;
+
+ ExampleCSHRoomChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/debug.h b/qt4/tests/lib/glib/debug.h
new file mode 100644
index 000000000..60e070b4c
--- /dev/null
+++ b/qt4/tests/lib/glib/debug.h
@@ -0,0 +1,3 @@
+#undef DEBUG
+#define DEBUG(format, ...) \
+ g_debug ("%s: " format, G_STRFUNC, ##__VA_ARGS__)
diff --git a/qt4/tests/lib/glib/echo/CMakeLists.txt b/qt4/tests/lib/glib/echo/CMakeLists.txt
new file mode 100644
index 000000000..36696a30d
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/CMakeLists.txt
@@ -0,0 +1,18 @@
+if(ENABLE_TP_GLIB_TESTS)
+ include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+ set(example_cm_echo_SRCS
+ chan.c
+ chan.h
+ conn.c
+ conn.h
+ connection-manager.c
+ connection-manager.h
+ im-manager.c
+ im-manager.h
+ )
+
+ add_library(example-cm-echo STATIC ${example_cm_echo_SRCS})
+ target_link_libraries(example-cm-echo ${TPGLIB_LIBRARIES})
+ tpqt4_generate_manager_file(${CMAKE_CURRENT_SOURCE_DIR}/manager-file.py example_echo.manager connection-manager.c)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/echo/chan.c b/qt4/tests/lib/glib/echo/chan.c
new file mode 100644
index 000000000..90f52e0d1
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/chan.c
@@ -0,0 +1,528 @@
+/*
+ * chan.c - an example text channel talking to a particular
+ * contact. Similar code is used for 1-1 IM channels in many protocols
+ * (IRC private messages ("/query"), XMPP IM etc.)
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "chan.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+
+static void text_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+static void destroyable_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleEchoChannel,
+ example_echo_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DESTROYABLE,
+ destroyable_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ N_PROPS
+};
+
+struct _ExampleEchoChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+
+ /* These are really booleans, but gboolean is signed. Thanks, GLib */
+ unsigned closed:1;
+ unsigned disposed:1;
+};
+
+static const char * example_echo_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE,
+ NULL
+};
+
+static void
+example_echo_channel_init (ExampleEchoChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_CHANNEL,
+ ExampleEchoChannelPrivate);
+}
+
+static GObject *
+constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *object =
+ G_OBJECT_CLASS (example_echo_channel_parent_class)->constructor (type,
+ n_props, props);
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+
+ if (self->priv->initiator != 0)
+ tp_handle_ref (contact_repo, self->priv->initiator);
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn),
+ self->priv->object_path, self);
+
+ tp_text_mixin_init (object, G_STRUCT_OFFSET (ExampleEchoChannel, text),
+ contact_repo);
+
+ tp_text_mixin_set_message_types (object,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+ G_MAXUINT);
+
+ return object;
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value,
+ (self->priv->initiator == self->priv->conn->self_handle));
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ self->priv->initiator == 0
+ ? ""
+ : tp_handle_inspect (contact_repo, self->priv->initiator));
+ }
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, example_echo_channel_interfaces);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * contact repo yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ /* likewise */
+ self->priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) example_echo_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, self->priv->handle);
+
+ if (self->priv->initiator != 0)
+ tp_handle_unref (contact_handles, self->priv->initiator);
+
+ g_free (self->priv->object_path);
+
+ tp_text_mixin_finalize (object);
+
+ ((GObjectClass *) example_echo_channel_parent_class)->finalize (object);
+}
+
+static void
+example_echo_channel_class_init (ExampleEchoChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (ExampleEchoChannelPrivate));
+
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ tp_text_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleEchoChannelClass, text_class));
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleEchoChannelClass, dbus_properties_class));
+}
+
+static void
+example_echo_channel_close (ExampleEchoChannel *self)
+{
+ GObject *object = (GObject *) self;
+
+ if (!self->priv->closed)
+ {
+ TpHandle first_sender;
+
+ /* The manager wants to be able to respawn the channel if it has pending
+ * messages. When respawned, the channel must have the initiator set
+ * to the contact who sent us those messages (if it isn't already),
+ * and the messages must be marked as having been rescued so they
+ * don't get logged twice. */
+ if (tp_text_mixin_has_pending_messages (object, &first_sender))
+ {
+ if (self->priv->initiator != first_sender)
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle old_initiator = self->priv->initiator;
+
+ if (first_sender != 0)
+ tp_handle_ref (contact_repo, first_sender);
+
+ self->priv->initiator = first_sender;
+
+ if (old_initiator != 0)
+ tp_handle_unref (contact_repo, old_initiator);
+ }
+
+ tp_text_mixin_set_rescued (object);
+ }
+ else
+ {
+ /* No pending messages, so it's OK to really close */
+ self->priv->closed = TRUE;
+ }
+
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (iface);
+
+ example_echo_channel_close (self);
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ example_echo_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+text_send (TpSvcChannelTypeText *iface,
+ guint type,
+ const gchar *text,
+ DBusGMethodInvocation *context)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (iface);
+ time_t timestamp = time (NULL);
+ gchar *echo;
+ guint echo_type = type;
+
+ /* Send should return just before Sent is emitted. */
+ tp_svc_channel_type_text_return_from_send (context);
+
+ /* Tell the client that the message was submitted for sending */
+ tp_svc_channel_type_text_emit_sent ((GObject *) self, timestamp, type, text);
+
+ /* Pretend that the remote contact has replied. Normally, you'd
+ * call tp_text_mixin_receive or tp_text_mixin_receive_with_flags
+ * in response to network events */
+
+ switch (type)
+ {
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
+ echo = g_strdup_printf ("You said: %s", text);
+ break;
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
+ echo = g_strdup_printf ("notices that the user %s", text);
+ break;
+ case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
+ echo = g_strdup_printf ("You sent a notice: %s", text);
+ break;
+ default:
+ echo = g_strdup_printf ("You sent some weird message type, %u: \"%s\"",
+ type, text);
+ echo_type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+ }
+
+ tp_text_mixin_receive ((GObject *) self, echo_type, self->priv->handle,
+ timestamp, echo);
+
+ g_free (echo);
+}
+
+static void
+text_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeTextClass *klass = iface;
+
+ tp_text_mixin_iface_init (iface, data);
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass, text_##x)
+ IMPLEMENT (send);
+#undef IMPLEMENT
+}
+
+static void
+destroyable_destroy (TpSvcChannelInterfaceDestroyable *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleEchoChannel *self = EXAMPLE_ECHO_CHANNEL (iface);
+
+ tp_text_mixin_clear ((GObject *) self);
+ example_echo_channel_close (self);
+ g_assert (self->priv->closed);
+ tp_svc_channel_interface_destroyable_return_from_destroy (context);
+}
+
+static void
+destroyable_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelInterfaceDestroyableClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_interface_destroyable_implement_##x (klass, destroyable_##x)
+ IMPLEMENT (destroy);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/echo/chan.h b/qt4/tests/lib/glib/echo/chan.h
new file mode 100644
index 000000000..81afc2ee9
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/chan.h
@@ -0,0 +1,58 @@
+/*
+ * chan.h - header for an example channel
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_CHAN_H__
+#define __EXAMPLE_CHAN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/text-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEchoChannel ExampleEchoChannel;
+typedef struct _ExampleEchoChannelClass ExampleEchoChannelClass;
+typedef struct _ExampleEchoChannelPrivate ExampleEchoChannelPrivate;
+
+GType example_echo_channel_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_CHANNEL \
+ (example_echo_channel_get_type ())
+#define EXAMPLE_ECHO_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_ECHO_CHANNEL, \
+ ExampleEchoChannel))
+#define EXAMPLE_ECHO_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_ECHO_CHANNEL, \
+ ExampleEchoChannelClass))
+#define EXAMPLE_IS_ECHO_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_ECHO_CHANNEL))
+#define EXAMPLE_IS_ECHO_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_ECHO_CHANNEL))
+#define EXAMPLE_ECHO_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_CHANNEL, \
+ ExampleEchoChannelClass))
+
+struct _ExampleEchoChannelClass {
+ GObjectClass parent_class;
+ TpTextMixinClass text_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleEchoChannel {
+ GObject parent;
+ TpTextMixin text;
+
+ ExampleEchoChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif /* #ifndef __EXAMPLE_CHAN_H__ */
diff --git a/qt4/tests/lib/glib/echo/conn.c b/qt4/tests/lib/glib/echo/conn.c
new file mode 100644
index 000000000..24e0db07e
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/conn.c
@@ -0,0 +1,192 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+
+#include "im-manager.h"
+
+G_DEFINE_TYPE (ExampleEchoConnection,
+ example_echo_connection,
+ TP_TYPE_BASE_CONNECTION)
+
+/* type definition stuff */
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ N_PROPS
+};
+
+struct _ExampleEchoConnectionPrivate
+{
+ gchar *account;
+};
+
+static void
+example_echo_connection_init (ExampleEchoConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_CONNECTION,
+ ExampleEchoConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleEchoConnection *self = EXAMPLE_ECHO_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleEchoConnection *self = EXAMPLE_ECHO_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_utf8_strdown (g_value_get_string (value), -1);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleEchoConnection *self = EXAMPLE_ECHO_CONNECTION (object);
+
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (example_echo_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleEchoConnection *self = EXAMPLE_ECHO_CONNECTION (conn);
+
+ return g_strdup (self->priv->account);
+}
+
+static gchar *
+example_echo_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must not be empty");
+ return NULL;
+ }
+
+ return g_utf8_strdown (id, -1);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_echo_normalize_contact, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ g_ptr_array_add (ret, g_object_new (EXAMPLE_TYPE_ECHO_IM_MANAGER,
+ "connection", conn,
+ NULL));
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleEchoConnection *self = EXAMPLE_ECHO_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, NULL);
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static void
+example_echo_connection_class_init (ExampleEchoConnectionClass *klass)
+{
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ NULL };
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass, sizeof (ExampleEchoConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+}
diff --git a/qt4/tests/lib/glib/echo/conn.h b/qt4/tests/lib/glib/echo/conn.h
new file mode 100644
index 000000000..dfbf36576
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/conn.h
@@ -0,0 +1,55 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_ECHO_CONN_H__
+#define __EXAMPLE_ECHO_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEchoConnection ExampleEchoConnection;
+typedef struct _ExampleEchoConnectionClass ExampleEchoConnectionClass;
+typedef struct _ExampleEchoConnectionPrivate ExampleEchoConnectionPrivate;
+
+struct _ExampleEchoConnectionClass {
+ TpBaseConnectionClass parent_class;
+};
+
+struct _ExampleEchoConnection {
+ TpBaseConnection parent;
+
+ ExampleEchoConnectionPrivate *priv;
+};
+
+GType example_echo_connection_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_ECHO_CONNECTION \
+ (example_echo_connection_get_type ())
+#define EXAMPLE_ECHO_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_CONNECTION, \
+ ExampleEchoConnection))
+#define EXAMPLE_ECHO_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_CONNECTION, \
+ ExampleEchoConnectionClass))
+#define EXAMPLE_IS_ECHO_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_CONNECTION))
+#define EXAMPLE_IS_ECHO_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_CONNECTION))
+#define EXAMPLE_ECHO_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_CONNECTION, \
+ ExampleEchoConnectionClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo/connection-manager.c b/qt4/tests/lib/glib/echo/connection-manager.c
new file mode 100644
index 000000000..3493429b3
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/connection-manager.c
@@ -0,0 +1,87 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleEchoConnectionManager,
+ example_echo_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+/* type definition stuff */
+
+static void
+example_echo_connection_manager_init (ExampleEchoConnectionManager *self)
+{
+}
+
+/* private data */
+
+typedef struct {
+ gchar *account;
+} ExampleParams;
+
+#include "_gen/param-spec-struct.h"
+
+static gpointer
+alloc_params (void)
+{
+ return g_slice_new0 (ExampleParams);
+}
+
+static void
+free_params (gpointer p)
+{
+ ExampleParams *params = p;
+
+ g_free (params->account);
+
+ g_slice_free (ExampleParams, params);
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+ { "example", example_echo_example_params, alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ ExampleParams *params = parsed_params;
+ ExampleEchoConnection *conn = EXAMPLE_ECHO_CONNECTION
+ (g_object_new (EXAMPLE_TYPE_ECHO_CONNECTION,
+ "account", params->account,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+example_echo_connection_manager_class_init (
+ ExampleEchoConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_echo";
+ base_class->protocol_params = example_protocols;
+}
diff --git a/qt4/tests/lib/glib/echo/connection-manager.h b/qt4/tests/lib/glib/echo/connection-manager.h
new file mode 100644
index 000000000..048387340
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/connection-manager.h
@@ -0,0 +1,59 @@
+/*
+ * manager.h - header for an example connection manager
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_ECHO_CONNECTION_MANAGER_H__
+#define __EXAMPLE_ECHO_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEchoConnectionManager ExampleEchoConnectionManager;
+typedef struct _ExampleEchoConnectionManagerPrivate
+ ExampleEchoConnectionManagerPrivate;
+typedef struct _ExampleEchoConnectionManagerClass
+ ExampleEchoConnectionManagerClass;
+typedef struct _ExampleEchoConnectionManagerClassPrivate
+ ExampleEchoConnectionManagerClassPrivate;
+
+struct _ExampleEchoConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ ExampleEchoConnectionManagerClassPrivate *priv;
+};
+
+struct _ExampleEchoConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleEchoConnectionManagerPrivate *priv;
+};
+
+GType example_echo_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_ECHO_CONNECTION_MANAGER \
+ (example_echo_connection_manager_get_type ())
+#define EXAMPLE_ECHO_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_CONNECTION_MANAGER, \
+ ExampleEchoConnectionManager))
+#define EXAMPLE_ECHO_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_CONNECTION_MANAGER, \
+ ExampleEchoConnectionManagerClass))
+#define EXAMPLE_IS_ECHO_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_CONNECTION_MANAGER))
+#define EXAMPLE_IS_ECHO_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_CONNECTION_MANAGER))
+#define EXAMPLE_ECHO_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_CONNECTION_MANAGER, \
+ ExampleEchoConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo/im-manager.c b/qt4/tests/lib/glib/echo/im-manager.c
new file mode 100644
index 000000000..7403af282
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/im-manager.c
@@ -0,0 +1,378 @@
+/*
+ * im-manager.c - an example channel manager for channels talking to a
+ * particular contact. Similar code is used for 1-1 IM channels in many
+ * protocols (IRC private messages ("/query"), XMPP IM etc.)
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "im-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "chan.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleEchoImManager,
+ example_echo_im_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_CONNECTION = 1,
+ N_PROPS
+};
+
+struct _ExampleEchoImManagerPrivate
+{
+ TpBaseConnection *conn;
+
+ /* GUINT_TO_POINTER (handle) => ExampleEchoChannel */
+ GHashTable *channels;
+ gulong status_changed_id;
+};
+
+static void
+example_echo_im_manager_init (ExampleEchoImManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_IM_MANAGER,
+ ExampleEchoImManagerPrivate);
+
+ self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+}
+
+static void example_echo_im_manager_close_all (ExampleEchoImManager *self);
+
+static void
+dispose (GObject *object)
+{
+ ExampleEchoImManager *self = EXAMPLE_ECHO_IM_MANAGER (object);
+
+ example_echo_im_manager_close_all (self);
+ g_assert (self->priv->channels == NULL);
+
+ ((GObjectClass *) example_echo_im_manager_parent_class)->dispose (object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEchoImManager *self = EXAMPLE_ECHO_IM_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEchoImManager *self = EXAMPLE_ECHO_IM_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * channel manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleEchoImManager *self)
+{
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ example_echo_im_manager_close_all (self);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleEchoImManager *self = EXAMPLE_ECHO_IM_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_echo_im_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_echo_im_manager_class_init (ExampleEchoImManagerClass *klass)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ param_spec = g_param_spec_object ("connection", "Connection object",
+ "The connection that owns this channel manager",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ g_type_class_add_private (klass, sizeof (ExampleEchoImManagerPrivate));
+}
+
+static void
+example_echo_im_manager_close_all (ExampleEchoImManager *self)
+{
+ if (self->priv->channels != NULL)
+ {
+ GHashTable *tmp = self->priv->channels;
+
+ self->priv->channels = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+example_echo_im_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleEchoImManager *self = EXAMPLE_ECHO_IM_MANAGER (iface);
+ GHashTableIter iter;
+ gpointer handle, channel;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &handle, &channel))
+ {
+ callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+ }
+}
+
+static void
+channel_closed_cb (ExampleEchoChannel *chan,
+ ExampleEchoImManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->channels != NULL)
+ {
+ TpHandle handle;
+ gboolean really_destroyed;
+
+ g_object_get (chan,
+ "handle", &handle,
+ "channel-destroyed", &really_destroyed,
+ NULL);
+
+ /* Re-announce the channel if it's not yet ready to go away (pending
+ * messages) */
+ if (really_destroyed)
+ {
+ g_hash_table_remove (self->priv->channels,
+ GUINT_TO_POINTER (handle));
+ }
+ else
+ {
+ tp_channel_manager_emit_new_channel (self,
+ TP_EXPORTABLE_CHANNEL (chan), NULL);
+ }
+ }
+}
+
+static void
+new_channel (ExampleEchoImManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token)
+{
+ ExampleEchoChannel *chan;
+ gchar *object_path;
+ GSList *requests = NULL;
+
+ object_path = g_strdup_printf ("%s/EchoChannel%u",
+ self->priv->conn->object_path, handle);
+
+ chan = g_object_new (EXAMPLE_TYPE_ECHO_CHANNEL,
+ "connection", self->priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ "initiator-handle", initiator,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", (GCallback) channel_closed_cb, self);
+
+ /* self->priv->channels takes ownership of 'chan' */
+ g_hash_table_insert (self->priv->channels, GUINT_TO_POINTER (handle), chan);
+
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+ g_slist_free (requests);
+}
+
+static const gchar * const fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ NULL
+};
+
+static const gchar * const allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ NULL
+};
+
+static void
+example_echo_im_manager_foreach_channel_class (TpChannelManager *manager,
+ TpChannelManagerChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+ NULL);
+
+
+ func (manager, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_echo_im_manager_request (ExampleEchoImManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandle handle;
+ ExampleEchoChannel *chan;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ TP_IFACE_CHANNEL_TYPE_TEXT))
+ {
+ return FALSE;
+ }
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ fixed_properties, allowed_properties, &error))
+ {
+ goto error;
+ }
+
+ chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+
+ if (chan == NULL)
+ {
+ new_channel (self, handle, self->priv->conn->self_handle, request_token);
+ }
+ else if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "An echo channel to contact #%u already exists", handle);
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ }
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+example_echo_im_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_echo_im_manager_request (EXAMPLE_ECHO_IM_MANAGER (manager),
+ request_token, request_properties, TRUE);
+}
+
+static gboolean
+example_echo_im_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_echo_im_manager_request (EXAMPLE_ECHO_IM_MANAGER (manager),
+ request_token, request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_echo_im_manager_foreach_channel;
+ iface->foreach_channel_class = example_echo_im_manager_foreach_channel_class;
+ iface->create_channel = example_echo_im_manager_create_channel;
+ iface->ensure_channel = example_echo_im_manager_ensure_channel;
+ /* In this channel manager, Request has the same semantics as Ensure */
+ iface->request_channel = example_echo_im_manager_ensure_channel;
+}
diff --git a/qt4/tests/lib/glib/echo/im-manager.h b/qt4/tests/lib/glib/echo/im-manager.h
new file mode 100644
index 000000000..9022ba934
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/im-manager.h
@@ -0,0 +1,54 @@
+/*
+ * im-manager.h - header for an example channel manager
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __EXAMPLE_ECHO_IM_MANAGER_H__
+#define __EXAMPLE_ECHO_IM_MANAGER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEchoImManager ExampleEchoImManager;
+typedef struct _ExampleEchoImManagerClass ExampleEchoImManagerClass;
+typedef struct _ExampleEchoImManagerPrivate ExampleEchoImManagerPrivate;
+
+struct _ExampleEchoImManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _ExampleEchoImManager {
+ GObject parent;
+
+ ExampleEchoImManagerPrivate *priv;
+};
+
+GType example_echo_im_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_ECHO_IM_MANAGER \
+ (example_echo_im_manager_get_type ())
+#define EXAMPLE_ECHO_IM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_IM_MANAGER, \
+ ExampleEchoImManager))
+#define EXAMPLE_ECHO_IM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_IM_MANAGER, \
+ ExampleEchoImManagerClass))
+#define EXAMPLE_IS_ECHO_IM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_IM_MANAGER))
+#define EXAMPLE_IS_ECHO_IM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_IM_MANAGER))
+#define EXAMPLE_ECHO_IM_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_IM_MANAGER, \
+ ExampleEchoImManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo/manager-file.py b/qt4/tests/lib/glib/echo/manager-file.py
new file mode 100644
index 000000000..4c173b9a0
--- /dev/null
+++ b/qt4/tests/lib/glib/echo/manager-file.py
@@ -0,0 +1,19 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_echo'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'tp_cm_param_filter_string_nonempty',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ },
+ }
+STRUCTS = {
+ 'example': 'ExampleParams'
+ }
diff --git a/qt4/tests/lib/glib/echo2/CMakeLists.txt b/qt4/tests/lib/glib/echo2/CMakeLists.txt
new file mode 100644
index 000000000..54d998652
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/CMakeLists.txt
@@ -0,0 +1,17 @@
+if(ENABLE_TP_GLIB_TESTS)
+ set(example_cm_echo2_SRCS
+ chan.c
+ chan.h
+ conn.c
+ connection-manager.c
+ connection-manager.h
+ conn.h
+ im-manager.c
+ im-manager.h
+ protocol.c
+ protocol.h)
+
+ add_library(example-cm-echo2 STATIC ${example_cm_echo2_SRCS})
+ target_link_libraries(example-cm-echo2 ${TPGLIB_LIBRARIES})
+ tpqt4_generate_manager_file(${CMAKE_CURRENT_SOURCE_DIR}/manager-file.py example_echo_2.manager connection-manager.c)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/echo2/chan.c b/qt4/tests/lib/glib/echo2/chan.c
new file mode 100644
index 000000000..a431543f6
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/chan.c
@@ -0,0 +1,661 @@
+/*
+ * chan.c - an example text channel talking to a particular
+ * contact. Similar code is used for 1-1 IM channels in many protocols
+ * (IRC private messages ("/query"), XMPP IM etc.)
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "chan.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+
+#include <string.h>
+
+static void channel_iface_init (gpointer iface, gpointer data);
+static void chat_state_iface_init (gpointer iface, gpointer data);
+static void destroyable_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleEcho2Channel,
+ example_echo_2_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
+ tp_message_mixin_text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
+ tp_message_mixin_messages_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CHAT_STATE,
+ chat_state_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DESTROYABLE,
+ destroyable_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ N_PROPS
+};
+
+struct _ExampleEcho2ChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+
+ /* These are really booleans, but gboolean is signed. Thanks, GLib */
+ unsigned closed:1;
+ unsigned disposed:1;
+};
+
+static const char * example_echo_2_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES,
+ TP_IFACE_CHANNEL_INTERFACE_CHAT_STATE,
+ TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE,
+ NULL };
+
+static void
+example_echo_2_channel_init (ExampleEcho2Channel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_2_CHANNEL,
+ ExampleEcho2ChannelPrivate);
+}
+
+
+static void
+send_message (GObject *object,
+ TpMessage *message,
+ TpMessageSendingFlags flags)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+ time_t timestamp = time (NULL);
+ guint len = tp_message_count_parts (message);
+ const gchar *content = NULL;
+ TpMessage *received = NULL;
+ guint i;
+
+ if (tp_asv_get_string (tp_message_peek (message, 0), "interface") != NULL)
+ {
+ /* this message is interface-specific - let's not echo it */
+ goto finally;
+ }
+
+ content = tp_asv_get_string (tp_message_peek (message, 1), "content");
+ if (content && strstr (content, "(fail)") != NULL)
+ {
+ TpMessage *delivery_report = tp_message_new (self->priv->conn, 1, len);
+
+ tp_message_set_uint32 (delivery_report, 0, "message-type",
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT);
+ tp_message_set_handle (delivery_report, 0, "message-sender",
+ TP_HANDLE_TYPE_CONTACT, self->priv->handle);
+ tp_message_set_int64 (delivery_report, 0, "message-received",
+ timestamp);
+
+ tp_message_set_uint32 (delivery_report, 0, "delivery-status",
+ TP_DELIVERY_STATUS_PERMANENTLY_FAILED);
+ tp_message_set_uint32 (delivery_report, 0, "delivery-error",
+ TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED);
+ tp_message_set_string (delivery_report, 0, "delivery-error-message",
+ "You asked for it");
+
+ tp_message_set_string (delivery_report, 0, "delivery-token", "1111");
+
+ tp_cm_message_take_message (delivery_report, 0, "delivery-echo", message);
+
+ tp_message_mixin_take_received (object, delivery_report);
+
+ return;
+ }
+
+ received = tp_message_new (self->priv->conn, 1, len);
+
+ /* Copy/modify the headers for the "received" message */
+ {
+ TpChannelTextMessageType message_type;
+ gboolean valid;
+
+ tp_message_set_handle (received, 0, "message-sender",
+ TP_HANDLE_TYPE_CONTACT, self->priv->handle);
+
+ tp_message_set_string (received, 0, "message-token", "0000");
+ tp_message_set_string (received, 0, "supersedes", "1234");
+
+ if (!tp_message_mixin_has_pending_messages (object, NULL))
+ tp_message_set_boolean (received, 0, "scrollback", TRUE);
+
+ message_type = tp_asv_get_uint32 (tp_message_peek (message, 0),
+ "message-type", &valid);
+
+ /* The check for 'valid' means that if message-type is missing or of the
+ * wrong type, fall back to NORMAL (this is in fact a no-op, since
+ * NORMAL == 0 and tp_asv_get_uint32 returns 0 on missing or wrongly
+ * typed values) */
+ if (valid && message_type != TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL)
+ tp_message_set_uint32 (received, 0, "message-type", message_type);
+
+ tp_message_set_uint32 (received, 0, "message-sent", timestamp);
+ tp_message_set_uint32 (received, 0, "message-received", timestamp);
+ }
+
+ /* Copy the content for the "received" message */
+ for (i = 1; i < len; i++)
+ {
+ const GHashTable *input = tp_message_peek (message, i);
+ const gchar *s;
+ const GValue *value;
+ guint j;
+
+ /* in this example we ignore interface-specific parts */
+
+ s = tp_asv_get_string (input, "content-type");
+
+ if (s == NULL)
+ continue;
+
+ s = tp_asv_get_string (input, "interface");
+
+ if (s != NULL)
+ continue;
+
+ /* OK, we want to copy this part */
+
+ j = tp_message_append_part (received);
+
+ s = tp_asv_get_string (input, "content-type");
+ g_assert (s != NULL); /* already checked */
+ tp_message_set_string (received, j, "content-type", s);
+
+ s = tp_asv_get_string (input, "identifier");
+
+ if (s != NULL)
+ tp_message_set_string (received, j, "identifier", s);
+
+ s = tp_asv_get_string (input, "alternative");
+
+ if (s != NULL)
+ tp_message_set_string (received, j, "alternative", s);
+
+ s = tp_asv_get_string (input, "lang");
+
+ if (s != NULL)
+ tp_message_set_string (received, j, "lang", s);
+
+ value = tp_asv_lookup (input, "content");
+
+ if (value != NULL)
+ tp_message_set (received, j, "content", value);
+ }
+
+finally:
+ /* "OK, we've sent the message" (after calling this, message must not be
+ * dereferenced) */
+ tp_message_mixin_sent (object, message, flags, "", NULL);
+
+ if (received != NULL)
+ {
+ /* Pretend the other user sent us back the same message. After this call,
+ * the received message is owned by the mixin */
+ tp_message_mixin_take_received (object, received);
+ }
+}
+
+
+static GObject *
+constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *object =
+ G_OBJECT_CLASS (example_echo_2_channel_parent_class)->constructor (type,
+ n_props, props);
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ static TpChannelTextMessageType const types[] = {
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE
+ };
+ static const char * const content_types[] = { "*/*", NULL };
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+
+ if (self->priv->initiator != 0)
+ tp_handle_ref (contact_repo, self->priv->initiator);
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn),
+ self->priv->object_path, self);
+
+ tp_message_mixin_init (object, G_STRUCT_OFFSET (ExampleEcho2Channel, text),
+ self->priv->conn);
+
+ tp_message_mixin_implement_sending (object, send_message,
+ (sizeof (types) / sizeof (types[0])), types,
+ TP_MESSAGE_PART_SUPPORT_FLAG_ONE_ATTACHMENT |
+ TP_MESSAGE_PART_SUPPORT_FLAG_MULTIPLE_ATTACHMENTS,
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES,
+ content_types);
+
+ return object;
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value,
+ (self->priv->initiator == self->priv->conn->self_handle));
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ self->priv->initiator == 0
+ ? ""
+ : tp_handle_inspect (contact_repo, self->priv->initiator));
+ }
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, example_echo_2_channel_interfaces);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * contact repo yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ /* likewise */
+ self->priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) example_echo_2_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, self->priv->handle);
+
+ if (self->priv->initiator != 0)
+ tp_handle_unref (contact_handles, self->priv->initiator);
+
+ g_free (self->priv->object_path);
+
+ tp_message_mixin_finalize (object);
+
+ ((GObjectClass *) example_echo_2_channel_parent_class)->finalize (object);
+}
+
+static void
+example_echo_2_channel_class_init (ExampleEcho2ChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (ExampleEcho2ChannelPrivate));
+
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (ExampleEcho2ChannelClass, dbus_properties_class));
+
+ tp_message_mixin_init_dbus_properties (object_class);
+}
+
+static void
+example_echo_2_channel_close (ExampleEcho2Channel *self)
+{
+ GObject *object = (GObject *) self;
+
+ if (!self->priv->closed)
+ {
+ TpHandle first_sender;
+
+ /* The manager wants to be able to respawn the channel if it has pending
+ * messages. When respawned, the channel must have the initiator set
+ * to the contact who sent us those messages (if it isn't already),
+ * and the messages must be marked as having been rescued so they
+ * don't get logged twice. */
+ if (tp_message_mixin_has_pending_messages (object, &first_sender))
+ {
+ if (self->priv->initiator != first_sender)
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpHandle old_initiator = self->priv->initiator;
+
+ if (first_sender != 0)
+ tp_handle_ref (contact_repo, first_sender);
+
+ self->priv->initiator = first_sender;
+
+ if (old_initiator != 0)
+ tp_handle_unref (contact_repo, old_initiator);
+ }
+
+ tp_message_mixin_set_rescued (object);
+ }
+ else
+ {
+ /* No pending messages, so it's OK to really close */
+ self->priv->closed = TRUE;
+ }
+
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+
+ example_echo_2_channel_close (self);
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ example_echo_2_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+chat_state_set_chat_state(TpSvcChannelInterfaceChatState *iface,
+ guint state,
+ DBusGMethodInvocation *context)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+ GError *error = NULL;
+
+ if (state >= NUM_TP_CHANNEL_CHAT_STATES)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "invalid state: %u", state);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_channel_interface_chat_state_emit_chat_state_changed (
+ iface, self->priv->conn->self_handle, state);
+
+ tp_svc_channel_interface_chat_state_return_from_set_chat_state (context);
+}
+
+static void
+chat_state_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelInterfaceChatStateClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_interface_chat_state_implement_##x (klass, chat_state_##x)
+ IMPLEMENT (set_chat_state);
+#undef IMPLEMENT
+}
+
+static void
+destroyable_destroy (TpSvcChannelInterfaceDestroyable *iface,
+ DBusGMethodInvocation *context)
+{
+ ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+
+ tp_message_mixin_clear ((GObject *) self);
+ example_echo_2_channel_close (self);
+ g_assert (self->priv->closed);
+ tp_svc_channel_interface_destroyable_return_from_destroy (context);
+}
+
+static void
+destroyable_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelInterfaceDestroyableClass *klass = iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_channel_interface_destroyable_implement_##x (klass, destroyable_##x)
+ IMPLEMENT (destroy);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/echo2/chan.h b/qt4/tests/lib/glib/echo2/chan.h
new file mode 100644
index 000000000..049991ed9
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/chan.h
@@ -0,0 +1,58 @@
+/*
+ * chan.h - header for an example channel
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_ECHO_MESSAGE_PARTS_CHAN_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_CHAN_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/message-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2Channel ExampleEcho2Channel;
+typedef struct _ExampleEcho2ChannelClass ExampleEcho2ChannelClass;
+typedef struct _ExampleEcho2ChannelPrivate ExampleEcho2ChannelPrivate;
+
+GType example_echo_2_channel_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_CHANNEL \
+ (example_echo_2_channel_get_type ())
+#define EXAMPLE_ECHO_2_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_ECHO_2_CHANNEL, \
+ ExampleEcho2Channel))
+#define EXAMPLE_ECHO_2_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_ECHO_2_CHANNEL, \
+ ExampleEcho2ChannelClass))
+#define EXAMPLE_IS_ECHO_2_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_ECHO_2_CHANNEL))
+#define EXAMPLE_IS_ECHO_2_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_ECHO_2_CHANNEL))
+#define EXAMPLE_ECHO_2_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_2_CHANNEL, \
+ ExampleEcho2ChannelClass))
+
+struct _ExampleEcho2ChannelClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleEcho2Channel {
+ GObject parent;
+ TpMessageMixin text;
+
+ ExampleEcho2ChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo2/conn.c b/qt4/tests/lib/glib/echo2/conn.c
new file mode 100644
index 000000000..46531b3fd
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/conn.c
@@ -0,0 +1,194 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+
+#include "im-manager.h"
+#include "protocol.h"
+
+G_DEFINE_TYPE (ExampleEcho2Connection,
+ example_echo_2_connection,
+ TP_TYPE_BASE_CONNECTION)
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ N_PROPS
+};
+
+struct _ExampleEcho2ConnectionPrivate
+{
+ gchar *account;
+};
+
+static void
+example_echo_2_connection_init (ExampleEcho2Connection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ ExampleEcho2ConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_utf8_strdown (g_value_get_string (value), -1);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+finalize (GObject *object)
+{
+ ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (object);
+
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (example_echo_2_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (conn);
+
+ return g_strdup (self->priv->account);
+}
+
+static gchar *
+example_normalize_contact (TpHandleRepoIface *repo G_GNUC_UNUSED,
+ const gchar *id,
+ gpointer context G_GNUC_UNUSED,
+ GError **error)
+{
+ return example_echo_2_protocol_normalize_contact (id, error);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, example_normalize_contact, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+ GPtrArray *ret = g_ptr_array_sized_new (1);
+
+ g_ptr_array_add (ret, g_object_new (EXAMPLE_TYPE_ECHO_2_IM_MANAGER,
+ "connection", conn,
+ NULL));
+
+ return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (conn);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished, but here
+ * we can do it immediately. */
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, NULL);
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ return TRUE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished, but here
+ * we can do it immediately. */
+ tp_base_connection_finish_shutdown (conn);
+}
+
+static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ NULL };
+
+const gchar * const *
+example_echo_2_connection_get_possible_interfaces (void)
+{
+ /* in this example CM we don't have any extra interfaces that are sometimes,
+ * but not always, present */
+ return interfaces_always_present;
+}
+
+static void
+example_echo_2_connection_class_init (ExampleEcho2ConnectionClass *klass)
+{
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass, sizeof (ExampleEcho2ConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_managers = create_channel_managers;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+}
diff --git a/qt4/tests/lib/glib/echo2/conn.h b/qt4/tests/lib/glib/echo2/conn.h
new file mode 100644
index 000000000..bf3c377fc
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/conn.h
@@ -0,0 +1,59 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_ECHO_MESSAGE_PARTS_CONN_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_CONN_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2Connection ExampleEcho2Connection;
+typedef struct _ExampleEcho2ConnectionClass ExampleEcho2ConnectionClass;
+typedef struct _ExampleEcho2ConnectionPrivate ExampleEcho2ConnectionPrivate;
+
+struct _ExampleEcho2ConnectionClass {
+ TpBaseConnectionClass parent_class;
+};
+
+struct _ExampleEcho2Connection {
+ TpBaseConnection parent;
+
+ ExampleEcho2ConnectionPrivate *priv;
+};
+
+GType example_echo_2_connection_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_CONNECTION \
+ (example_echo_2_connection_get_type ())
+#define EXAMPLE_ECHO_2_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_2_CONNECTION, \
+ ExampleEcho2Connection))
+#define EXAMPLE_ECHO_2_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_2_CONNECTION, \
+ ExampleEcho2ConnectionClass))
+#define EXAMPLE_IS_ECHO_2_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_2_CONNECTION))
+#define EXAMPLE_IS_ECHO_2_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_2_CONNECTION))
+#define EXAMPLE_ECHO_2_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_2_CONNECTION, \
+ ExampleEcho2ConnectionClass))
+
+const gchar * const *example_echo_2_connection_get_guaranteed_interfaces (
+ void);
+const gchar * const *example_echo_2_connection_get_possible_interfaces (
+ void);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo2/connection-manager.c b/qt4/tests/lib/glib/echo2/connection-manager.c
new file mode 100644
index 000000000..c5618cf19
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/connection-manager.c
@@ -0,0 +1,81 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright © 2007-2010 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "connection-manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "protocol.h"
+
+G_DEFINE_TYPE (ExampleEcho2ConnectionManager,
+ example_echo_2_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+/* type definition stuff */
+
+static void
+example_echo_2_connection_manager_init (
+ ExampleEcho2ConnectionManager *self)
+{
+}
+
+static void
+example_echo_2_connection_manager_constructed (GObject *object)
+{
+ ExampleEcho2ConnectionManager *self =
+ EXAMPLE_ECHO_2_CONNECTION_MANAGER (object);
+ TpBaseConnectionManager *base = (TpBaseConnectionManager *) self;
+ void (*constructed) (GObject *) =
+ ((GObjectClass *) example_echo_2_connection_manager_parent_class)->constructed;
+ TpBaseProtocol *protocol;
+
+ if (constructed != NULL)
+ constructed (object);
+
+ protocol = g_object_new (EXAMPLE_TYPE_ECHO_2_PROTOCOL,
+ "name", "example",
+ NULL);
+ tp_base_connection_manager_add_protocol (base, protocol);
+ g_object_unref (protocol);
+}
+
+static const TpCMProtocolSpec no_protocols[] = {
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Protocol's new_connection() should be called instead");
+ return NULL;
+}
+
+static void
+example_echo_2_connection_manager_class_init (
+ ExampleEcho2ConnectionManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ object_class->constructed = example_echo_2_connection_manager_constructed;
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "example_echo_2";
+ base_class->protocol_params = no_protocols;
+}
diff --git a/qt4/tests/lib/glib/echo2/connection-manager.h b/qt4/tests/lib/glib/echo2/connection-manager.h
new file mode 100644
index 000000000..645ac9524
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/connection-manager.h
@@ -0,0 +1,64 @@
+/*
+ * manager.h - header for an example connection manager
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_ECHO_MESSAGE_PARTS_MANAGER_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_MANAGER_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2ConnectionManager
+ ExampleEcho2ConnectionManager;
+typedef struct _ExampleEcho2ConnectionManagerPrivate
+ ExampleEcho2ConnectionManagerPrivate;
+typedef struct _ExampleEcho2ConnectionManagerClass
+ ExampleEcho2ConnectionManagerClass;
+typedef struct _ExampleEcho2ConnectionManagerClassPrivate
+ ExampleEcho2ConnectionManagerClassPrivate;
+
+struct _ExampleEcho2ConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ ExampleEcho2ConnectionManagerClassPrivate *priv;
+};
+
+struct _ExampleEcho2ConnectionManager {
+ TpBaseConnectionManager parent;
+
+ ExampleEcho2ConnectionManagerPrivate *priv;
+};
+
+GType example_echo_2_connection_manager_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER \
+ (example_echo_2_connection_manager_get_type ())
+#define EXAMPLE_ECHO_2_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER, \
+ ExampleEcho2ConnectionManager))
+#define EXAMPLE_ECHO_2_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER, \
+ ExampleEcho2ConnectionManagerClass))
+#define EXAMPLE_IS_ECHO_2_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER))
+#define EXAMPLE_IS_ECHO_2_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER))
+#define EXAMPLE_ECHO_2_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER, \
+ ExampleEcho2ConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo2/im-manager.c b/qt4/tests/lib/glib/echo2/im-manager.c
new file mode 100644
index 000000000..3fb550d8f
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/im-manager.c
@@ -0,0 +1,377 @@
+/*
+ * im-manager.c - an example channel manager for channels talking to a
+ * particular contact. Similar code is used for 1-1 IM channels in many
+ * protocols (IRC private messages ("/query"), XMPP IM etc.)
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "im-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "chan.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleEcho2ImManager,
+ example_echo_2_im_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_CONNECTION = 1,
+ N_PROPS
+};
+
+struct _ExampleEcho2ImManagerPrivate
+{
+ TpBaseConnection *conn;
+
+ /* GUINT_TO_POINTER (handle) => ExampleEcho2Channel */
+ GHashTable *channels;
+ gulong status_changed_id;
+};
+
+static void
+example_echo_2_im_manager_init (ExampleEcho2ImManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_2_IM_MANAGER,
+ ExampleEcho2ImManagerPrivate);
+
+ self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+}
+
+static void example_echo_2_im_manager_close_all (ExampleEcho2ImManager *self);
+
+static void
+dispose (GObject *object)
+{
+ ExampleEcho2ImManager *self = EXAMPLE_ECHO_2_IM_MANAGER (object);
+
+ example_echo_2_im_manager_close_all (self);
+ g_assert (self->priv->channels == NULL);
+
+ ((GObjectClass *) example_echo_2_im_manager_parent_class)->dispose (object);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEcho2ImManager *self = EXAMPLE_ECHO_2_IM_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ExampleEcho2ImManager *self = EXAMPLE_ECHO_2_IM_MANAGER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ /* We don't ref the connection, because it owns a reference to the
+ * channel manager, and it guarantees that the manager's lifetime is
+ * less than its lifetime */
+ self->priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ ExampleEcho2ImManager *self)
+{
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ example_echo_2_im_manager_close_all (self);
+}
+
+static void
+constructed (GObject *object)
+{
+ ExampleEcho2ImManager *self = EXAMPLE_ECHO_2_IM_MANAGER (object);
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) example_echo_2_im_manager_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+ "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_echo_2_im_manager_class_init (ExampleEcho2ImManagerClass *klass)
+{
+ GParamSpec *param_spec;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = constructed;
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ param_spec = g_param_spec_object ("connection", "Connection object",
+ "The connection that owns this channel manager",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ g_type_class_add_private (klass, sizeof (ExampleEcho2ImManagerPrivate));
+}
+
+static void
+example_echo_2_im_manager_close_all (ExampleEcho2ImManager *self)
+{
+ if (self->priv->channels != NULL)
+ {
+ GHashTable *tmp = self->priv->channels;
+
+ self->priv->channels = NULL;
+ g_hash_table_destroy (tmp);
+ }
+
+ if (self->priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (self->priv->conn,
+ self->priv->status_changed_id);
+ self->priv->status_changed_id = 0;
+ }
+}
+
+static void
+example_echo_2_im_manager_foreach_channel (TpChannelManager *iface,
+ TpExportableChannelFunc callback,
+ gpointer user_data)
+{
+ ExampleEcho2ImManager *self = EXAMPLE_ECHO_2_IM_MANAGER (iface);
+ GHashTableIter iter;
+ gpointer handle, channel;
+
+ g_hash_table_iter_init (&iter, self->priv->channels);
+
+ while (g_hash_table_iter_next (&iter, &handle, &channel))
+ {
+ callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+ }
+}
+
+static void
+channel_closed_cb (ExampleEcho2Channel *chan,
+ ExampleEcho2ImManager *self)
+{
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (self->priv->channels != NULL)
+ {
+ TpHandle handle;
+ gboolean really_destroyed;
+
+ g_object_get (chan,
+ "handle", &handle,
+ "channel-destroyed", &really_destroyed,
+ NULL);
+
+ /* Re-announce the channel if it's not yet ready to go away (pending
+ * messages) */
+ if (really_destroyed)
+ {
+ g_hash_table_remove (self->priv->channels,
+ GUINT_TO_POINTER (handle));
+ }
+ else
+ {
+ tp_channel_manager_emit_new_channel (self,
+ TP_EXPORTABLE_CHANNEL (chan), NULL);
+ }
+ }
+}
+
+static void
+new_channel (ExampleEcho2ImManager *self,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token)
+{
+ ExampleEcho2Channel *chan;
+ gchar *object_path;
+ GSList *requests = NULL;
+
+ object_path = g_strdup_printf ("%s/Echo2Channel%u",
+ self->priv->conn->object_path, handle);
+
+ chan = g_object_new (EXAMPLE_TYPE_ECHO_2_CHANNEL,
+ "connection", self->priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ "initiator-handle", initiator,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", (GCallback) channel_closed_cb, self);
+
+ g_hash_table_insert (self->priv->channels, GUINT_TO_POINTER (handle), chan);
+
+ if (request_token != NULL)
+ requests = g_slist_prepend (requests, request_token);
+
+ tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
+ requests);
+ g_slist_free (requests);
+}
+
+static const gchar * const fixed_properties[] = {
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+ NULL
+};
+
+static const gchar * const allowed_properties[] = {
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ NULL
+};
+
+static void
+example_echo_2_im_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
+ NULL);
+
+ func (type, table, allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+static gboolean
+example_echo_2_im_manager_request (ExampleEcho2ImManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ TpHandle handle;
+ ExampleEcho2Channel *chan;
+ GError *error = NULL;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ TP_IFACE_CHANNEL_TYPE_TEXT))
+ {
+ return FALSE;
+ }
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT)
+ {
+ return FALSE;
+ }
+
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ fixed_properties, allowed_properties, &error))
+ {
+ goto error;
+ }
+
+ chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+
+ if (chan == NULL)
+ {
+ new_channel (self, handle, self->priv->conn->self_handle,
+ request_token);
+ }
+ else if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "An echo2 channel to contact #%u already exists", handle);
+ goto error;
+ }
+ else
+ {
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (chan));
+ }
+
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+example_echo_2_im_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_echo_2_im_manager_request (EXAMPLE_ECHO_2_IM_MANAGER (manager),
+ request_token, request_properties, TRUE);
+}
+
+static gboolean
+example_echo_2_im_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return example_echo_2_im_manager_request (EXAMPLE_ECHO_2_IM_MANAGER (manager),
+ request_token, request_properties, FALSE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer data G_GNUC_UNUSED)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = example_echo_2_im_manager_foreach_channel;
+ iface->type_foreach_channel_class = example_echo_2_im_manager_type_foreach_channel_class;
+ iface->create_channel = example_echo_2_im_manager_create_channel;
+ iface->ensure_channel = example_echo_2_im_manager_ensure_channel;
+ /* In this channel manager, Request has the same semantics as Ensure */
+ iface->request_channel = example_echo_2_im_manager_ensure_channel;
+}
diff --git a/qt4/tests/lib/glib/echo2/im-manager.h b/qt4/tests/lib/glib/echo2/im-manager.h
new file mode 100644
index 000000000..28cfef933
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/im-manager.h
@@ -0,0 +1,54 @@
+/*
+ * manager.h - header for an example channel manager
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_ECHO_MESSAGE_PARTS_IM_MANAGER_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_IM_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2ImManager ExampleEcho2ImManager;
+typedef struct _ExampleEcho2ImManagerClass ExampleEcho2ImManagerClass;
+typedef struct _ExampleEcho2ImManagerPrivate ExampleEcho2ImManagerPrivate;
+
+struct _ExampleEcho2ImManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _ExampleEcho2ImManager {
+ GObject parent;
+
+ ExampleEcho2ImManagerPrivate *priv;
+};
+
+GType example_echo_2_im_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_ECHO_2_IM_MANAGER \
+ (example_echo_2_im_manager_get_type ())
+#define EXAMPLE_ECHO_2_IM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_2_IM_MANAGER, \
+ ExampleEcho2ImManager))
+#define EXAMPLE_ECHO_2_IM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_2_IM_MANAGER, \
+ ExampleEcho2ImManagerClass))
+#define EXAMPLE_IS_ECHO_2_IM_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_2_IM_MANAGER))
+#define EXAMPLE_IS_ECHO_2_IM_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_2_IM_MANAGER))
+#define EXAMPLE_ECHO_2_IM_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_2_IM_MANAGER, \
+ ExampleEcho2ImManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/echo2/manager-file.py b/qt4/tests/lib/glib/echo2/manager-file.py
new file mode 100644
index 000000000..9d0dea0b3
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/manager-file.py
@@ -0,0 +1,19 @@
+# Input for tools/manager-file.py
+
+MANAGER = 'example_echo_2'
+PARAMS = {
+ 'example' : {
+ 'account': {
+ 'dtype': 's',
+ 'flags': 'required register',
+ 'filter': 'tp_cm_param_filter_string_nonempty',
+ # 'filter_data': 'NULL',
+ # 'default': ...,
+ # 'struct_field': '...',
+ # 'setter_data': 'NULL',
+ },
+ },
+ }
+STRUCTS = {
+ 'example': None
+ }
diff --git a/qt4/tests/lib/glib/echo2/protocol.c b/qt4/tests/lib/glib/echo2/protocol.c
new file mode 100644
index 000000000..4e160f654
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/protocol.c
@@ -0,0 +1,293 @@
+/*
+ * protocol.c - an example Protocol
+ *
+ * Copyright © 2007-2010 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "protocol.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "conn.h"
+#include "im-manager.h"
+
+#include <string.h>
+
+G_DEFINE_TYPE (ExampleEcho2Protocol,
+ example_echo_2_protocol,
+ TP_TYPE_BASE_PROTOCOL)
+
+const gchar * const protocol_interfaces[] = {
+ TP_IFACE_PROTOCOL_INTERFACE_AVATARS,
+ TP_IFACE_PROTOCOL_INTERFACE_PRESENCE,
+ NULL };
+
+const gchar * const supported_avatar_mime_types[] = {
+ "image/png",
+ "image/jpeg",
+ "image/gif",
+ NULL };
+
+struct _ExampleEcho2ProtocolPrivate
+{
+ TpPresenceStatusSpec *statuses;
+};
+
+static TpPresenceStatusSpec
+new_status_spec (const gchar *name,
+ TpConnectionPresenceType type,
+ gboolean settable,
+ gboolean can_have_message)
+{
+ TpPresenceStatusSpec ret;
+ TpPresenceStatusOptionalArgumentSpec *args = g_new0 (TpPresenceStatusOptionalArgumentSpec, 2);
+
+ memset (&ret, 0, sizeof (TpPresenceStatusSpec));
+ ret.name = g_strdup (name);
+ ret.presence_type = type;
+ ret.self = settable;
+ if (can_have_message)
+ {
+ args[0].name = g_strdup ("message");
+ args[0].dtype = g_strdup ("s");
+ }
+ ret.optional_arguments = args;
+
+ return ret;
+}
+
+static void
+example_echo_2_protocol_init (
+ ExampleEcho2Protocol *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_2_PROTOCOL,
+ ExampleEcho2ProtocolPrivate);
+
+ self->priv->statuses = g_new0 (TpPresenceStatusSpec, 4);
+ self->priv->statuses[0] = new_status_spec ("offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE,
+ FALSE, FALSE);
+ self->priv->statuses[1] = new_status_spec ("dnd", TP_CONNECTION_PRESENCE_TYPE_BUSY,
+ TRUE, FALSE);
+ self->priv->statuses[2] = new_status_spec ("available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE,
+ TRUE, TRUE);
+}
+
+static void
+example_echo_2_protocol_finalize (GObject *object)
+{
+ ExampleEcho2Protocol *self = EXAMPLE_ECHO_2_PROTOCOL (object);
+ TpPresenceStatusSpec *status = self->priv->statuses;
+
+ for (; status->name != NULL; status++) {
+ TpPresenceStatusOptionalArgumentSpec *arg =
+ (TpPresenceStatusOptionalArgumentSpec *) status->optional_arguments;
+
+ for (; arg->name != NULL; arg++) {
+ g_free ((gpointer) arg->name);
+ g_free ((gpointer) arg->dtype);
+ }
+
+ g_free ((gpointer) status->name);
+ g_free ((gpointer) status->optional_arguments);
+ }
+
+ ((GObjectClass *) example_echo_2_protocol_parent_class)->finalize (object);
+}
+
+static const TpCMParamSpec example_echo_2_example_params[] = {
+ { "account", "s", G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER,
+ NULL, /* no default */
+ 0, /* formerly struct offset, now unused */
+ tp_cm_param_filter_string_nonempty, /* filter - empty strings disallowed */
+ NULL, /* filter data, unused for our filter */
+ NULL /* setter data, now unused */ },
+ { NULL }
+};
+
+static const TpCMParamSpec *
+get_parameters (TpBaseProtocol *self)
+{
+ return example_echo_2_example_params;
+}
+
+static TpBaseConnection *
+new_connection (TpBaseProtocol *protocol,
+ GHashTable *asv,
+ GError **error)
+{
+ ExampleEcho2Connection *conn;
+ const gchar *account;
+
+ account = tp_asv_get_string (asv, "account");
+
+ if (account == NULL || account[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "The 'account' parameter is required");
+ return NULL;
+ }
+
+ conn = EXAMPLE_ECHO_2_CONNECTION (
+ g_object_new (EXAMPLE_TYPE_ECHO_2_CONNECTION,
+ "account", account,
+ "protocol", tp_base_protocol_get_name (protocol),
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+gchar *
+example_echo_2_protocol_normalize_contact (const gchar *id, GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must not be empty");
+ return NULL;
+ }
+
+ return g_utf8_strdown (id, -1);
+}
+
+static gchar *
+normalize_contact (TpBaseProtocol *self G_GNUC_UNUSED,
+ const gchar *contact,
+ GError **error)
+{
+ return example_echo_2_protocol_normalize_contact (contact, error);
+}
+
+static gchar *
+identify_account (TpBaseProtocol *self G_GNUC_UNUSED,
+ GHashTable *asv,
+ GError **error)
+{
+ const gchar *account = tp_asv_get_string (asv, "account");
+
+ if (account != NULL)
+ return g_strdup (account);
+
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "'account' parameter not given");
+ return NULL;
+}
+
+static GStrv
+get_interfaces (TpBaseProtocol *self)
+{
+ return g_strdupv ((GStrv) protocol_interfaces);
+}
+
+static void
+get_connection_details (TpBaseProtocol *self G_GNUC_UNUSED,
+ GStrv *connection_interfaces,
+ GType **channel_managers,
+ gchar **icon_name,
+ gchar **english_name,
+ gchar **vcard_field)
+{
+ if (connection_interfaces != NULL)
+ {
+ *connection_interfaces = g_strdupv (
+ (GStrv) example_echo_2_connection_get_possible_interfaces ());
+ }
+
+ if (channel_managers != NULL)
+ {
+ GType types[] = { EXAMPLE_TYPE_ECHO_2_IM_MANAGER, G_TYPE_INVALID };
+
+ *channel_managers = g_memdup (types, sizeof (types));
+ }
+
+ if (icon_name != NULL)
+ {
+ /* a real protocol would use its own icon name - for this example we
+ * borrow the one from ICQ */
+ *icon_name = g_strdup ("im-icq");
+ }
+
+ if (english_name != NULL)
+ {
+ /* in a real protocol this would be "ICQ" or
+ * "Windows Live Messenger (MSN)" or something */
+ *english_name = g_strdup ("Echo II example");
+ }
+
+ if (vcard_field != NULL)
+ {
+ /* in a real protocol this would be "tel" or "x-jabber" or something */
+ *vcard_field = g_strdup ("x-telepathy-example");
+ }
+}
+
+static void
+get_avatar_details (TpBaseProtocol *self,
+ GStrv *supported_mime_types,
+ guint *min_height,
+ guint *min_width,
+ guint *recommended_height,
+ guint *recommended_width,
+ guint *max_height,
+ guint *max_width,
+ guint *max_bytes)
+{
+ if (supported_mime_types != NULL)
+ *supported_mime_types = g_strdupv ((GStrv) supported_avatar_mime_types);
+
+ if (min_height != NULL)
+ *min_height = 32;
+
+ if (min_width != NULL)
+ *min_width = 32;
+
+ if (recommended_height != NULL)
+ *recommended_height = 64;
+
+ if (recommended_width != NULL)
+ *recommended_width = 64;
+
+ if (max_height != NULL)
+ *max_height = 96;
+
+ if (max_width != NULL)
+ *max_width = 96;
+
+ if (max_bytes != NULL)
+ *max_bytes = 37748736;
+}
+
+static const TpPresenceStatusSpec *
+get_statuses (TpBaseProtocol *object)
+{
+ ExampleEcho2Protocol *self = EXAMPLE_ECHO_2_PROTOCOL (object);
+
+ return self->priv->statuses;
+}
+
+static void
+example_echo_2_protocol_class_init (
+ ExampleEcho2ProtocolClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ TpBaseProtocolClass *base_class =
+ (TpBaseProtocolClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (ExampleEcho2ProtocolPrivate));
+
+ object_class->finalize = example_echo_2_protocol_finalize;
+
+ base_class->get_parameters = get_parameters;
+ base_class->new_connection = new_connection;
+
+ base_class->normalize_contact = normalize_contact;
+ base_class->identify_account = identify_account;
+ base_class->get_interfaces = get_interfaces;
+ base_class->get_connection_details = get_connection_details;
+ base_class->get_avatar_details = get_avatar_details;
+ base_class->get_statuses = get_statuses;
+}
diff --git a/qt4/tests/lib/glib/echo2/protocol.h b/qt4/tests/lib/glib/echo2/protocol.h
new file mode 100644
index 000000000..ce0377e0f
--- /dev/null
+++ b/qt4/tests/lib/glib/echo2/protocol.h
@@ -0,0 +1,67 @@
+/*
+ * protocol.h - header for an example Protocol
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef EXAMPLE_ECHO_MESSAGE_PARTS_PROTOCOL_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_PROTOCOL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-protocol.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2Protocol
+ ExampleEcho2Protocol;
+typedef struct _ExampleEcho2ProtocolPrivate
+ ExampleEcho2ProtocolPrivate;
+typedef struct _ExampleEcho2ProtocolClass
+ ExampleEcho2ProtocolClass;
+typedef struct _ExampleEcho2ProtocolClassPrivate
+ ExampleEcho2ProtocolClassPrivate;
+
+struct _ExampleEcho2ProtocolClass {
+ TpBaseProtocolClass parent_class;
+
+ ExampleEcho2ProtocolClassPrivate *priv;
+};
+
+struct _ExampleEcho2Protocol {
+ TpBaseProtocol parent;
+
+ ExampleEcho2ProtocolPrivate *priv;
+};
+
+GType example_echo_2_protocol_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_PROTOCOL \
+ (example_echo_2_protocol_get_type ())
+#define EXAMPLE_ECHO_2_PROTOCOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ EXAMPLE_TYPE_ECHO_2_PROTOCOL, \
+ ExampleEcho2Protocol))
+#define EXAMPLE_ECHO_2_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ EXAMPLE_TYPE_ECHO_2_PROTOCOL, \
+ ExampleEcho2ProtocolClass))
+#define EXAMPLE_IS_ECHO_2_PROTOCOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ EXAMPLE_TYPE_ECHO_2_PROTOCOL))
+#define EXAMPLE_IS_ECHO_2_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ EXAMPLE_TYPE_ECHO_2_PROTOCOL))
+#define EXAMPLE_ECHO_2_PROTOCOL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ EXAMPLE_TYPE_ECHO_2_PROTOCOL, \
+ ExampleEcho2ProtocolClass))
+
+gchar *example_echo_2_protocol_normalize_contact (const gchar *id,
+ GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/future/CMakeLists.txt b/qt4/tests/lib/glib/future/CMakeLists.txt
new file mode 100644
index 000000000..d995576dd
--- /dev/null
+++ b/qt4/tests/lib/glib/future/CMakeLists.txt
@@ -0,0 +1,4 @@
+if(ENABLE_TP_GLIB_TESTS)
+ add_subdirectory(extensions)
+ add_subdirectory(conference)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/future/conference/CMakeLists.txt b/qt4/tests/lib/glib/future/conference/CMakeLists.txt
new file mode 100644
index 000000000..2e66ed423
--- /dev/null
+++ b/qt4/tests/lib/glib/future/conference/CMakeLists.txt
@@ -0,0 +1,12 @@
+if(ENABLE_TP_GLIB_TESTS)
+ include_directories(
+ ${CMAKE_SOURCE_DIR}/tests/lib/glib/future)
+
+ set(future_example_cm_conference_SRCS
+ chan.c
+ chan.h)
+
+ add_library(future-example-cm-conference STATIC ${future_example_cm_conference_SRCS})
+ target_link_libraries(future-example-cm-conference ${TPGLIB_LIBRARIES}
+ tp-glib-tests-future-extensions)
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/future/conference/chan.c b/qt4/tests/lib/glib/future/conference/chan.c
new file mode 100644
index 000000000..c3e6d4fd8
--- /dev/null
+++ b/qt4/tests/lib/glib/future/conference/chan.c
@@ -0,0 +1,665 @@
+/*
+ * conference-channel.c - an tp_tests conference channel
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2010 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "chan.h"
+
+#include <string.h>
+
+#include <gobject/gvaluecollector.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-properties-interface.h>
+
+#include "extensions/extensions.h"
+
+/* TODO:
+ * Simulate Conference.ChannelRemoved
+ */
+
+static void mergeable_conference_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsConferenceChannel,
+ tp_tests_conference_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CONFERENCE,
+ NULL);
+ G_IMPLEMENT_INTERFACE (FUTURE_TYPE_SVC_CHANNEL_INTERFACE_MERGEABLE_CONFERENCE,
+ mergeable_conference_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL))
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_CONFERENCE_CHANNELS,
+ PROP_CONFERENCE_INITIAL_CHANNELS,
+ PROP_CONFERENCE_INITIAL_INVITEE_HANDLES,
+ PROP_CONFERENCE_INITIAL_INVITEE_IDS,
+ PROP_CONFERENCE_INVITATION_MESSAGE,
+ PROP_CONFERENCE_ORIGINAL_CHANNELS,
+ N_PROPS
+};
+
+struct _TpTestsConferenceChannelPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ guint handle_type;
+
+ GPtrArray *conference_initial_channels;
+ GPtrArray *conference_channels;
+ GArray *conference_initial_invitee_handles;
+ gchar **conference_initial_invitee_ids;
+ gchar *conference_invitation_message;
+ GHashTable *conference_original_channels;
+
+ gboolean disposed;
+ gboolean closed;
+};
+
+static const gchar * tp_tests_conference_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE,
+ FUTURE_IFACE_CHANNEL_INTERFACE_MERGEABLE_CONFERENCE,
+ NULL
+};
+
+static void
+tp_tests_conference_channel_init (TpTestsConferenceChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_CONFERENCE_CHANNEL,
+ TpTestsConferenceChannelPrivate);
+
+ self->priv->handle_type = (guint) -1;
+}
+
+static void
+constructed (GObject *object)
+{
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) tp_tests_conference_channel_parent_class)->constructed;
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+ TpDBusDaemon *bus;
+
+ if (chain_up != NULL)
+ {
+ chain_up (object);
+ }
+
+ bus = tp_dbus_daemon_dup (NULL);
+ tp_dbus_daemon_register_object (bus, self->priv->object_path, object);
+
+ tp_group_mixin_init (object,
+ G_STRUCT_OFFSET (TpTestsConferenceChannel, group),
+ contact_repo, self->priv->conn->self_handle);
+
+ if (self->priv->handle_type == (guint) -1)
+ {
+ self->priv->handle_type = TP_HANDLE_TYPE_NONE;
+ }
+ if (!self->priv->conference_channels)
+ {
+ self->priv->conference_channels = g_ptr_array_new ();
+ }
+ if (!self->priv->conference_initial_channels)
+ {
+ self->priv->conference_initial_channels = g_ptr_array_new ();
+ }
+ if (!self->priv->conference_initial_invitee_handles)
+ {
+ self->priv->conference_initial_invitee_handles =
+ g_array_new (FALSE, TRUE, sizeof (guint));
+ }
+ if (!self->priv->conference_initial_invitee_ids)
+ {
+ self->priv->conference_initial_invitee_ids = g_new0 (gchar *, 1);
+ }
+ if (!self->priv->conference_invitation_message)
+ {
+ self->priv->conference_invitation_message = g_strdup ("");
+ }
+ if (!self->priv->conference_original_channels)
+ {
+ self->priv->conference_original_channels = dbus_g_type_specialized_construct (
+ TP_HASH_TYPE_CHANNEL_ORIGINATOR_MAP);
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, self->priv->handle_type);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+
+ case PROP_TARGET_ID:
+ g_value_set_string (value, "");
+ break;
+
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, TRUE);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+
+ case PROP_INITIATOR_ID:
+ g_value_set_string (value, "");
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, tp_tests_conference_channel_interfaces);
+ break;
+
+ case PROP_CHANNEL_DESTROYED:
+ g_value_set_boolean (value, self->priv->closed);
+ break;
+
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, "Channels",
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, "InitialChannels",
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, "InitialInviteeHandles",
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, "InitialInviteeIDs",
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, "InvitationMessage",
+ TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, "OriginalChannels",
+ NULL));
+ break;
+
+ case PROP_CONFERENCE_CHANNELS:
+ g_value_set_boxed (value, self->priv->conference_channels);
+ g_assert (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST));
+ break;
+
+ case PROP_CONFERENCE_INITIAL_CHANNELS:
+ g_value_set_boxed (value, self->priv->conference_initial_channels);
+ g_assert (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST));
+ break;
+
+ case PROP_CONFERENCE_INITIAL_INVITEE_HANDLES:
+ g_value_set_boxed (value, self->priv->conference_initial_invitee_handles);
+ g_assert (G_VALUE_HOLDS (value, DBUS_TYPE_G_UINT_ARRAY));
+ break;
+
+ case PROP_CONFERENCE_INITIAL_INVITEE_IDS:
+ g_value_set_boxed (value, self->priv->conference_initial_invitee_ids);
+ g_assert (G_VALUE_HOLDS (value, G_TYPE_STRV));
+ break;
+
+ case PROP_CONFERENCE_INVITATION_MESSAGE:
+ g_value_set_string (value, self->priv->conference_invitation_message);
+ g_assert (G_VALUE_HOLDS (value, G_TYPE_STRING));
+ break;
+
+ case PROP_CONFERENCE_ORIGINAL_CHANNELS:
+ g_value_set_boxed (value, self->priv->conference_original_channels);
+ g_assert (G_VALUE_HOLDS (value, TP_HASH_TYPE_CHANNEL_ORIGINATOR_MAP));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ self->priv->handle_type = g_value_get_uint (value);
+ break;
+
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_CONFERENCE_INITIAL_CHANNELS:
+ g_ptr_array_free(self->priv->conference_initial_channels, TRUE);
+ self->priv->conference_initial_channels = g_value_dup_boxed (value);
+
+ g_ptr_array_free(self->priv->conference_channels, TRUE);
+ self->priv->conference_channels = g_value_dup_boxed (value);
+ break;
+
+ case PROP_CONFERENCE_INITIAL_INVITEE_HANDLES:
+ self->priv->conference_initial_invitee_handles = g_value_dup_boxed (value);
+ break;
+
+ case PROP_CONFERENCE_INITIAL_INVITEE_IDS:
+ self->priv->conference_initial_invitee_ids = g_value_dup_boxed (value);
+ break;
+
+ case PROP_CONFERENCE_INVITATION_MESSAGE:
+ self->priv->conference_invitation_message = g_value_dup_string (value);
+ break;
+
+ case PROP_CHANNEL_TYPE:
+ case PROP_HANDLE:
+ case PROP_TARGET_ID:
+ case PROP_REQUESTED:
+ case PROP_INITIATOR_HANDLE:
+ case PROP_INITIATOR_ID:
+ case PROP_CONFERENCE_CHANNELS:
+ /* these properties are not actually meaningfully changeable on this
+ * channel, so we do nothing */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (object);
+
+ if (self->priv->disposed)
+ {
+ return;
+ }
+
+ self->priv->disposed = TRUE;
+
+ g_ptr_array_free (self->priv->conference_channels, TRUE);
+ self->priv->conference_channels = NULL;
+ g_ptr_array_free (self->priv->conference_initial_channels, TRUE);
+ self->priv->conference_initial_channels = NULL;
+ if (self->priv->conference_initial_invitee_handles)
+ {
+ g_array_free (self->priv->conference_initial_invitee_handles, FALSE);
+ }
+ self->priv->conference_initial_invitee_handles = NULL;
+ g_strfreev (self->priv->conference_initial_invitee_ids);
+ self->priv->conference_initial_invitee_ids = NULL;
+ g_free (self->priv->conference_invitation_message);
+ self->priv->conference_invitation_message = NULL;
+ g_boxed_free (TP_HASH_TYPE_CHANNEL_ORIGINATOR_MAP, self->priv->conference_original_channels);
+ self->priv->conference_original_channels = NULL;
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) tp_tests_conference_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (object);
+
+ g_free (self->priv->object_path);
+
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) tp_tests_conference_channel_parent_class)->finalize (object);
+}
+
+static gboolean
+add_member (GObject *obj,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (obj);
+ TpIntSet *add = tp_intset_new ();
+
+ tp_intset_add (add, handle);
+ tp_group_mixin_change_members (obj, message, add, NULL, NULL, NULL,
+ self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (add);
+
+ return TRUE;
+}
+
+static void
+tp_tests_conference_channel_class_init (TpTestsConferenceChannelClass *klass)
+{
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl conference_props[] = {
+ { "Channels", "channels", NULL },
+ { "InitialChannels", "initial-channels", NULL },
+ { "InitialInviteeHandles", "initial-invitee-handles", NULL },
+ { "InitialInviteeIDs", "initial-invitee-ids", NULL },
+ { "InvitationMessage", "invitation-message", NULL },
+ { "OriginalChannels", "original-channels", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { TP_IFACE_CHANNEL_INTERFACE_CONFERENCE,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ conference_props,
+ },
+ { NULL }
+ };
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass,
+ sizeof (TpTestsConferenceChannelPrivate));
+
+ object_class->constructed = constructed;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE,
+ "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_boxed ("channels", "Channel paths",
+ "A list of the object paths of channels",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_CHANNELS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("initial-channels", "Initial Channel paths",
+ "A list of the object paths of initial channels",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_INITIAL_CHANNELS,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("initial-invitee-handles", "Initial Invitee Handles",
+ "A list of additional contacts invited to this conference when it was created",
+ DBUS_TYPE_G_UINT_ARRAY,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_INITIAL_INVITEE_HANDLES, param_spec);
+
+ param_spec = g_param_spec_boxed ("initial-invitee-ids", "Initial Invitee IDs",
+ "A list of additional contacts invited to this conference when it was created",
+ G_TYPE_STRV,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_INITIAL_INVITEE_IDS, param_spec);
+
+ param_spec = g_param_spec_string ("invitation-message", "Invitation message",
+ "The message that was sent to the InitialInviteeHandles when they were invited",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_INVITATION_MESSAGE,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("original-channels",
+ "Original Channels",
+ "A map of channel specific handles to channels",
+ TP_HASH_TYPE_CHANNEL_ORIGINATOR_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONFERENCE_ORIGINAL_CHANNELS,
+ param_spec);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsConferenceChannelClass,
+ dbus_properties_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsConferenceChannelClass, group_class),
+ add_member,
+ NULL);
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (iface);
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (iface);
+
+ tp_svc_channel_return_from_get_handle (context, self->priv->handle_type, 0);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ tp_tests_conference_channel_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+mergeable_conference_merge (FutureSvcChannelInterfaceMergeableConference *iface G_GNUC_UNUSED,
+ const gchar *channel,
+ DBusGMethodInvocation *context)
+{
+ TpTestsConferenceChannel *self = TP_TESTS_CONFERENCE_CHANNEL (iface);
+ GHashTable *immutable_props = g_hash_table_new (NULL, NULL);
+
+ g_ptr_array_add (self->priv->conference_channels, g_strdup (channel));
+
+ tp_svc_channel_interface_conference_emit_channel_merged (self, channel, 0, immutable_props);
+
+ g_hash_table_destroy (immutable_props);
+
+ future_svc_channel_interface_mergeable_conference_return_from_merge (context);
+}
+
+static void
+mergeable_conference_iface_init (gpointer iface,
+ gpointer data)
+{
+ FutureSvcChannelInterfaceMergeableConferenceClass *klass = iface;
+
+#define IMPLEMENT(x) future_svc_channel_interface_mergeable_conference_implement_##x (klass, mergeable_conference_##x)
+ IMPLEMENT (merge);
+#undef IMPLEMENT
+}
+
+void tp_tests_conference_channel_remove_channel (TpTestsConferenceChannel *self,
+ const gchar *channel)
+{
+ guint i;
+
+ for (i = 0; i < self->priv->conference_channels->len; i++)
+ {
+ gchar *path = g_ptr_array_index (self->priv->conference_channels, i);
+
+ if (strcmp (path, channel) == 0)
+ {
+ GHashTable *details = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ g_ptr_array_remove (self->priv->conference_channels, (gpointer) path);
+ g_free (path);
+
+ g_hash_table_insert (details, "actor",
+ tp_g_value_slice_new_uint (self->priv->conn->self_handle));
+ g_hash_table_insert (details, "domain-specific-detail-uint", tp_g_value_slice_new_uint (3));
+
+ tp_svc_channel_interface_conference_emit_channel_removed (self, channel, details);
+
+ g_hash_table_destroy (details);
+ }
+ }
+}
diff --git a/qt4/tests/lib/glib/future/conference/chan.h b/qt4/tests/lib/glib/future/conference/chan.h
new file mode 100644
index 000000000..04248e88b
--- /dev/null
+++ b/qt4/tests/lib/glib/future/conference/chan.h
@@ -0,0 +1,80 @@
+/*
+ * conference-channel.h - header for an tp_tests conference channel
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2010 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TP_TESTS_CONFERENCE_CHANNEL_H
+#define TP_TESTS_CONFERENCE_CHANNEL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsConferenceChannel TpTestsConferenceChannel;
+typedef struct _TpTestsConferenceChannelPrivate
+ TpTestsConferenceChannelPrivate;
+
+typedef struct _TpTestsConferenceChannelClass
+ TpTestsConferenceChannelClass;
+typedef struct _TpTestsConferenceChannelClassPrivate
+ TpTestsConferenceChannelClassPrivate;
+
+GType tp_tests_conference_channel_get_type (void);
+
+#define TP_TESTS_TYPE_CONFERENCE_CHANNEL \
+ (tp_tests_conference_channel_get_type ())
+#define TP_TESTS_CONFERENCE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_CONFERENCE_CHANNEL, \
+ TpTestsConferenceChannel))
+#define TP_TESTS_CONFERENCE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_CONFERENCE_CHANNEL, \
+ TpTestsConferenceChannelClass))
+#define TP_TESTS_IS_CONFERENCE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_CONFERENCE_CHANNEL))
+#define TP_TESTS_IS_CONFERENCE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_CONFERENCE_CHANNEL))
+#define TP_TESTS_CONFERENCE_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONFERENCE_CHANNEL, \
+ TpTestsConferenceChannelClass))
+
+struct _TpTestsConferenceChannelClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_properties_class;
+ TpGroupMixinClass group_class;
+
+ TpTestsConferenceChannelClassPrivate *priv;
+};
+
+struct _TpTestsConferenceChannel {
+ GObject parent;
+
+ TpGroupMixin group;
+
+ TpTestsConferenceChannelPrivate *priv;
+};
+
+void tp_tests_conference_channel_remove_channel (TpTestsConferenceChannel *self,
+ const gchar *channel);
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/future/extensions/CMakeLists.txt b/qt4/tests/lib/glib/future/extensions/CMakeLists.txt
new file mode 100644
index 000000000..a7f07534a
--- /dev/null
+++ b/qt4/tests/lib/glib/future/extensions/CMakeLists.txt
@@ -0,0 +1,118 @@
+if(ENABLE_TP_GLIB_TESTS)
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen")
+
+ set(tp_glib_tests_future_extensions_SRCS
+ extensions.c
+ extensions.h)
+
+ set(gen_all_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml)
+ add_custom_command(OUTPUT ${gen_all_xml}
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/xincludator.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/all.xml
+ > ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/all.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/channel.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/misc.xml
+ ${CMAKE_SOURCE_DIR}/tools/xincludator.py)
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/gtypes.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/gtypes-body.h
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/glib-gtypes-generator.py
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml
+ _gen/gtypes Future
+ DEPENDS ${gen_all_xml}
+ ${CMAKE_SOURCE_DIR}/tools/glib-gtypes-generator.py)
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.list
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/glib-signals-marshal-gen.py
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml
+ > _gen/signals-marshal.list
+ DEPENDS ${gen_all_xml})
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.h
+ COMMAND ${GLIB_GENMARSHAL} --header --prefix=_future_ext_marshal
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.list
+ > _gen/signals-marshal.h
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.list)
+
+ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gen-signals-marshal-c.sh "
+ echo '#include \"_gen/signals-marshal.h\"' > _gen/signals-marshal.c
+ ${GLIB_GENMARSHAL} --body --prefix=_future_ext_marshal ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.list >> _gen/signals-marshal.c
+ ")
+ set(gen_signals_marshal_c ${SH} ${CMAKE_CURRENT_BINARY_DIR}/gen-signals-marshal-c.sh)
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.c
+ COMMAND ${gen_signals_marshal_c}
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.list)
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/enums.h
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/c-constants-gen.py
+ Future ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml
+ > _gen/enums.h
+ DEPENDS ${gen_all_xml}
+ ${CMAKE_SOURCE_DIR}/tools/c-constants-gen.py)
+
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_gen/interfaces-body.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/interfaces.h
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/glib-interfaces-gen.py
+ Future ${CMAKE_CURRENT_BINARY_DIR}/_gen/interfaces-body.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/interfaces.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/all.xml
+ DEPENDS ${gen_all_xml}
+ ${CMAKE_SOURCE_DIR}/tools/glib-interfaces-gen.py)
+
+ set(SPECS
+ channel
+ misc)
+ foreach(spec ${SPECS})
+ add_custom_command(OUTPUT "_gen/${spec}.xml"
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${CMAKE_SOURCE_DIR}/tools/xincludator.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/${spec}.xml
+ > ${CMAKE_CURRENT_BINARY_DIR}/_gen/${spec}.xml
+ DEPENDS ${spec}.xml
+ ${CMAKE_SOURCE_DIR}/tools/xincludator.py)
+ endforeach(spec ${SPECS})
+
+ function(SVC_GENERATOR spec)
+ set(ARGS
+ ${CMAKE_SOURCE_DIR}/tools/glib-ginterface-gen.py
+ --filename=${CMAKE_CURRENT_BINARY_DIR}/_gen/svc-${spec}
+ --signal-marshal-prefix=_future_ext
+ --include='<telepathy-glib/dbus.h>'
+ --include='_gen/signals-marshal.h'
+ --not-implemented-func='tp_dbus_g_method_return_not_implemented'
+ --allow-unstable
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/${spec}.xml Future_Svc_)
+ add_custom_command(OUTPUT _gen/svc-${spec}.c _gen/svc-${spec}.h
+ COMMAND ${PYTHON_EXECUTABLE}
+ ARGS ${ARGS}
+ DEPENDS ${CMAKE_SOURCE_DIR}/tools/glib-ginterface-gen.py
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/${spec}.xml
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+ endfunction(SVC_GENERATOR spec)
+
+ svc_generator(channel)
+ svc_generator(misc)
+
+ set(NEW_FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/enums.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/gtypes.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/gtypes-body.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/interfaces.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/interfaces-body.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.c
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/signals-marshal.list
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/svc-channel.c
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/svc-channel.h
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/svc-misc.c
+ ${CMAKE_CURRENT_BINARY_DIR}/_gen/svc-misc.h)
+ list(APPEND tp_glib_tests_future_extensions_SRCS ${NEW_FILES})
+ set_source_files_properties(${NEW_FILES} PROPERTIES GENERATED true)
+
+ add_library(tp-glib-tests-future-extensions STATIC ${tp_glib_tests_future_extensions_SRCS})
+ target_link_libraries(tp-glib-tests-future-extensions ${TPGLIB_LIBRARIES})
+endif(ENABLE_TP_GLIB_TESTS)
diff --git a/qt4/tests/lib/glib/future/extensions/all.xml b/qt4/tests/lib/glib/future/extensions/all.xml
new file mode 100644
index 000000000..53cedc54a
--- /dev/null
+++ b/qt4/tests/lib/glib/future/extensions/all.xml
@@ -0,0 +1,11 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Extensions from the future</tp:title>
+
+<xi:include href="channel.xml"/>
+<xi:include href="misc.xml"/>
+<xi:include href="../../../../../spec/generic-types.xml"/>
+
+</tp:spec>
diff --git a/qt4/tests/lib/glib/future/extensions/channel.xml b/qt4/tests/lib/glib/future/extensions/channel.xml
new file mode 100644
index 000000000..71ec39e8b
--- /dev/null
+++ b/qt4/tests/lib/glib/future/extensions/channel.xml
@@ -0,0 +1,12 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Channel extensions from the future</tp:title>
+
+<xi:include href="../../../../../spec/Channel_Interface_Mergeable_Conference.xml"/>
+<xi:include href="../../../../../spec/Channel_Interface_Splittable.xml"/>
+
+<xi:include href="../../../../../spec/Channel_Type_Call.xml"/>
+
+</tp:spec>
diff --git a/qt4/tests/lib/glib/future/extensions/extensions.c b/qt4/tests/lib/glib/future/extensions/extensions.c
new file mode 100644
index 000000000..eeda4623c
--- /dev/null
+++ b/qt4/tests/lib/glib/future/extensions/extensions.c
@@ -0,0 +1,6 @@
+#include "extensions.h"
+
+/* include auto-generated stubs for things common to service and client */
+#include "_gen/gtypes-body.h"
+#include "_gen/interfaces-body.h"
+#include "_gen/signals-marshal.h"
diff --git a/qt4/tests/lib/glib/future/extensions/extensions.h b/qt4/tests/lib/glib/future/extensions/extensions.h
new file mode 100644
index 000000000..4f3bcfc11
--- /dev/null
+++ b/qt4/tests/lib/glib/future/extensions/extensions.h
@@ -0,0 +1,18 @@
+#ifndef FUTURE_EXTENSIONS_H
+#define FUTURE_EXTENSIONS_H
+
+#include <glib-object.h>
+#include <telepathy-glib/channel.h>
+
+#include "tests/lib/glib/future/extensions/_gen/enums.h"
+#include "tests/lib/glib/future/extensions/_gen/svc-channel.h"
+#include "tests/lib/glib/future/extensions/_gen/svc-misc.h"
+
+G_BEGIN_DECLS
+
+#include "tests/lib/glib/future/extensions/_gen/gtypes.h"
+#include "tests/lib/glib/future/extensions/_gen/interfaces.h"
+
+G_END_DECLS
+
+#endif
diff --git a/qt4/tests/lib/glib/future/extensions/misc.xml b/qt4/tests/lib/glib/future/extensions/misc.xml
new file mode 100644
index 000000000..061b4d405
--- /dev/null
+++ b/qt4/tests/lib/glib/future/extensions/misc.xml
@@ -0,0 +1,14 @@
+<tp:spec
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<tp:title>Miscellaneous extensions from the future</tp:title>
+
+<xi:include href="../../../../../spec/Call_Content.xml"/>
+<xi:include href="../../../../../spec/Call_Content_Codec_Offer.xml"/>
+<xi:include href="../../../../../spec/Call_Content_Interface_Media.xml"/>
+<xi:include href="../../../../../spec/Call_Stream_Endpoint.xml"/>
+<xi:include href="../../../../../spec/Call_Stream_Interface_Media.xml"/>
+<xi:include href="../../../../../spec/Call_Stream.xml"/>
+
+</tp:spec>
diff --git a/qt4/tests/lib/glib/params-cm.c b/qt4/tests/lib/glib/params-cm.c
new file mode 100644
index 000000000..a0b1820cf
--- /dev/null
+++ b/qt4/tests/lib/glib/params-cm.c
@@ -0,0 +1,208 @@
+/*
+ * params-cm.h - source for TpTestsParamConnectionManager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "params-cm.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+G_DEFINE_TYPE (TpTestsParamConnectionManager,
+ tp_tests_param_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+struct _TpTestsParamConnectionManagerPrivate
+{
+ int dummy;
+};
+
+static void
+tp_tests_param_connection_manager_init (
+ TpTestsParamConnectionManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER,
+ TpTestsParamConnectionManagerPrivate);
+}
+
+enum {
+ TP_TESTS_PARAM_STRING,
+ TP_TESTS_PARAM_INT16,
+ TP_TESTS_PARAM_INT32,
+ TP_TESTS_PARAM_UINT16,
+ TP_TESTS_PARAM_UINT32,
+ TP_TESTS_PARAM_INT64,
+ TP_TESTS_PARAM_UINT64,
+ TP_TESTS_PARAM_BOOLEAN,
+ TP_TESTS_PARAM_DOUBLE,
+ TP_TESTS_PARAM_ARRAY_STRINGS,
+ TP_TESTS_PARAM_ARRAY_BYTES,
+ TP_TESTS_PARAM_OBJECT_PATH,
+ TP_TESTS_PARAM_LC_STRING,
+ TP_TESTS_PARAM_UC_STRING,
+ NUM_PARAM
+};
+
+static gboolean
+filter_string_ascii_case (const TpCMParamSpec *param_spec,
+ GValue *value,
+ GError **error)
+{
+ const gchar *s = g_value_get_string (value);
+ guint i;
+
+ for (i = 0; s[i] != '\0'; i++)
+ {
+ int c = s[i]; /* just to avoid -Wtype-limits */
+
+ if (c < 0 || c > 127) /* char might be signed or unsigned */
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "%s must be ASCII", param_spec->name);
+ return FALSE;
+ }
+ }
+
+ if (GINT_TO_POINTER (param_spec->filter_data))
+ g_value_take_string (value, g_ascii_strup (s, -1));
+ else
+ g_value_take_string (value, g_ascii_strdown (s, -1));
+
+ return TRUE;
+}
+
+static TpCMParamSpec param_example_params[] = {
+ { "a-string", "s", G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "the default string",
+ G_STRUCT_OFFSET (TpTestsCMParams, a_string), NULL, NULL, NULL },
+ { "a-int16", "n", G_TYPE_INT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER (42),
+ G_STRUCT_OFFSET (TpTestsCMParams, a_int16), NULL, NULL, NULL },
+ { "a-int32", "i", G_TYPE_INT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER (42),
+ G_STRUCT_OFFSET (TpTestsCMParams, a_int32), NULL, NULL, NULL },
+ { "a-uint16", "q", G_TYPE_UINT, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_uint16), NULL, NULL, NULL },
+ { "a-uint32", "u", G_TYPE_UINT, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_uint32), NULL, NULL, NULL },
+ { "a-int64", "x", G_TYPE_INT64, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_int64), NULL, NULL, NULL },
+ { "a-uint64", "t", G_TYPE_UINT64, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_uint64), NULL, NULL, NULL },
+ { "a-boolean", "b", G_TYPE_BOOLEAN, TP_CONN_MGR_PARAM_FLAG_REQUIRED, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_boolean), NULL, NULL, NULL },
+ { "a-double", "d", G_TYPE_DOUBLE, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_double), NULL, NULL, NULL },
+ { "a-array-of-strings", "as", 0, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_array_of_strings), NULL, NULL, NULL },
+ { "a-array-of-bytes", "ay", 0, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_array_of_bytes), NULL, NULL, NULL },
+ { "a-object-path", "o", 0, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, a_object_path), NULL, NULL, NULL },
+
+ /* demo of a filter */
+ { "lc-string", "s", G_TYPE_STRING, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, lc_string),
+ filter_string_ascii_case, GINT_TO_POINTER (FALSE), NULL },
+ { "uc-string", "s", G_TYPE_STRING, 0, NULL,
+ G_STRUCT_OFFSET (TpTestsCMParams, uc_string),
+ filter_string_ascii_case, GINT_TO_POINTER (TRUE), NULL },
+ { NULL }
+};
+
+static TpTestsCMParams *params = NULL;
+
+static gpointer
+alloc_params (void)
+{
+ params = g_slice_new0 (TpTestsCMParams);
+
+ return params;
+}
+
+static void
+free_params (gpointer p)
+{
+ /* CM user is responsible to free params so he can check their values */
+ params = (TpTestsCMParams *) p;
+ params->would_have_been_freed = TRUE;
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+ { "example", param_example_params,
+ alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "No connection for you");
+ return NULL;
+}
+
+static void
+tp_tests_param_connection_manager_class_init (
+ TpTestsParamConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ g_type_class_add_private (klass,
+ sizeof (TpTestsParamConnectionManagerPrivate));
+
+ param_example_params[TP_TESTS_PARAM_ARRAY_STRINGS].gtype = G_TYPE_STRV;
+ param_example_params[TP_TESTS_PARAM_ARRAY_BYTES].gtype =
+ DBUS_TYPE_G_UCHAR_ARRAY;
+ param_example_params[TP_TESTS_PARAM_OBJECT_PATH].gtype =
+ DBUS_TYPE_G_OBJECT_PATH;
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "params_cm";
+ base_class->protocol_params = example_protocols;
+}
+
+TpTestsCMParams *
+tp_tests_param_connection_manager_steal_params_last_conn (void)
+{
+ TpTestsCMParams *p = params;
+
+ params = NULL;
+ return p;
+}
+
+void
+tp_tests_param_connection_manager_free_params (TpTestsCMParams *p)
+{
+ g_free (p->a_string);
+ g_strfreev (p->a_array_of_strings);
+ if (p->a_array_of_bytes != NULL)
+ g_array_free (p->a_array_of_bytes, TRUE);
+ g_free (p->a_object_path);
+
+ g_slice_free (TpTestsCMParams, p);
+}
diff --git a/qt4/tests/lib/glib/params-cm.h b/qt4/tests/lib/glib/params-cm.h
new file mode 100644
index 000000000..c54a2c975
--- /dev/null
+++ b/qt4/tests/lib/glib/params-cm.h
@@ -0,0 +1,95 @@
+/*
+ * params-cm.h - header for TpTestsParamConnectionManager
+ *
+ * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2007-2009 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TP_TESTS_PARAM_CONNECTION_MANAGER_H__
+#define __TP_TESTS_PARAM_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsParamConnectionManager
+ TpTestsParamConnectionManager;
+typedef struct _TpTestsParamConnectionManagerPrivate
+ TpTestsParamConnectionManagerPrivate;
+
+typedef struct _TpTestsParamConnectionManagerClass
+ TpTestsParamConnectionManagerClass;
+typedef struct _TpTestsParamConnectionManagerClassPrivate
+ TpTestsParamConnectionManagerClassPrivate;
+
+struct _TpTestsParamConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ TpTestsParamConnectionManagerClassPrivate *priv;
+};
+
+struct _TpTestsParamConnectionManager {
+ TpBaseConnectionManager parent;
+
+ TpTestsParamConnectionManagerPrivate *priv;
+};
+
+GType tp_tests_param_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER \
+ (tp_tests_param_connection_manager_get_type ())
+#define TP_TESTS_PARAM_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER, \
+ TpTestsParamConnectionManager))
+#define TP_TESTS_PARAM_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER, \
+ TpTestsParamConnectionManagerClass))
+#define IS_PARAM_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER))
+#define IS_PARAM_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER))
+#define TP_TESTS_PARAM_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_PARAM_CONNECTION_MANAGER, \
+ TpTestsParamConnectionManagerClass))
+
+typedef struct {
+ gchar *a_string;
+ gint a_int16;
+ gint a_int32;
+ guint a_uint16;
+ guint a_uint32;
+ gint64 a_int64;
+ guint64 a_uint64;
+ gboolean a_boolean;
+ gdouble a_double;
+ GStrv a_array_of_strings;
+ GArray *a_array_of_bytes;
+ gchar *a_object_path;
+ gchar *lc_string;
+ gchar *uc_string;
+ gboolean would_have_been_freed;
+} TpTestsCMParams;
+
+TpTestsCMParams * tp_tests_param_connection_manager_steal_params_last_conn (
+ void);
+void tp_tests_param_connection_manager_free_params (TpTestsCMParams *params);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_PARAM_CONNECTION_MANAGER_H__ */
diff --git a/qt4/tests/lib/glib/simple-account-manager.c b/qt4/tests/lib/glib/simple-account-manager.c
new file mode 100644
index 000000000..e1d1611f2
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-account-manager.c
@@ -0,0 +1,180 @@
+/*
+ * simple-account-manager.c - a simple account manager service.
+ *
+ * Copyright (C) 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "simple-account-manager.h"
+
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/svc-account-manager.h>
+
+static void account_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleAccountManager,
+ tp_tests_simple_account_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT_MANAGER,
+ account_manager_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init)
+ )
+
+
+/* TP_IFACE_ACCOUNT_MANAGER is implied */
+static const char *ACCOUNT_MANAGER_INTERFACES[] = { NULL };
+
+static gchar *VALID_ACCOUNTS[] = {
+ "/org/freedesktop/Telepathy/Account/fakecm/fakeproto/validaccount",
+ NULL };
+
+static gchar *INVALID_ACCOUNTS[] = {
+ "/org/freedesktop/Telepathy/Account/fakecm/fakeproto/invalidaccount",
+ NULL };
+
+enum
+{
+ PROP_0,
+ PROP_INTERFACES,
+ PROP_VALID_ACCOUNTS,
+ PROP_INVALID_ACCOUNTS,
+};
+
+struct _TpTestsSimpleAccountManagerPrivate
+{
+ int dummy;
+};
+
+static void
+tp_tests_simple_account_manager_create_account (TpSvcAccountManager *self,
+ const gchar *in_Connection_Manager,
+ const gchar *in_Protocol,
+ const gchar *in_Display_Name,
+ GHashTable *in_Parameters,
+ GHashTable *in_Properties,
+ DBusGMethodInvocation *context)
+{
+ const gchar *out_Account = "/some/fake/account/i/think";
+
+ tp_svc_account_manager_return_from_create_account (context, out_Account);
+}
+
+static void
+account_manager_iface_init (gpointer klass,
+ gpointer unused G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) tp_svc_account_manager_implement_##x (\
+ klass, tp_tests_simple_account_manager_##x)
+ IMPLEMENT (create_account);
+#undef IMPLEMENT
+}
+
+
+static void
+tp_tests_simple_account_manager_init (TpTestsSimpleAccountManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, TpTestsSimpleAccountManagerPrivate);
+}
+
+static void
+tp_tests_simple_account_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ GPtrArray *accounts;
+ guint i = 0;
+
+ switch (property_id) {
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, ACCOUNT_MANAGER_INTERFACES);
+ break;
+
+ case PROP_VALID_ACCOUNTS:
+ accounts = g_ptr_array_new ();
+
+ for (i=0; VALID_ACCOUNTS[i] != NULL; i++)
+ g_ptr_array_add (accounts, g_strdup (VALID_ACCOUNTS[i]));
+
+ g_value_take_boxed (value, accounts);
+ break;
+
+ case PROP_INVALID_ACCOUNTS:
+ accounts = g_ptr_array_new ();
+
+ for (i=0; INVALID_ACCOUNTS[i] != NULL; i++)
+ g_ptr_array_add (accounts, g_strdup (VALID_ACCOUNTS[i]));
+
+ g_value_take_boxed (value, accounts);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ break;
+ }
+}
+
+/**
+ * This class currently only provides the minimum for
+ * tp_account_manager_prepare to succeed. This turns out to be only a working
+ * Properties.GetAll(). If we wanted later to check the case where
+ * tp_account_prepare succeeds, we would need to implement an account object
+ * too.
+ */
+static void
+tp_tests_simple_account_manager_class_init (
+ TpTestsSimpleAccountManagerClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ static TpDBusPropertiesMixinPropImpl am_props[] = {
+ { "Interfaces", "interfaces", NULL },
+ { "ValidAccounts", "valid-accounts", NULL },
+ { "InvalidAccounts", "invalid-accounts", NULL },
+ /*
+ { "SupportedAccountProperties", "supported-account-properties", NULL },
+ */
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_ACCOUNT_MANAGER,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ am_props
+ },
+ { NULL },
+ };
+
+ g_type_class_add_private (klass, sizeof (TpTestsSimpleAccountManagerPrivate));
+ object_class->get_property = tp_tests_simple_account_manager_get_property;
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "In this case we only implement AccountManager, so none.",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+ param_spec = g_param_spec_boxed ("valid-accounts", "Valid accounts",
+ "The accounts which are valid on this account. This may be a lie.",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_VALID_ACCOUNTS, param_spec);
+ param_spec = g_param_spec_boxed ("invalid-accounts", "Invalid accounts",
+ "The accounts which are invalid on this account. This may be a lie.",
+ TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_INVALID_ACCOUNTS, param_spec);
+
+ klass->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsSimpleAccountManagerClass, dbus_props_class));
+}
diff --git a/qt4/tests/lib/glib/simple-account-manager.h b/qt4/tests/lib/glib/simple-account-manager.h
new file mode 100644
index 000000000..b4f787c18
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-account-manager.h
@@ -0,0 +1,58 @@
+/*
+ * simple-account-manager.h - header for a simple account manager service.
+ *
+ * Copyright (C) 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_ACCOUNT_MANAGER_H__
+#define __TP_TESTS_SIMPLE_ACCOUNT_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleAccountManager TpTestsSimpleAccountManager;
+typedef struct _TpTestsSimpleAccountManagerClass TpTestsSimpleAccountManagerClass;
+typedef struct _TpTestsSimpleAccountManagerPrivate TpTestsSimpleAccountManagerPrivate;
+
+struct _TpTestsSimpleAccountManagerClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _TpTestsSimpleAccountManager {
+ GObject parent;
+
+ TpTestsSimpleAccountManagerPrivate *priv;
+};
+
+GType tp_tests_simple_account_manager_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER \
+ (tp_tests_simple_account_manager_get_type ())
+#define SIMPLE_ACCOUNT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, \
+ TpTestsSimpleAccountManager))
+#define SIMPLE_ACCOUNT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, \
+ TpTestsSimpleAccountManagerClass))
+#define SIMPLE_IS_ACCOUNT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER))
+#define SIMPLE_IS_ACCOUNT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER))
+#define SIMPLE_ACCOUNT_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, \
+ TpTestsSimpleAccountManagerClass))
+
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_ACCOUNT_MANAGER_H__ */
diff --git a/qt4/tests/lib/glib/simple-account.c b/qt4/tests/lib/glib/simple-account.c
new file mode 100644
index 000000000..2674f71bb
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-account.c
@@ -0,0 +1,301 @@
+/*
+ * simple-account.c - a simple account service.
+ *
+ * Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "simple-account.h"
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/defs.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/svc-account.h>
+
+static void account_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleAccount,
+ tp_tests_simple_account,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT,
+ account_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init)
+ )
+
+/* TP_IFACE_ACCOUNT is implied */
+static const char *ACCOUNT_INTERFACES[] = { NULL };
+
+enum
+{
+ PROP_0,
+ PROP_INTERFACES,
+ PROP_DISPLAY_NAME,
+ PROP_ICON,
+ PROP_VALID,
+ PROP_ENABLED,
+ PROP_NICKNAME,
+ PROP_PARAMETERS,
+ PROP_AUTOMATIC_PRESENCE,
+ PROP_CONNECT_AUTO,
+ PROP_CONNECTION,
+ PROP_CONNECTION_STATUS,
+ PROP_CONNECTION_STATUS_REASON,
+ PROP_CURRENT_PRESENCE,
+ PROP_REQUESTED_PRESENCE,
+ PROP_NORMALIZED_NAME,
+ PROP_HAS_BEEN_ONLINE,
+};
+
+struct _TpTestsSimpleAccountPrivate
+{
+ gpointer unused;
+};
+
+static void
+account_iface_init (gpointer klass,
+ gpointer unused G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) tp_svc_account_implement_##x (\
+ klass, tp_tests_simple_account_##x)
+ /* TODO */
+#undef IMPLEMENT
+}
+
+
+static void
+tp_tests_simple_account_init (TpTestsSimpleAccount *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+ TpTestsSimpleAccountPrivate);
+}
+
+static void
+tp_tests_simple_account_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ GValueArray *presence;
+
+ presence = tp_value_array_build (3,
+ G_TYPE_UINT, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE,
+ G_TYPE_STRING, "available",
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID);
+
+ switch (property_id) {
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, ACCOUNT_INTERFACES);
+ break;
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, "Fake Account");
+ break;
+ case PROP_ICON:
+ g_value_set_string (value, "");
+ break;
+ case PROP_VALID:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_ENABLED:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_NICKNAME:
+ g_value_set_string (value, "badger");
+ break;
+ case PROP_PARAMETERS:
+ g_value_take_boxed (value, g_hash_table_new (NULL, NULL));
+ break;
+ case PROP_AUTOMATIC_PRESENCE:
+ g_value_set_boxed (value, presence);
+ break;
+ case PROP_CONNECT_AUTO:
+ g_value_set_boolean (value, FALSE);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_boxed (value, "/");
+ break;
+ case PROP_CONNECTION_STATUS:
+ g_value_set_uint (value, TP_CONNECTION_STATUS_CONNECTED);
+ break;
+ case PROP_CONNECTION_STATUS_REASON:
+ g_value_set_uint (value, TP_CONNECTION_STATUS_REASON_REQUESTED);
+ break;
+ case PROP_CURRENT_PRESENCE:
+ g_value_set_boxed (value, presence);
+ break;
+ case PROP_REQUESTED_PRESENCE:
+ g_value_set_boxed (value, presence);
+ break;
+ case PROP_NORMALIZED_NAME:
+ g_value_set_string (value, "");
+ break;
+ case PROP_HAS_BEEN_ONLINE:
+ g_value_set_boolean (value, TRUE);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ break;
+ }
+
+ g_boxed_free (TP_STRUCT_TYPE_SIMPLE_PRESENCE, presence);
+}
+
+/**
+ * This class currently only provides the minimum for
+ * tp_account_prepare to succeed. This turns out to be only a working
+ * Properties.GetAll().
+ */
+static void
+tp_tests_simple_account_class_init (TpTestsSimpleAccountClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ static TpDBusPropertiesMixinPropImpl a_props[] = {
+ { "Interfaces", "interfaces", NULL },
+ { "DisplayName", "display-name", NULL },
+ { "Icon", "icon", NULL },
+ { "Valid", "valid", NULL },
+ { "Enabled", "enabled", NULL },
+ { "Nickname", "nickname", NULL },
+ { "Parameters", "parameters", NULL },
+ { "AutomaticPresence", "automatic-presence", NULL },
+ { "ConnectAutomatically", "connect-automatically", NULL },
+ { "Connection", "connection", NULL },
+ { "ConnectionStatus", "connection-status", NULL },
+ { "ConnectionStatusReason", "connection-status-reason", NULL },
+ { "CurrentPresence", "current-presence", NULL },
+ { "RequestedPresence", "requested-presence", NULL },
+ { "NormalizedName", "normalized-name", NULL },
+ { "HasBeenOnline", "has-been-online", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_ACCOUNT,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ a_props
+ },
+ { NULL },
+ };
+
+ g_type_class_add_private (klass, sizeof (TpTestsSimpleAccountPrivate));
+ object_class->get_property = tp_tests_simple_account_get_property;
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "In this case we only implement Account, so none.",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("display-name", "display name",
+ "DisplayName property",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DISPLAY_NAME, param_spec);
+
+ param_spec = g_param_spec_string ("icon", "icon",
+ "Icon property",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ICON, param_spec);
+
+ param_spec = g_param_spec_boolean ("valid", "valid",
+ "Valid property",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_VALID, param_spec);
+
+ param_spec = g_param_spec_boolean ("enabled", "enabled",
+ "Enabled property",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ENABLED, param_spec);
+
+ param_spec = g_param_spec_string ("nickname", "nickname",
+ "Nickname property",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NICKNAME, param_spec);
+
+ param_spec = g_param_spec_boxed ("parameters", "parameters",
+ "Parameters property",
+ TP_HASH_TYPE_STRING_VARIANT_MAP,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PARAMETERS, param_spec);
+
+ param_spec = g_param_spec_boxed ("automatic-presence", "automatic presence",
+ "AutomaticPresence property",
+ TP_STRUCT_TYPE_SIMPLE_PRESENCE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_AUTOMATIC_PRESENCE,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("connect-automatically",
+ "connect automatically", "ConnectAutomatically property",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECT_AUTO, param_spec);
+
+ param_spec = g_param_spec_boxed ("connection", "connection",
+ "Connection property",
+ DBUS_TYPE_G_OBJECT_PATH,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_uint ("connection-status", "connection status",
+ "ConnectionStatus property",
+ 0, NUM_TP_CONNECTION_STATUSES, TP_CONNECTION_STATUS_DISCONNECTED,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION_STATUS,
+ param_spec);
+
+ param_spec = g_param_spec_uint ("connection-status-reason",
+ "connection status reason", "ConnectionStatusReason property",
+ 0, NUM_TP_CONNECTION_STATUS_REASONS,
+ TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION_STATUS_REASON,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("current-presence", "current presence",
+ "CurrentPresence property",
+ TP_STRUCT_TYPE_SIMPLE_PRESENCE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CURRENT_PRESENCE,
+ param_spec);
+
+ param_spec = g_param_spec_boxed ("requested-presence", "requested presence",
+ "RequestedPresence property",
+ TP_STRUCT_TYPE_SIMPLE_PRESENCE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED_PRESENCE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("normalized-name", "normalized name",
+ "NormalizedName property",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NORMALIZED_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("has-been-online", "has been online",
+ "HasBeenOnline property",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HAS_BEEN_ONLINE,
+ param_spec);
+
+ klass->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsSimpleAccountClass, dbus_props_class));
+}
diff --git a/qt4/tests/lib/glib/simple-account.h b/qt4/tests/lib/glib/simple-account.h
new file mode 100644
index 000000000..7af263d61
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-account.h
@@ -0,0 +1,56 @@
+/*
+ * simple-account.h - header for a simple account service.
+ *
+ * Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_ACCOUNT_H__
+#define __TP_TESTS_SIMPLE_ACCOUNT_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleAccount TpTestsSimpleAccount;
+typedef struct _TpTestsSimpleAccountClass TpTestsSimpleAccountClass;
+typedef struct _TpTestsSimpleAccountPrivate TpTestsSimpleAccountPrivate;
+
+struct _TpTestsSimpleAccountClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _TpTestsSimpleAccount {
+ GObject parent;
+
+ TpTestsSimpleAccountPrivate *priv;
+};
+
+GType tp_tests_simple_account_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_ACCOUNT \
+ (tp_tests_simple_account_get_type ())
+#define TP_TESTS_SIMPLE_ACCOUNT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT, \
+ TpTestsSimpleAccount))
+#define TP_TESTS_SIMPLE_ACCOUNT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT, \
+ TpTestsSimpleAccountClass))
+#define TP_TESTS_SIMPLE_IS_ACCOUNT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT))
+#define TP_TESTS_SIMPLE_IS_ACCOUNT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT))
+#define TP_TESTS_SIMPLE_ACCOUNT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT, \
+ TpTestsSimpleAccountClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_ACCOUNT_H__ */
diff --git a/qt4/tests/lib/glib/simple-channel-dispatch-operation.c b/qt4/tests/lib/glib/simple-channel-dispatch-operation.c
new file mode 100644
index 000000000..c74ff773f
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-channel-dispatch-operation.c
@@ -0,0 +1,297 @@
+/*
+ * simple-channel-dispatch-operation.c - a simple channel dispatch operation
+ * service.
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "simple-channel-dispatch-operation.h"
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/defs.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/svc-channel-dispatch-operation.h>
+
+static void channel_dispatch_operation_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleChannelDispatchOperation,
+ tp_tests_simple_channel_dispatch_operation,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_DISPATCH_OPERATION,
+ channel_dispatch_operation_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init)
+ )
+
+/* TP_IFACE_CHANNEL_DISPATCH_OPERATION is implied */
+static const char *CHANNEL_DISPATCH_OPERATION_INTERFACES[] = { NULL };
+
+static const gchar *CHANNEL_DISPATCH_OPERATION_POSSIBLE_HANDLERS[] = {
+ TP_CLIENT_BUS_NAME_BASE ".Badger", NULL, };
+
+enum
+{
+ PROP_0,
+ PROP_INTERFACES,
+ PROP_CONNECTION,
+ PROP_ACCOUNT,
+ PROP_CHANNELS,
+ PROP_POSSIBLE_HANDLERS,
+};
+
+struct _SimpleChannelDispatchOperationPrivate
+{
+ gchar *conn_path;
+ gchar *account_path;
+ /* Array of TpChannel */
+ GPtrArray *channels;
+};
+
+static void
+tp_tests_simple_channel_dispatch_operation_handle_with (
+ TpSvcChannelDispatchOperation *iface,
+ const gchar *handler,
+ DBusGMethodInvocation *context)
+{
+ if (!tp_strdiff (handler, "FAIL"))
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Nope" };
+
+ dbus_g_method_return_error (context, &error);
+ return;
+ }
+
+ dbus_g_method_return (context);
+}
+
+static void
+tp_tests_simple_channel_dispatch_operation_claim (
+ TpSvcChannelDispatchOperation *iface,
+ DBusGMethodInvocation *context)
+{
+ dbus_g_method_return (context);
+}
+
+static void
+tp_tests_simple_channel_dispatch_operation_handle_with_time (
+ TpSvcChannelDispatchOperation *iface,
+ const gchar *handler,
+ gint64 user_action_timestamp,
+ DBusGMethodInvocation *context)
+{
+ dbus_g_method_return (context);
+}
+
+static void
+channel_dispatch_operation_iface_init (gpointer klass,
+ gpointer unused G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) tp_svc_channel_dispatch_operation_implement_##x (\
+ klass, tp_tests_simple_channel_dispatch_operation_##x)
+ IMPLEMENT(handle_with);
+ IMPLEMENT(claim);
+ IMPLEMENT(handle_with_time);
+#undef IMPLEMENT
+}
+
+
+static void
+tp_tests_simple_channel_dispatch_operation_init (TpTestsSimpleChannelDispatchOperation *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION,
+ TpTestsSimpleChannelDispatchOperationPrivate);
+
+ self->priv->channels = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) g_object_unref);
+}
+
+static void
+tp_tests_simple_channel_dispatch_operation_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ TpTestsSimpleChannelDispatchOperation *self =
+ TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION (object);
+
+ switch (property_id) {
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, CHANNEL_DISPATCH_OPERATION_INTERFACES);
+ break;
+
+ case PROP_ACCOUNT:
+ g_value_set_boxed (value, self->priv->account_path);
+ break;
+
+ case PROP_CONNECTION:
+ g_value_set_boxed (value, self->priv->conn_path);
+ break;
+
+ case PROP_CHANNELS:
+ {
+ GPtrArray *arr = g_ptr_array_new ();
+ guint i;
+
+ for (i = 0; i < self->priv->channels->len; i++)
+ {
+ TpChannel *channel = g_ptr_array_index (self->priv->channels, i);
+
+ g_ptr_array_add (arr,
+ tp_value_array_build (2,
+ DBUS_TYPE_G_OBJECT_PATH, tp_proxy_get_object_path (channel),
+ TP_HASH_TYPE_STRING_VARIANT_MAP,
+ tp_channel_borrow_immutable_properties (channel),
+ G_TYPE_INVALID));
+ }
+
+ g_value_take_boxed (value, arr);
+ }
+ break;
+
+ case PROP_POSSIBLE_HANDLERS:
+ g_value_set_boxed (value, CHANNEL_DISPATCH_OPERATION_POSSIBLE_HANDLERS);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ break;
+ }
+}
+
+static void
+tp_tests_simple_channel_dispatch_operation_finalize (GObject *object)
+{
+ TpTestsSimpleChannelDispatchOperation *self =
+ TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION (object);
+ void (*finalize) (GObject *) =
+ G_OBJECT_CLASS (tp_tests_simple_channel_dispatch_operation_parent_class)->finalize;
+
+ g_free (self->priv->conn_path);
+ g_free (self->priv->account_path);
+ g_ptr_array_free (self->priv->channels, TRUE);
+
+ if (finalize != NULL)
+ finalize (object);
+}
+
+/**
+ * This class currently only provides the minimum for
+ * tp_channel_dispatch_operation_prepare to succeed. This turns out to be only a working
+ * Properties.GetAll().
+ */
+static void
+tp_tests_simple_channel_dispatch_operation_class_init (TpTestsSimpleChannelDispatchOperationClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ static TpDBusPropertiesMixinPropImpl a_props[] = {
+ { "Interfaces", "interfaces", NULL },
+ { "Connection", "connection", NULL },
+ { "Account", "account", NULL },
+ { "Channels", "channels", NULL },
+ { "PossibleHandlers", "possible-handlers", NULL },
+ { NULL }
+ };
+
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL_DISPATCH_OPERATION,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ a_props
+ },
+ { NULL },
+ };
+
+ g_type_class_add_private (klass, sizeof (TpTestsSimpleChannelDispatchOperationPrivate));
+ object_class->get_property = tp_tests_simple_channel_dispatch_operation_get_property;
+ object_class->finalize = tp_tests_simple_channel_dispatch_operation_finalize;
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "In this case we only implement ChannelDispatchOperation, so none.",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_boxed ("connection", "connection path",
+ "Connection path",
+ DBUS_TYPE_G_OBJECT_PATH,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("account", "account path",
+ "Account path",
+ DBUS_TYPE_G_OBJECT_PATH,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_boxed ("channels", "channel paths",
+ "Channel paths",
+ TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CHANNELS, param_spec);
+
+ param_spec = g_param_spec_boxed ("possible-handlers", "possible handlers",
+ "possible handles",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_POSSIBLE_HANDLERS,
+ param_spec);
+
+ klass->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsSimpleChannelDispatchOperationClass, dbus_props_class));
+}
+
+void
+tp_tests_simple_channel_dispatch_operation_set_conn_path (
+ TpTestsSimpleChannelDispatchOperation *self,
+ const gchar *conn_path)
+{
+ self->priv->conn_path = g_strdup (conn_path);
+}
+
+void
+tp_tests_simple_channel_dispatch_operation_add_channel (
+ TpTestsSimpleChannelDispatchOperation *self,
+ TpChannel *chan)
+{
+ g_ptr_array_add (self->priv->channels, g_object_ref (chan));
+}
+
+void
+tp_tests_simple_channel_dispatch_operation_lost_channel (
+ TpTestsSimpleChannelDispatchOperation *self,
+ TpChannel *chan)
+{
+ const gchar *path = tp_proxy_get_object_path (chan);
+
+ g_ptr_array_remove (self->priv->channels, chan);
+
+ tp_svc_channel_dispatch_operation_emit_channel_lost (self, path,
+ TP_ERROR_STR_NOT_AVAILABLE, "Badger");
+
+ if (self->priv->channels->len == 0)
+ {
+ /* We removed the last channel; fire Finished */
+ tp_svc_channel_dispatch_operation_emit_finished (self);
+ }
+}
+
+void
+tp_tests_simple_channel_dispatch_operation_set_account_path (
+ TpTestsSimpleChannelDispatchOperation *self,
+ const gchar *account_path)
+{
+ self->priv->account_path = g_strdup (account_path);
+}
diff --git a/qt4/tests/lib/glib/simple-channel-dispatch-operation.h b/qt4/tests/lib/glib/simple-channel-dispatch-operation.h
new file mode 100644
index 000000000..84d8f0417
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-channel-dispatch-operation.h
@@ -0,0 +1,74 @@
+/*
+ * simple-channel-dispatch-operation.h - a simple channel dispatch operation
+ * service.
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION_H__
+#define __TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/channel.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SimpleChannelDispatchOperation TpTestsSimpleChannelDispatchOperation;
+typedef struct _SimpleChannelDispatchOperationClass TpTestsSimpleChannelDispatchOperationClass;
+typedef struct _SimpleChannelDispatchOperationPrivate TpTestsSimpleChannelDispatchOperationPrivate;
+
+struct _SimpleChannelDispatchOperationClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _SimpleChannelDispatchOperation {
+ GObject parent;
+
+ TpTestsSimpleChannelDispatchOperationPrivate *priv;
+};
+
+GType tp_tests_simple_channel_dispatch_operation_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION \
+ (tp_tests_simple_channel_dispatch_operation_get_type ())
+#define TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION, \
+ TpTestsSimpleChannelDispatchOperation))
+#define TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION, \
+ TpTestsSimpleChannelDispatchOperationClass))
+#define SIMPLE_IS_CHANNEL_DISPATCH_OPERATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION))
+#define SIMPLE_IS_CHANNEL_DISPATCH_OPERATION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION))
+#define TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_CHANNEL_DISPATCH_OPERATION, \
+ TpTestsSimpleChannelDispatchOperationClass))
+
+void tp_tests_simple_channel_dispatch_operation_set_conn_path (
+ TpTestsSimpleChannelDispatchOperation *self,
+ const gchar *conn_path);
+
+void tp_tests_simple_channel_dispatch_operation_add_channel (
+ TpTestsSimpleChannelDispatchOperation *self,
+ TpChannel *chan);
+
+void tp_tests_simple_channel_dispatch_operation_lost_channel (
+ TpTestsSimpleChannelDispatchOperation *self,
+ TpChannel *chan);
+
+void tp_tests_simple_channel_dispatch_operation_set_account_path (
+ TpTestsSimpleChannelDispatchOperation *self,
+ const gchar *account_path);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_CHANNEL_DISPATCH_OPERATION_H__ */
diff --git a/qt4/tests/lib/glib/simple-client.c b/qt4/tests/lib/glib/simple-client.c
new file mode 100644
index 000000000..831abd953
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-client.c
@@ -0,0 +1,243 @@
+/*
+ * simple-client.c - a simple client
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "simple-client.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include "util.h"
+
+G_DEFINE_TYPE (TpTestsSimpleClient, tp_tests_simple_client, TP_TYPE_BASE_CLIENT)
+
+static void
+simple_observe_channels (
+ TpBaseClient *client,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ TpChannelDispatchOperation *dispatch_operation,
+ GList *requests,
+ TpObserveChannelsContext *context)
+{
+ TpTestsSimpleClient *self = TP_TESTS_SIMPLE_CLIENT (client);
+ GHashTable *info;
+ gboolean fail;
+ GList *l;
+
+ /* Fail if caller set the fake "FAIL" info */
+ g_object_get (context, "observer-info", &info, NULL);
+ fail = tp_asv_get_boolean (info, "FAIL", NULL);
+ g_hash_table_unref (info);
+
+ if (self->observe_ctx != NULL)
+ {
+ g_object_unref (self->observe_ctx);
+ self->observe_ctx = NULL;
+ }
+
+ if (fail)
+ {
+ GError error = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No observation for you!" };
+
+ tp_observe_channels_context_fail (context, &error);
+ return;
+ }
+
+ g_assert (TP_IS_ACCOUNT (account));
+ g_assert (tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ g_assert (TP_IS_CONNECTION (connection));
+ g_assert (tp_proxy_is_prepared (connection, TP_CONNECTION_FEATURE_CORE));
+
+ g_assert_cmpuint (g_list_length (channels), >, 0);
+ for (l = channels; l != NULL; l = g_list_next (l))
+ {
+ TpChannel *channel = l->data;
+
+ g_assert (TP_IS_CHANNEL (channel));
+ g_assert (tp_proxy_is_prepared (channel, TP_CHANNEL_FEATURE_CORE) ||
+ tp_proxy_get_invalidated (channel) != NULL);
+ }
+
+ if (dispatch_operation != NULL)
+ g_assert (TP_IS_CHANNEL_DISPATCH_OPERATION (dispatch_operation));
+
+ for (l = requests; l != NULL; l = g_list_next (l))
+ {
+ TpChannelRequest *request = l->data;
+
+ g_assert (TP_IS_CHANNEL_REQUEST (request));
+ }
+
+ self->observe_ctx = g_object_ref (context);
+ tp_observe_channels_context_accept (context);
+}
+
+static void
+simple_add_dispatch_operation (
+ TpBaseClient *client,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ TpChannelDispatchOperation *dispatch_operation,
+ TpAddDispatchOperationContext *context)
+{
+ TpTestsSimpleClient *self = TP_TESTS_SIMPLE_CLIENT (client);
+ GList *l;
+
+ g_assert (TP_IS_ACCOUNT (account));
+ g_assert (tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ g_assert (TP_IS_CONNECTION (connection));
+ g_assert (tp_proxy_is_prepared (connection, TP_CONNECTION_FEATURE_CORE));
+
+ g_assert (TP_IS_CHANNEL_DISPATCH_OPERATION (dispatch_operation));
+ g_assert (tp_proxy_is_prepared (dispatch_operation,
+ TP_CHANNEL_DISPATCH_OPERATION_FEATURE_CORE) ||
+ tp_proxy_get_invalidated (dispatch_operation) != NULL);
+
+ if (self->add_dispatch_ctx != NULL)
+ {
+ g_object_unref (self->add_dispatch_ctx);
+ self->add_dispatch_ctx = NULL;
+ }
+
+ g_assert_cmpuint (g_list_length (channels), >, 0);
+ for (l = channels; l != NULL; l = g_list_next (l))
+ {
+ TpChannel *channel = l->data;
+
+ g_assert (TP_IS_CHANNEL (channel));
+ g_assert (tp_proxy_is_prepared (channel, TP_CHANNEL_FEATURE_CORE) ||
+ tp_proxy_get_invalidated (channel) != NULL);
+ }
+
+ self->add_dispatch_ctx = g_object_ref (context);
+ tp_add_dispatch_operation_context_accept (context);
+}
+
+static void
+simple_handle_channels (TpBaseClient *client,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ GList *requests_satisfied,
+ gint64 user_action_time,
+ TpHandleChannelsContext *context)
+{
+ TpTestsSimpleClient *self = TP_TESTS_SIMPLE_CLIENT (client);
+ GList *l;
+
+ if (self->handle_channels_ctx != NULL)
+ {
+ g_object_unref (self->handle_channels_ctx);
+ self->handle_channels_ctx = NULL;
+ }
+
+ g_assert (TP_IS_ACCOUNT (account));
+ g_assert (tp_proxy_is_prepared (account, TP_ACCOUNT_FEATURE_CORE));
+
+ g_assert (TP_IS_CONNECTION (connection));
+ g_assert (tp_proxy_is_prepared (connection, TP_CONNECTION_FEATURE_CORE));
+
+ g_assert_cmpuint (g_list_length (channels), >, 0);
+ for (l = channels; l != NULL; l = g_list_next (l))
+ {
+ TpChannel *channel = l->data;
+
+ g_assert (TP_IS_CHANNEL (channel));
+ g_assert (tp_proxy_is_prepared (channel, TP_CHANNEL_FEATURE_CORE) ||
+ tp_proxy_get_invalidated (channel) != NULL);
+ }
+
+ for (l = requests_satisfied; l != NULL; l = g_list_next (l))
+ {
+ TpChannelRequest *request = l->data;
+
+ g_assert (TP_IS_CHANNEL_REQUEST (request));
+ }
+
+ self->handle_channels_ctx = g_object_ref (context);
+ tp_handle_channels_context_accept (context);
+}
+
+static void
+tp_tests_simple_client_init (TpTestsSimpleClient *self)
+{
+}
+
+static void
+tp_tests_simple_client_dispose (GObject *object)
+{
+ TpTestsSimpleClient *self = TP_TESTS_SIMPLE_CLIENT (object);
+ void (*dispose) (GObject *) =
+ G_OBJECT_CLASS (tp_tests_simple_client_parent_class)->dispose;
+
+ if (self->observe_ctx != NULL)
+ {
+ g_object_unref (self->observe_ctx);
+ self->observe_ctx = NULL;
+ }
+
+ if (self->add_dispatch_ctx != NULL)
+ {
+ g_object_unref (self->add_dispatch_ctx);
+ self->add_dispatch_ctx = NULL;
+ }
+
+ if (self->handle_channels_ctx != NULL)
+ {
+ g_object_unref (self->handle_channels_ctx);
+ self->handle_channels_ctx = NULL;
+ }
+
+ if (dispose != NULL)
+ dispose (object);
+}
+
+static void
+tp_tests_simple_client_class_init (TpTestsSimpleClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ TpBaseClientClass *base_class = TP_BASE_CLIENT_CLASS (klass);
+
+ object_class->dispose = tp_tests_simple_client_dispose;
+
+ tp_base_client_implement_observe_channels (base_class,
+ simple_observe_channels);
+
+ tp_base_client_implement_add_dispatch_operation (base_class,
+ simple_add_dispatch_operation);
+
+ tp_base_client_implement_handle_channels (base_class,
+ simple_handle_channels);
+}
+
+TpTestsSimpleClient *
+tp_tests_simple_client_new (TpDBusDaemon *dbus_daemon,
+ const gchar *name,
+ gboolean uniquify_name)
+{
+ return tp_tests_object_new_static_class (TP_TESTS_TYPE_SIMPLE_CLIENT,
+ "dbus-daemon", dbus_daemon,
+ "name", name,
+ "uniquify-name", uniquify_name,
+ NULL);
+}
diff --git a/qt4/tests/lib/glib/simple-client.h b/qt4/tests/lib/glib/simple-client.h
new file mode 100644
index 000000000..ec4fd31b8
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-client.h
@@ -0,0 +1,59 @@
+/*
+ * simple-client.h - header for a simple client
+ *
+ * Copyright © 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_CLIENT_H__
+#define __TP_TESTS_SIMPLE_CLIENT_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-client.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleClient TpTestsSimpleClient;
+typedef struct _TpTestsSimpleClientClass TpTestsSimpleClientClass;
+
+struct _TpTestsSimpleClientClass {
+ TpBaseClientClass parent_class;
+};
+
+struct _TpTestsSimpleClient {
+ TpBaseClient parent;
+
+ TpObserveChannelsContext *observe_ctx;
+ TpAddDispatchOperationContext *add_dispatch_ctx;
+ TpHandleChannelsContext *handle_channels_ctx;
+};
+
+GType tp_tests_simple_client_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_CLIENT \
+ (tp_tests_simple_client_get_type ())
+#define TP_TESTS_SIMPLE_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_CLIENT, \
+ TpTestsSimpleClient))
+#define TP_TESTS_SIMPLE_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_CLIENT, \
+ TpTestsSimpleClientClass))
+#define SIMPLE_IS_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_CLIENT))
+#define SIMPLE_IS_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_CLIENT))
+#define TP_TESTS_SIMPLE_CLIENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_CLIENT, \
+ TpTestsSimpleClientClass))
+
+TpTestsSimpleClient * tp_tests_simple_client_new (TpDBusDaemon *dbus_daemon,
+ const gchar *name,
+ gboolean uniquify_name);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_CONN_H__ */
diff --git a/qt4/tests/lib/glib/simple-conn.c b/qt4/tests/lib/glib/simple-conn.c
new file mode 100644
index 000000000..f9564352d
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-conn.c
@@ -0,0 +1,453 @@
+/*
+ * simple-conn.c - a simple connection
+ *
+ * Copyright (C) 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "simple-conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include "textchan-null.h"
+#include "util.h"
+
+static void conn_iface_init (TpSvcConnectionClass *);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleConnection, tp_tests_simple_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION, conn_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+ PROP_ACCOUNT = 1,
+ PROP_BREAK_PROPS = 2,
+ PROP_DBUS_STATUS = 3,
+ N_PROPS
+};
+
+enum
+{
+ SIGNAL_GOT_SELF_HANDLE,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = {0};
+
+struct _TpTestsSimpleConnectionPrivate
+{
+ gchar *account;
+ guint connect_source;
+ guint disconnect_source;
+ gboolean break_fastpath_props;
+
+ /* TpHandle => reffed TpTestsTextChannelNull */
+ GHashTable *channels;
+
+ GError *get_self_handle_error /* initially NULL */ ;
+};
+
+static void
+tp_tests_simple_connection_init (TpTestsSimpleConnection *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_SIMPLE_CONNECTION, TpTestsSimpleConnectionPrivate);
+
+ self->priv->channels = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) g_object_unref);
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *spec)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_value_set_string (value, self->priv->account);
+ break;
+ case PROP_BREAK_PROPS:
+ g_value_set_boolean (value, self->priv->break_fastpath_props);
+ break;
+ case PROP_DBUS_STATUS:
+ if (self->priv->break_fastpath_props)
+ {
+ g_debug ("returning broken value for Connection.Status");
+ g_value_set_uint (value, 0xdeadbeefU);
+ }
+ else
+ {
+ guint32 status = TP_BASE_CONNECTION (self)->status;
+
+ if (status == TP_INTERNAL_CONNECTION_STATUS_NEW)
+ g_value_set_uint (value, TP_CONNECTION_STATUS_DISCONNECTED);
+ else
+ g_value_set_uint (value, status);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *spec)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+ switch (property_id) {
+ case PROP_ACCOUNT:
+ g_free (self->priv->account);
+ self->priv->account = g_utf8_strdown (g_value_get_string (value), -1);
+ break;
+ case PROP_BREAK_PROPS:
+ self->priv->break_fastpath_props = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+ g_hash_table_unref (self->priv->channels);
+
+ G_OBJECT_CLASS (tp_tests_simple_connection_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+ if (self->priv->connect_source != 0)
+ {
+ g_source_remove (self->priv->connect_source);
+ }
+
+ if (self->priv->disconnect_source != 0)
+ {
+ g_source_remove (self->priv->disconnect_source);
+ }
+
+ g_clear_error (&self->priv->get_self_handle_error);
+ g_free (self->priv->account);
+
+ G_OBJECT_CLASS (tp_tests_simple_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn);
+
+ return g_strdup (self->priv->account);
+}
+
+static gchar *
+tp_tests_simple_normalize_contact (TpHandleRepoIface *repo,
+ const gchar *id,
+ gpointer context,
+ GError **error)
+{
+ if (id[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must not be empty");
+ return NULL;
+ }
+
+ if (strchr (id, ' ') != NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "ID must not contain spaces");
+ return NULL;
+ }
+
+ return g_utf8_strdown (id, -1);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, tp_tests_simple_normalize_contact, NULL);
+ repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_ROOM, NULL, NULL);
+}
+
+static GPtrArray *
+create_channel_factories (TpBaseConnection *conn)
+{
+ return g_ptr_array_sized_new (0);
+}
+
+void
+tp_tests_simple_connection_inject_disconnect (TpTestsSimpleConnection *self)
+{
+ tp_base_connection_change_status ((TpBaseConnection *) self,
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+}
+
+static gboolean
+pretend_connected (gpointer data)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (data);
+ TpBaseConnection *conn = (TpBaseConnection *) self;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+ NULL, NULL);
+
+ if (conn->status == TP_CONNECTION_STATUS_CONNECTING)
+ {
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+ }
+
+ self->priv->connect_source = 0;
+ return FALSE;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+ GError **error)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn);
+
+ tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTING,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start connecting, then go to state CONNECTED when finished. Here there
+ * isn't actually a connection, so we'll fake a connection process that
+ * takes time. */
+ self->priv->connect_source = g_timeout_add (0, pretend_connected, self);
+
+ return TRUE;
+}
+
+static gboolean
+pretend_disconnected (gpointer data)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (data);
+
+ /* We are disconnected, all our channels are invalidated */
+ g_hash_table_remove_all (self->priv->channels);
+
+ tp_base_connection_finish_shutdown (TP_BASE_CONNECTION (data));
+ self->priv->disconnect_source = 0;
+ return FALSE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn);
+
+ /* In a real connection manager we'd ask the underlying implementation to
+ * start shutting down, then call this function when finished. Here there
+ * isn't actually a connection, so we'll fake a disconnection process that
+ * takes time. */
+ self->priv->disconnect_source = g_timeout_add (0, pretend_disconnected,
+ conn);
+}
+
+static void
+tp_tests_simple_connection_class_init (TpTestsSimpleConnectionClass *klass)
+{
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+ static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS, NULL };
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+ g_type_class_add_private (klass, sizeof (TpTestsSimpleConnectionPrivate));
+
+ base_class->create_handle_repos = create_handle_repos;
+ base_class->get_unique_connection_name = get_unique_connection_name;
+ base_class->create_channel_factories = create_channel_factories;
+ base_class->start_connecting = start_connecting;
+ base_class->shut_down = shut_down;
+
+ base_class->interfaces_always_present = interfaces_always_present;
+
+ param_spec = g_param_spec_string ("account", "Account name",
+ "The username of this user", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+ param_spec = g_param_spec_boolean ("break-0192-properties",
+ "Break 0.19.2 properties",
+ "Break Connection D-Bus properties introduced in spec 0.19.2", FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_BREAK_PROPS, param_spec);
+
+ param_spec = g_param_spec_uint ("dbus-status",
+ "Connection.Status",
+ "The connection status as visible on D-Bus (overridden so can break it)",
+ TP_CONNECTION_STATUS_CONNECTED, G_MAXUINT,
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_STATUS, param_spec);
+
+ signals[SIGNAL_GOT_SELF_HANDLE] = g_signal_new ("got-self-handle",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+void
+tp_tests_simple_connection_set_identifier (TpTestsSimpleConnection *self,
+ const gchar *identifier)
+{
+ TpBaseConnection *conn = (TpBaseConnection *) self;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle = tp_handle_ensure (contact_repo, identifier, NULL, NULL);
+
+ /* if this fails then the identifier was bad - caller error */
+ g_return_if_fail (handle != 0);
+
+ tp_base_connection_set_self_handle (conn, handle);
+ tp_handle_unref (contact_repo, handle);
+}
+
+TpTestsSimpleConnection *
+tp_tests_simple_connection_new (const gchar *account,
+ const gchar *protocol)
+{
+ return TP_TESTS_SIMPLE_CONNECTION (g_object_new (
+ TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", account,
+ "protocol", protocol,
+ NULL));
+}
+
+gchar *
+tp_tests_simple_connection_ensure_text_chan (TpTestsSimpleConnection *self,
+ const gchar *target_id,
+ GHashTable **props)
+{
+ TpTestsTextChannelNull *chan;
+ gchar *chan_path;
+ TpHandleRepoIface *contact_repo;
+ TpHandle handle;
+ static guint count = 0;
+ TpBaseConnection *base_conn = (TpBaseConnection *) self;
+
+ /* Get contact handle */
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ g_assert (contact_repo != NULL);
+
+ handle = tp_handle_ensure (contact_repo, target_id, NULL, NULL);
+
+ chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+ if (chan != NULL)
+ {
+ /* Channel already exist, reuse it */
+ g_object_get (chan, "object-path", &chan_path, NULL);
+ }
+ else
+ {
+ chan_path = g_strdup_printf ("%s/Channel%u", base_conn->object_path,
+ count++);
+
+ chan = TP_TESTS_TEXT_CHANNEL_NULL (
+ tp_tests_object_new_static_class (
+ TP_TESTS_TYPE_TEXT_CHANNEL_NULL,
+ "connection", self,
+ "object-path", chan_path,
+ "handle", handle,
+ NULL));
+
+ g_hash_table_insert (self->priv->channels, GUINT_TO_POINTER (handle),
+ chan);
+ }
+
+ tp_handle_unref (contact_repo, handle);
+
+ if (props != NULL)
+ *props = tp_tests_text_channel_get_props (chan);
+
+ return chan_path;
+}
+
+void
+tp_tests_simple_connection_set_get_self_handle_error (
+ TpTestsSimpleConnection *self,
+ GQuark domain,
+ gint code,
+ const gchar *message)
+{
+ self->priv->get_self_handle_error = g_error_new_literal (domain, code,
+ message);
+}
+
+static void
+get_self_handle (TpSvcConnection *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (iface);
+ TpBaseConnection *base = TP_BASE_CONNECTION (iface);
+
+ g_assert (TP_IS_BASE_CONNECTION (base));
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (self->priv->get_self_handle_error != NULL)
+ {
+ dbus_g_method_return_error (context, self->priv->get_self_handle_error);
+ return;
+ }
+
+ tp_svc_connection_return_from_get_self_handle (context, base->self_handle);
+ g_signal_emit (self, signals[SIGNAL_GOT_SELF_HANDLE], 0);
+}
+
+static void
+conn_iface_init (TpSvcConnectionClass *iface)
+{
+#define IMPLEMENT(prefix,x) \
+ tp_svc_connection_implement_##x (iface, prefix##x)
+ IMPLEMENT(,get_self_handle);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/simple-conn.h b/qt4/tests/lib/glib/simple-conn.h
new file mode 100644
index 000000000..6322f4b86
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-conn.h
@@ -0,0 +1,77 @@
+/*
+ * simple-conn.h - header for a simple connection
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_CONN_H__
+#define __TP_TESTS_SIMPLE_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleConnection TpTestsSimpleConnection;
+typedef struct _TpTestsSimpleConnectionClass TpTestsSimpleConnectionClass;
+typedef struct _TpTestsSimpleConnectionPrivate TpTestsSimpleConnectionPrivate;
+
+struct _TpTestsSimpleConnectionClass {
+ TpBaseConnectionClass parent_class;
+};
+
+struct _TpTestsSimpleConnection {
+ TpBaseConnection parent;
+
+ TpTestsSimpleConnectionPrivate *priv;
+};
+
+GType tp_tests_simple_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_CONNECTION \
+ (tp_tests_simple_connection_get_type ())
+#define TP_TESTS_SIMPLE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION, \
+ TpTestsSimpleConnection))
+#define TP_TESTS_SIMPLE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_CONNECTION, \
+ TpTestsSimpleConnectionClass))
+#define TP_TESTS_SIMPLE_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION))
+#define TP_TESTS_SIMPLE_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_CONNECTION))
+#define TP_TESTS_SIMPLE_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION, \
+ TpTestsSimpleConnectionClass))
+
+TpTestsSimpleConnection * tp_tests_simple_connection_new (const gchar *account,
+ const gchar *protocol);
+
+/* Cause "network events", for debugging/testing */
+
+void tp_tests_simple_connection_inject_disconnect (
+ TpTestsSimpleConnection *self);
+
+void tp_tests_simple_connection_set_identifier (TpTestsSimpleConnection *self,
+ const gchar *identifier);
+
+gchar * tp_tests_simple_connection_ensure_text_chan (
+ TpTestsSimpleConnection *self,
+ const gchar *target_id,
+ GHashTable **props);
+
+void tp_tests_simple_connection_set_get_self_handle_error (
+ TpTestsSimpleConnection *self,
+ GQuark domain,
+ gint code,
+ const gchar *message);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_CONN_H__ */
diff --git a/qt4/tests/lib/glib/simple-manager.c b/qt4/tests/lib/glib/simple-manager.c
new file mode 100644
index 000000000..d1592cfca
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-manager.c
@@ -0,0 +1,97 @@
+/*
+ * simple-manager.c - an simple connection manager
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "simple-manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+#include "simple-conn.h"
+#include "util.h"
+
+G_DEFINE_TYPE (TpTestsSimpleConnectionManager,
+ tp_tests_simple_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER)
+
+/* type definition stuff */
+
+static void
+tp_tests_simple_connection_manager_init (TpTestsSimpleConnectionManager *self)
+{
+}
+
+/* private data */
+
+typedef struct {
+ gchar *account;
+} TpTestsSimpleParams;
+
+static const TpCMParamSpec simple_params[] = {
+ { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER, NULL,
+ G_STRUCT_OFFSET (TpTestsSimpleParams, account),
+ tp_cm_param_filter_string_nonempty, NULL },
+
+ { NULL }
+};
+
+static gpointer
+alloc_params (void)
+{
+ return g_slice_new0 (TpTestsSimpleParams);
+}
+
+static void
+free_params (gpointer p)
+{
+ TpTestsSimpleParams *params = p;
+
+ g_free (params->account);
+
+ g_slice_free (TpTestsSimpleParams, params);
+}
+
+static const TpCMProtocolSpec simple_protocols[] = {
+ { "simple", simple_params, alloc_params, free_params },
+ { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+ const gchar *proto,
+ TpIntSet *params_present,
+ gpointer parsed_params,
+ GError **error)
+{
+ TpTestsSimpleParams *params = parsed_params;
+ TpTestsSimpleConnection *conn = TP_TESTS_SIMPLE_CONNECTION (
+ tp_tests_object_new_static_class (TP_TESTS_TYPE_SIMPLE_CONNECTION,
+ "account", params->account,
+ "protocol", proto,
+ NULL));
+
+ return (TpBaseConnection *) conn;
+}
+
+static void
+tp_tests_simple_connection_manager_class_init (
+ TpTestsSimpleConnectionManagerClass *klass)
+{
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *) klass;
+
+ base_class->new_connection = new_connection;
+ base_class->cm_dbus_name = "simple";
+ base_class->protocol_params = simple_protocols;
+}
diff --git a/qt4/tests/lib/glib/simple-manager.h b/qt4/tests/lib/glib/simple-manager.h
new file mode 100644
index 000000000..2edf21394
--- /dev/null
+++ b/qt4/tests/lib/glib/simple-manager.h
@@ -0,0 +1,58 @@
+/*
+ * simple-manager.h - header for a simple connection manager
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_CONNECTION_MANAGER_H__
+#define __TP_TESTS_SIMPLE_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleConnectionManager TpTestsSimpleConnectionManager;
+typedef struct _TpTestsSimpleConnectionManagerPrivate TpTestsSimpleConnectionManagerPrivate;
+typedef struct _TpTestsSimpleConnectionManagerClass TpTestsSimpleConnectionManagerClass;
+typedef struct _TpTestsSimpleConnectionManagerClassPrivate
+ TpTestsSimpleConnectionManagerClassPrivate;
+
+struct _TpTestsSimpleConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+
+ TpTestsSimpleConnectionManagerClassPrivate *priv;
+};
+
+struct _TpTestsSimpleConnectionManager {
+ TpBaseConnectionManager parent;
+
+ TpTestsSimpleConnectionManagerPrivate *priv;
+};
+
+GType tp_tests_simple_connection_manager_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER \
+ (tp_tests_simple_connection_manager_get_type ())
+#define TP_TESTS_SIMPLE_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER, \
+ simpleConnectionManager))
+#define TP_TESTS_SIMPLE_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER, \
+ TpTestsSimpleConnectionManagerClass))
+#define TP_TESTS_SIMPLE_IS_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER))
+#define TP_TESTS_SIMPLE_IS_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER))
+#define TP_TESTS_SIMPLE_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION_MANAGER, \
+ TpTestsSimpleConnectionManagerClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_CONNECTION_MANAGER_H__*/
diff --git a/qt4/tests/lib/glib/stream-tube-chan.c b/qt4/tests/lib/glib/stream-tube-chan.c
new file mode 100644
index 000000000..da5e406f2
--- /dev/null
+++ b/qt4/tests/lib/glib/stream-tube-chan.c
@@ -0,0 +1,778 @@
+/*
+ * stream-tube-chan.c - Simple stream tube channel
+ *
+ * Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "stream-tube-chan.h"
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/gnio-util.h>
+
+#include <gio/gunixsocketaddress.h>
+#include <gio/gunixconnection.h>
+
+#include <glib/gstdio.h>
+
+enum
+{
+ PROP_SERVICE = 1,
+ PROP_SUPPORTED_SOCKET_TYPES,
+ PROP_PARAMETERS,
+ PROP_STATE,
+};
+
+enum
+{
+ SIG_INCOMING_CONNECTION,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0, };
+
+
+struct _TpTestsStreamTubeChannelPrivate {
+ TpTubeChannelState state;
+ GHashTable *supported_socket_types;
+
+ /* Accepting side */
+ GSocketService *service;
+ GValue *access_control_param;
+
+ /* Offering side */
+ TpSocketAddressType address_type;
+ GValue *address;
+ gchar *unix_address;
+ guint connection_id;
+
+ TpSocketAccessControl access_control;
+
+ GHashTable *parameters;
+
+ gboolean close_on_accept;
+};
+
+static void
+destroy_socket_control_list (gpointer data)
+{
+ GArray *tab = data;
+ g_array_free (tab, TRUE);
+}
+
+static void
+create_supported_socket_types (TpTestsStreamTubeChannel *self)
+{
+ TpSocketAccessControl access_control;
+ GArray *unix_tab;
+
+ g_assert (self->priv->supported_socket_types == NULL);
+ self->priv->supported_socket_types = g_hash_table_new_full (NULL, NULL,
+ NULL, destroy_socket_control_list);
+
+ /* Socket_Address_Type_Unix */
+ unix_tab = g_array_sized_new (FALSE, FALSE, sizeof (TpSocketAccessControl),
+ 1);
+ access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
+ g_array_append_val (unix_tab, access_control);
+
+ g_hash_table_insert (self->priv->supported_socket_types,
+ GUINT_TO_POINTER (TP_SOCKET_ADDRESS_TYPE_UNIX), unix_tab);
+}
+
+static void
+tp_tests_stream_tube_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsStreamTubeChannel *self = (TpTestsStreamTubeChannel *) object;
+
+ switch (property_id)
+ {
+ case PROP_SERVICE:
+ g_value_set_string (value, "test-service");
+ break;
+
+ case PROP_SUPPORTED_SOCKET_TYPES:
+ g_value_set_boxed (value,
+ self->priv->supported_socket_types);
+ break;
+
+ case PROP_PARAMETERS:
+ g_value_set_boxed (value, self->priv->parameters);
+ break;
+
+ case PROP_STATE:
+ g_value_set_uint (value, self->priv->state);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tp_tests_stream_tube_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsStreamTubeChannel *self = (TpTestsStreamTubeChannel *) object;
+
+ switch (property_id)
+ {
+ case PROP_SUPPORTED_SOCKET_TYPES:
+ self->priv->supported_socket_types = g_value_dup_boxed (value);
+ break;
+
+ case PROP_PARAMETERS:
+ if (self->priv->parameters != NULL)
+ g_hash_table_destroy (self->priv->parameters);
+ self->priv->parameters = g_value_dup_boxed (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void stream_tube_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (TpTestsStreamTubeChannel,
+ tp_tests_stream_tube_channel,
+ TP_TYPE_BASE_CHANNEL,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAM_TUBE,
+ stream_tube_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_TUBE,
+ NULL);
+ )
+
+/* type definition stuff */
+
+static const char * tp_tests_stream_tube_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_TUBE,
+ NULL
+};
+
+static void
+tp_tests_stream_tube_channel_init (TpTestsStreamTubeChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ TP_TESTS_TYPE_STREAM_TUBE_CHANNEL, TpTestsStreamTubeChannelPrivate);
+}
+
+static GObject *
+constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *object =
+ G_OBJECT_CLASS (tp_tests_stream_tube_channel_parent_class)->constructor (
+ type, n_props, props);
+ TpTestsStreamTubeChannel *self = TP_TESTS_STREAM_TUBE_CHANNEL (object);
+
+ if (tp_base_channel_is_requested (TP_BASE_CHANNEL (self)))
+ {
+ self->priv->state = TP_TUBE_CHANNEL_STATE_NOT_OFFERED;
+ self->priv->parameters = tp_asv_new (NULL, NULL);
+ }
+ else
+ {
+ self->priv->state = TP_TUBE_CHANNEL_STATE_LOCAL_PENDING;
+ self->priv->parameters = tp_asv_new ("badger", G_TYPE_UINT, 42,
+ NULL);
+ }
+
+ if (self->priv->supported_socket_types == NULL)
+ create_supported_socket_types (self);
+
+ tp_base_channel_register (TP_BASE_CHANNEL (self));
+
+ return object;
+}
+
+static void
+dispose (GObject *object)
+{
+ TpTestsStreamTubeChannel *self = (TpTestsStreamTubeChannel *) object;
+
+ if (self->priv->service != NULL)
+ {
+ g_socket_service_stop (self->priv->service);
+ tp_clear_object (&self->priv->service);
+ }
+
+ tp_clear_pointer (&self->priv->address, tp_g_value_slice_free);
+ tp_clear_pointer (&self->priv->supported_socket_types, g_hash_table_unref);
+ tp_clear_pointer (&self->priv->access_control_param, tp_g_value_slice_free);
+
+ if (self->priv->unix_address != NULL)
+ g_unlink (self->priv->unix_address);
+
+ tp_clear_pointer (&self->priv->unix_address, g_free);
+
+ ((GObjectClass *) tp_tests_stream_tube_channel_parent_class)->dispose (
+ object);
+}
+
+static void
+channel_close (TpBaseChannel *channel)
+{
+ tp_base_channel_destroyed (channel);
+}
+
+static void
+fill_immutable_properties (TpBaseChannel *chan,
+ GHashTable *properties)
+{
+ TpBaseChannelClass *klass = TP_BASE_CHANNEL_CLASS (
+ tp_tests_stream_tube_channel_parent_class);
+
+ klass->fill_immutable_properties (chan, properties);
+
+ tp_dbus_properties_mixin_fill_properties_hash (
+ G_OBJECT (chan), properties,
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, "Service",
+ TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, "SupportedSocketTypes",
+ NULL);
+
+ if (!tp_base_channel_is_requested (chan))
+ {
+ /* Parameters is immutable only for incoming tubes */
+ tp_dbus_properties_mixin_fill_properties_hash (
+ G_OBJECT (chan), properties,
+ TP_IFACE_CHANNEL_INTERFACE_TUBE, "Parameters",
+ NULL);
+ }
+}
+
+static void
+tp_tests_stream_tube_channel_class_init (TpTestsStreamTubeChannelClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl stream_tube_props[] = {
+ { "Service", "service", NULL, },
+ { "SupportedSocketTypes", "supported-socket-types", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinPropImpl tube_props[] = {
+ { "Parameters", "parameters", NULL, },
+ { "State", "state", NULL, },
+ { NULL }
+ };
+
+ object_class->constructor = constructor;
+ object_class->get_property = tp_tests_stream_tube_channel_get_property;
+ object_class->set_property = tp_tests_stream_tube_channel_set_property;
+ object_class->dispose = dispose;
+
+ base_class->channel_type = TP_IFACE_CHANNEL_TYPE_STREAM_TUBE;
+ base_class->interfaces = tp_tests_stream_tube_channel_interfaces;
+ base_class->close = channel_close;
+ base_class->fill_immutable_properties = fill_immutable_properties;
+
+ /* base_class->target_handle_type is defined in subclasses */
+
+ tp_text_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsStreamTubeChannelClass, text_class));
+
+ param_spec = g_param_spec_string ("service", "service name",
+ "the service associated with this tube object.",
+ "",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SERVICE, param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "supported-socket-types", "Supported socket types",
+ "GHashTable containing supported socket types.",
+ TP_HASH_TYPE_SUPPORTED_SOCKET_MAP,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SUPPORTED_SOCKET_TYPES,
+ param_spec);
+
+ param_spec = g_param_spec_boxed (
+ "parameters", "Parameters",
+ "parameters of the tube",
+ TP_HASH_TYPE_STRING_VARIANT_MAP,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PARAMETERS,
+ param_spec);
+
+ param_spec = g_param_spec_uint (
+ "state", "TpTubeState",
+ "state of the tube",
+ 0, NUM_TP_TUBE_CHANNEL_STATES - 1, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATE,
+ param_spec);
+
+ signals[SIG_INCOMING_CONNECTION] = g_signal_new ("incoming-connection",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, G_TYPE_IO_STREAM);
+
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TP_IFACE_QUARK_CHANNEL_TYPE_STREAM_TUBE,
+ tp_dbus_properties_mixin_getter_gobject_properties, NULL,
+ stream_tube_props);
+
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TP_IFACE_QUARK_CHANNEL_INTERFACE_TUBE,
+ tp_dbus_properties_mixin_getter_gobject_properties, NULL,
+ tube_props);
+
+ g_type_class_add_private (object_class,
+ sizeof (TpTestsStreamTubeChannelPrivate));
+}
+
+static void
+change_state (TpTestsStreamTubeChannel *self,
+ TpTubeChannelState state)
+{
+ self->priv->state = state;
+
+ tp_svc_channel_interface_tube_emit_tube_channel_state_changed (self, state);
+}
+
+/* Return the address of the socket which has been shared over the tube */
+GSocketAddress *
+tp_tests_stream_tube_channel_get_server_address (TpTestsStreamTubeChannel *self)
+{
+ return tp_g_socket_address_from_variant (self->priv->address_type,
+ self->priv->address, NULL);
+}
+
+static gboolean
+check_address_type (TpTestsStreamTubeChannel *self,
+ TpSocketAddressType address_type,
+ TpSocketAccessControl access_control)
+{
+ GArray *arr;
+ guint i;
+
+ arr = g_hash_table_lookup (self->priv->supported_socket_types,
+ GUINT_TO_POINTER (address_type));
+ if (arr == NULL)
+ return FALSE;
+
+ for (i = 0; i < arr->len; i++)
+ {
+ if (g_array_index (arr, TpSocketAccessControl, i) == access_control)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+stream_tube_offer (TpSvcChannelTypeStreamTube *iface,
+ guint address_type,
+ const GValue *address,
+ guint access_control,
+ GHashTable *parameters,
+ DBusGMethodInvocation *context)
+{
+ TpTestsStreamTubeChannel *self = (TpTestsStreamTubeChannel *) iface;
+ GError *error = NULL;
+
+ if (self->priv->state != TP_TUBE_CHANNEL_STATE_NOT_OFFERED)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not in the not offered state");
+ goto fail;
+ }
+
+ if (!check_address_type (self, address_type, access_control))
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Address type not supported with this access control");
+ goto fail;
+ }
+
+ self->priv->address_type = address_type;
+ self->priv->address = tp_g_value_slice_dup (address);
+ self->priv->access_control = access_control;
+
+ g_object_set (self, "parameters", parameters, NULL);
+
+ change_state (self, TP_TUBE_CHANNEL_STATE_REMOTE_PENDING);
+
+ tp_svc_channel_type_stream_tube_return_from_offer (context);
+ return;
+
+fail:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+service_incoming_cb (GSocketService *service,
+ GSocketConnection *connection,
+ GObject *source_object,
+ gpointer user_data)
+{
+ TpTestsStreamTubeChannel *self = user_data;
+ GError *error = NULL;
+
+ if (self->priv->access_control == TP_SOCKET_ACCESS_CONTROL_CREDENTIALS)
+ {
+ GCredentials *creds;
+ guchar byte;
+
+ /* FIXME: we should an async version of this API (bgo #629503) */
+ creds = tp_unix_connection_receive_credentials_with_byte (
+ connection, &byte, NULL, &error);
+ g_assert_no_error (error);
+
+ g_assert_cmpuint (byte, ==,
+ g_value_get_uchar (self->priv->access_control_param));
+ g_object_unref (creds);
+ }
+ else if (self->priv->access_control == TP_SOCKET_ACCESS_CONTROL_PORT)
+ {
+ GSocketAddress *remote_addr;
+ gchar *host, *remote_host;
+ guint port, remote_port;
+
+ dbus_g_type_struct_get (self->priv->access_control_param,
+ 0, &host,
+ 1, &port,
+ G_MAXUINT);
+
+ remote_addr = g_socket_connection_get_remote_address (connection, &error);
+ g_assert_no_error (error);
+
+ remote_host = g_inet_address_to_string (
+ g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (remote_addr)));
+ remote_port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (remote_addr));
+
+ g_assert_cmpuint (remote_port, ==, port);
+ g_assert_cmpstr (remote_host, ==, host);
+
+ g_free (host);
+ g_free (remote_host);
+ g_object_unref (remote_addr);
+ }
+
+ tp_svc_channel_type_stream_tube_emit_new_local_connection (self,
+ self->priv->connection_id);
+
+ self->priv->connection_id++;
+
+ g_signal_emit (self, signals[SIG_INCOMING_CONNECTION], 0, connection);
+}
+
+static GValue *
+create_local_socket (TpTestsStreamTubeChannel *self,
+ TpSocketAddressType address_type,
+ TpSocketAccessControl access_control,
+ GError **error)
+{
+ gboolean success;
+ GSocketAddress *address, *effective_address;
+ GValue *address_gvalue;
+
+ switch (access_control)
+ {
+ case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
+ case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
+ case TP_SOCKET_ACCESS_CONTROL_PORT:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ switch (address_type)
+ {
+ case TP_SOCKET_ADDRESS_TYPE_UNIX:
+ {
+ address = g_unix_socket_address_new (tmpnam (NULL));
+ break;
+ }
+
+ case TP_SOCKET_ADDRESS_TYPE_IPV4:
+ case TP_SOCKET_ADDRESS_TYPE_IPV6:
+ {
+ GInetAddress *localhost;
+
+ localhost = g_inet_address_new_loopback (
+ address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ?
+ G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
+ address = g_inet_socket_address_new (localhost, 0);
+
+ g_object_unref (localhost);
+ break;
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ self->priv->service = g_socket_service_new ();
+
+ success = g_socket_listener_add_address (
+ G_SOCKET_LISTENER (self->priv->service),
+ address, G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL, &effective_address, NULL);
+ g_assert (success);
+
+ tp_g_signal_connect_object (self->priv->service, "incoming",
+ G_CALLBACK (service_incoming_cb), self, 0);
+
+ switch (address_type)
+ {
+ case TP_SOCKET_ADDRESS_TYPE_UNIX:
+ self->priv->unix_address = g_strdup (g_unix_socket_address_get_path (
+ G_UNIX_SOCKET_ADDRESS (effective_address)));
+ address_gvalue = tp_g_value_slice_new_bytes (
+ g_unix_socket_address_get_path_len (
+ G_UNIX_SOCKET_ADDRESS (effective_address)),
+ g_unix_socket_address_get_path (
+ G_UNIX_SOCKET_ADDRESS (effective_address)));
+ break;
+
+ case TP_SOCKET_ADDRESS_TYPE_IPV4:
+ case TP_SOCKET_ADDRESS_TYPE_IPV6:
+ address_gvalue = tp_g_value_slice_new_take_boxed (
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
+
+ dbus_g_type_struct_set (address_gvalue,
+ 0, address_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ?
+ "127.0.0.1" : "::1",
+ 1, g_inet_socket_address_get_port (
+ G_INET_SOCKET_ADDRESS (effective_address)),
+ G_MAXUINT);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+
+ g_object_unref (address);
+ g_object_unref (effective_address);
+ return address_gvalue;
+}
+
+static void
+stream_tube_accept (TpSvcChannelTypeStreamTube *iface,
+ TpSocketAddressType address_type,
+ TpSocketAccessControl access_control,
+ const GValue *access_control_param,
+ DBusGMethodInvocation *context)
+{
+ TpTestsStreamTubeChannel *self = (TpTestsStreamTubeChannel *) iface;
+ GError *error = NULL;
+ GValue *address;
+
+ if (self->priv->state != TP_TUBE_CHANNEL_STATE_LOCAL_PENDING)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Tube is not in the local pending state");
+ goto fail;
+ }
+
+ if (!check_address_type (self, address_type, access_control))
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Address type not supported with this access control");
+ goto fail;
+ }
+
+ if (self->priv->close_on_accept)
+ {
+ tp_base_channel_close (TP_BASE_CHANNEL (self));
+ return;
+ }
+
+ address = create_local_socket (self, address_type, access_control, &error);
+
+ self->priv->access_control = access_control;
+ self->priv->access_control_param = tp_g_value_slice_dup (
+ access_control_param);
+
+ change_state (self, TP_TUBE_CHANNEL_STATE_OPEN);
+
+ tp_svc_channel_type_stream_tube_return_from_accept (context, address);
+
+ tp_g_value_slice_free (address);
+ return;
+
+fail:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+stream_tube_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeStreamTubeClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_stream_tube_implement_##x (klass, stream_tube_##x)
+ IMPLEMENT(offer);
+ IMPLEMENT(accept);
+#undef IMPLEMENT
+}
+
+/* Called to emulate a peer connecting to an offered tube */
+void
+tp_tests_stream_tube_channel_peer_connected (TpTestsStreamTubeChannel *self,
+ GIOStream *stream,
+ TpHandle handle)
+{
+ GValue *connection_param;
+
+ if (self->priv->state == TP_TUBE_CHANNEL_STATE_REMOTE_PENDING)
+ change_state (self, TP_TUBE_CHANNEL_STATE_OPEN);
+
+ g_assert (self->priv->state == TP_TUBE_CHANNEL_STATE_OPEN);
+
+ switch (self->priv->access_control)
+ {
+ case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
+ connection_param = tp_g_value_slice_new_static_string ("dummy");
+ break;
+
+ case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
+ {
+ GError *error = NULL;
+ guchar byte = g_random_int_range (0, G_MAXUINT8);
+
+ /* FIXME: we should an async version of this API (bgo #629503) */
+ tp_unix_connection_send_credentials_with_byte (
+ G_SOCKET_CONNECTION (stream), byte, NULL, &error);
+ g_assert_no_error (error);
+
+ connection_param = tp_g_value_slice_new_byte (byte);
+ }
+ break;
+
+ case TP_SOCKET_ACCESS_CONTROL_PORT:
+ {
+ GSocketAddress *addr;
+ GError *error = NULL;
+
+ addr = g_socket_connection_get_local_address (
+ G_SOCKET_CONNECTION (stream), &error);
+ g_assert_no_error (error);
+
+ connection_param = tp_g_value_slice_new_take_boxed (
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4,
+ dbus_g_type_specialized_construct (
+ TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4));
+
+ dbus_g_type_struct_set (connection_param,
+ 0, "badger",
+ 1, g_inet_socket_address_get_port (
+ G_INET_SOCKET_ADDRESS (addr)),
+ G_MAXUINT);
+
+ g_object_unref (addr);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ tp_svc_channel_type_stream_tube_emit_new_remote_connection (self, handle,
+ connection_param, self->priv->connection_id);
+
+ self->priv->connection_id++;
+
+ tp_g_value_slice_free (connection_param);
+}
+
+/* Called to emulate a peer connecting to an offered tube */
+void
+tp_tests_stream_tube_channel_peer_connected_no_stream (TpTestsStreamTubeChannel *self,
+ const GValue *connection_param,
+ TpHandle handle)
+{
+ if (self->priv->state == TP_TUBE_CHANNEL_STATE_REMOTE_PENDING)
+ change_state (self, TP_TUBE_CHANNEL_STATE_OPEN);
+
+ g_assert (self->priv->state == TP_TUBE_CHANNEL_STATE_OPEN);
+
+ tp_svc_channel_type_stream_tube_emit_new_remote_connection (self, handle,
+ connection_param, self->priv->connection_id);
+
+ self->priv->connection_id++;
+}
+
+void
+tp_tests_stream_tube_channel_last_connection_disconnected (
+ TpTestsStreamTubeChannel *self,
+ const gchar *error)
+{
+ tp_svc_channel_type_stream_tube_emit_connection_closed (self,
+ self->priv->connection_id - 1, error, "kaboum");
+}
+
+void
+tp_tests_stream_tube_channel_set_close_on_accept (
+ TpTestsStreamTubeChannel *self,
+ gboolean close_on_accept)
+{
+ self->priv->close_on_accept = close_on_accept;
+}
+
+/* Contact Stream Tube */
+
+G_DEFINE_TYPE (TpTestsContactStreamTubeChannel,
+ tp_tests_contact_stream_tube_channel,
+ TP_TESTS_TYPE_STREAM_TUBE_CHANNEL)
+
+static void
+tp_tests_contact_stream_tube_channel_init (
+ TpTestsContactStreamTubeChannel *self)
+{
+}
+
+static void
+tp_tests_contact_stream_tube_channel_class_init (
+ TpTestsContactStreamTubeChannelClass *klass)
+{
+ TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass);
+
+ base_class->target_handle_type = TP_HANDLE_TYPE_CONTACT;
+}
+
+/* Room Stream Tube */
+
+G_DEFINE_TYPE (TpTestsRoomStreamTubeChannel,
+ tp_tests_room_stream_tube_channel,
+ TP_TESTS_TYPE_STREAM_TUBE_CHANNEL)
+
+static void
+tp_tests_room_stream_tube_channel_init (
+ TpTestsRoomStreamTubeChannel *self)
+{
+}
+
+static void
+tp_tests_room_stream_tube_channel_class_init (
+ TpTestsRoomStreamTubeChannelClass *klass)
+{
+ TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass);
+
+ base_class->target_handle_type = TP_HANDLE_TYPE_ROOM;
+}
diff --git a/qt4/tests/lib/glib/stream-tube-chan.h b/qt4/tests/lib/glib/stream-tube-chan.h
new file mode 100644
index 000000000..3702c6f21
--- /dev/null
+++ b/qt4/tests/lib/glib/stream-tube-chan.h
@@ -0,0 +1,141 @@
+/*
+ * stream-tube-chan.h - Simple stream tube channel
+ *
+ * Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_STREAM_TUBE_CHAN_H__
+#define __TP_STREAM_TUBE_CHAN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-channel.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/text-mixin.h>
+
+G_BEGIN_DECLS
+
+/* Base Class */
+typedef struct _TpTestsStreamTubeChannel TpTestsStreamTubeChannel;
+typedef struct _TpTestsStreamTubeChannelClass TpTestsStreamTubeChannelClass;
+typedef struct _TpTestsStreamTubeChannelPrivate TpTestsStreamTubeChannelPrivate;
+
+GType tp_tests_stream_tube_channel_get_type (void);
+
+#define TP_TESTS_TYPE_STREAM_TUBE_CHANNEL \
+ (tp_tests_stream_tube_channel_get_type ())
+#define TP_TESTS_STREAM_TUBE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_STREAM_TUBE_CHANNEL, \
+ TpTestsStreamTubeChannel))
+#define TP_TESTS_STREAM_TUBE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_STREAM_TUBE_CHANNEL, \
+ TpTestsStreamTubeChannelClass))
+#define TP_TESTS_IS_STREAM_TUBE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_STREAM_TUBE_CHANNEL))
+#define TP_TESTS_IS_STREAM_TUBE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_STREAM_TUBE_CHANNEL))
+#define TP_TESTS_STREAM_TUBE_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_STREAM_TUBE_CHANNEL, \
+ TpTestsStreamTubeChannelClass))
+
+struct _TpTestsStreamTubeChannelClass {
+ TpBaseChannelClass parent_class;
+ TpTextMixinClass text_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _TpTestsStreamTubeChannel {
+ TpBaseChannel parent;
+ TpTextMixin text;
+
+ TpTestsStreamTubeChannelPrivate *priv;
+};
+
+GSocketAddress * tp_tests_stream_tube_channel_get_server_address (
+ TpTestsStreamTubeChannel *self);
+
+void tp_tests_stream_tube_channel_peer_connected (
+ TpTestsStreamTubeChannel *self,
+ GIOStream *stream,
+ TpHandle handle);
+
+void tp_tests_stream_tube_channel_peer_connected_no_stream (TpTestsStreamTubeChannel *self,
+ const GValue *connection_param,
+ TpHandle handle);
+
+void tp_tests_stream_tube_channel_last_connection_disconnected (
+ TpTestsStreamTubeChannel *self,
+ const gchar *error);
+
+void tp_tests_stream_tube_channel_set_close_on_accept (
+ TpTestsStreamTubeChannel *self,
+ gboolean close_on_accept);
+
+/* Contact Stream Tube */
+
+typedef struct _TpTestsContactStreamTubeChannel TpTestsContactStreamTubeChannel;
+typedef struct _TpTestsContactStreamTubeChannelClass TpTestsContactStreamTubeChannelClass;
+
+GType tp_tests_contact_stream_tube_channel_get_type (void);
+
+#define TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL \
+ (tp_tests_contact_stream_tube_channel_get_type ())
+#define TP_TESTS_CONTACT_STREAM_TUBE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL, \
+ TpTestsContactStreamTubeChannel))
+#define TP_TESTS_CONTACT_STREAM_TUBE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL, \
+ TpTestsContactStreamTubeChannelClass))
+#define TP_TESTS_IS_CONTACT_STREAM_TUBE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL))
+#define TP_TESTS_IS_CONTACT_STREAM_TUBE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL))
+#define TP_TESTS_CONTACT_STREAM_TUBE_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONTACT_STREAM_TUBE_CHANNEL, \
+ TpTestsContactStreamTubeChannelClass))
+
+struct _TpTestsContactStreamTubeChannelClass {
+ TpTestsStreamTubeChannelClass parent_class;
+};
+
+struct _TpTestsContactStreamTubeChannel {
+ TpTestsStreamTubeChannel parent;
+};
+
+/* Room Stream Tube */
+
+typedef struct _TpTestsRoomStreamTubeChannel TpTestsRoomStreamTubeChannel;
+typedef struct _TpTestsRoomStreamTubeChannelClass TpTestsRoomStreamTubeChannelClass;
+
+GType tp_tests_room_stream_tube_channel_get_type (void);
+
+#define TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL \
+ (tp_tests_room_stream_tube_channel_get_type ())
+#define TP_TESTS_ROOM_STREAM_TUBE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL, \
+ TpTestsRoomStreamTubeChannel))
+#define TP_TESTS_ROOM_STREAM_TUBE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL, \
+ TpTestsRoomStreamTubeChannelClass))
+#define TP_TESTS_IS_ROOM_STREAM_TUBE_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL))
+#define TP_TESTS_IS_ROOM_STREAM_TUBE_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL))
+#define TP_TESTS_ROOM_STREAM_TUBE_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_ROOM_STREAM_TUBE_CHANNEL, \
+ TpTestsRoomStreamTubeChannelClass))
+
+struct _TpTestsRoomStreamTubeChannelClass {
+ TpTestsStreamTubeChannelClass parent_class;
+};
+
+struct _TpTestsRoomStreamTubeChannel {
+ TpTestsStreamTubeChannel parent;
+};
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_STREAM_TUBE_CHAN_H__ */
diff --git a/qt4/tests/lib/glib/textchan-group.c b/qt4/tests/lib/glib/textchan-group.c
new file mode 100644
index 000000000..6a6f5d7f3
--- /dev/null
+++ b/qt4/tests/lib/glib/textchan-group.c
@@ -0,0 +1,439 @@
+/*
+ * a stub anonymous MUC
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "textchan-group.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+static void text_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsTextChannelGroup,
+ tp_tests_text_channel_group,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init))
+
+static const char *text_channel_group_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+/* type definition stuff */
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_DETAILED,
+ PROP_PROPERTIES,
+ N_PROPS
+};
+
+struct _TpTestsTextChannelGroupPrivate
+{
+ gchar *object_path;
+
+ gboolean detailed;
+ gboolean properties;
+
+ gboolean closed;
+ gboolean disposed;
+};
+
+
+static gboolean
+add_member (GObject *obj,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (obj);
+ TpIntSet *add = tp_intset_new ();
+
+ tp_intset_add (add, handle);
+ tp_group_mixin_change_members (obj, message, add, NULL, NULL, NULL,
+ self->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (add);
+
+ return TRUE;
+}
+
+static void
+tp_tests_text_channel_group_init (TpTestsTextChannelGroup *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_TEXT_CHANNEL_GROUP, TpTestsTextChannelGroupPrivate);
+}
+
+static GObject *
+constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *object =
+ G_OBJECT_CLASS (tp_tests_text_channel_group_parent_class)->constructor (type,
+ n_props, props);
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->conn, TP_HANDLE_TYPE_CONTACT);
+ TpChannelGroupFlags flags = 0;
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->conn),
+ self->priv->object_path, self);
+
+ tp_text_mixin_init (object, G_STRUCT_OFFSET (TpTestsTextChannelGroup, text),
+ contact_repo);
+
+ tp_text_mixin_set_message_types (object,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+ G_MAXUINT);
+
+ if (self->priv->detailed)
+ flags |= TP_CHANNEL_GROUP_FLAG_MEMBERS_CHANGED_DETAILED;
+
+ if (self->priv->properties)
+ flags |= TP_CHANNEL_GROUP_FLAG_PROPERTIES;
+
+ tp_group_mixin_init (object, G_STRUCT_OFFSET (TpTestsTextChannelGroup, group),
+ contact_repo, self->conn->self_handle);
+ tp_group_mixin_change_flags (object, flags, 0);
+
+ return object;
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_NONE);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+ case PROP_TARGET_ID:
+ g_value_set_static_string (value, "");
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->conn->self_handle);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->conn->self_handle));
+ }
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, text_channel_group_interfaces);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->conn);
+ break;
+ case PROP_DETAILED:
+ g_value_set_boolean (value, self->priv->detailed);
+ break;
+ case PROP_PROPERTIES:
+ g_value_set_boolean (value, self->priv->properties);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->conn = g_value_get_object (value);
+ break;
+ case PROP_DETAILED:
+ self->priv->detailed = g_value_get_boolean (value);
+ break;
+ case PROP_PROPERTIES:
+ self->priv->properties = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+
+ if (!self->priv->closed)
+ {
+ tp_svc_channel_emit_closed (self);
+ }
+
+ ((GObjectClass *) tp_tests_text_channel_group_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (object);
+
+ g_free (self->priv->object_path);
+
+ tp_text_mixin_finalize (object);
+ tp_group_mixin_finalize (object);
+
+ ((GObjectClass *) tp_tests_text_channel_group_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_text_channel_group_class_init (TpTestsTextChannelGroupClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (klass, sizeof (TpTestsTextChannelGroupPrivate));
+
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "Always the empty string on this channel",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_boolean ("detailed",
+ "Has the Members_Changed_Detailed flag?",
+ "True if the Members_Changed_Detailed group flag should be set",
+ TRUE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DETAILED, param_spec);
+
+ param_spec = g_param_spec_boolean ("properties",
+ "Has the Properties flag?",
+ "True if the Properties group flag should be set",
+ TRUE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PROPERTIES, param_spec);
+
+ tp_text_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsTextChannelGroupClass, text_class));
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsTextChannelGroupClass, group_class), add_member,
+ NULL);
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsTextChannelGroupClass, dbus_properties_class));
+
+ tp_group_mixin_init_dbus_properties (object_class);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsTextChannelGroup *self = TP_TESTS_TEXT_CHANNEL_GROUP (iface);
+
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_NONE, 0);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ text_channel_group_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+text_send (TpSvcChannelTypeText *iface,
+ guint type,
+ const gchar *text,
+ DBusGMethodInvocation *context)
+{
+ /* silently swallow the message */
+ tp_svc_channel_type_text_return_from_send (context);
+}
+
+static void
+text_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeTextClass *klass = iface;
+
+ tp_text_mixin_iface_init (iface, data);
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass, text_##x)
+ IMPLEMENT (send);
+#undef IMPLEMENT
+}
diff --git a/qt4/tests/lib/glib/textchan-group.h b/qt4/tests/lib/glib/textchan-group.h
new file mode 100644
index 000000000..61e1c0c66
--- /dev/null
+++ b/qt4/tests/lib/glib/textchan-group.h
@@ -0,0 +1,65 @@
+/*
+ * a stub anonymous MUC
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TEST_TEXT_CHANNEL_GROUP_H__
+#define __TEST_TEXT_CHANNEL_GROUP_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+#include <telepathy-glib/text-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsTextChannelGroup TpTestsTextChannelGroup;
+typedef struct _TpTestsTextChannelGroupClass TpTestsTextChannelGroupClass;
+typedef struct _TpTestsTextChannelGroupPrivate TpTestsTextChannelGroupPrivate;
+
+GType tp_tests_text_channel_group_get_type (void);
+
+#define TP_TESTS_TYPE_TEXT_CHANNEL_GROUP \
+ (tp_tests_text_channel_group_get_type ())
+#define TP_TESTS_TEXT_CHANNEL_GROUP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_GROUP, \
+ TpTestsTextChannelGroup))
+#define TP_TESTS_TEXT_CHANNEL_GROUP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_TEXT_CHANNEL_GROUP, \
+ TpTestsTextChannelGroupClass))
+#define TEST_IS_TEXT_CHANNEL_GROUP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_GROUP))
+#define TEST_IS_TEXT_CHANNEL_GROUP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_TEXT_CHANNEL_GROUP))
+#define TP_TESTS_TEXT_CHANNEL_GROUP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_GROUP, \
+ TpTestsTextChannelGroupClass))
+
+struct _TpTestsTextChannelGroupClass {
+ GObjectClass parent_class;
+
+ TpTextMixinClass text_class;
+ TpGroupMixinClass group_class;
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _TpTestsTextChannelGroup {
+ GObject parent;
+
+ TpBaseConnection *conn;
+
+ TpTextMixin text;
+ TpGroupMixin group;
+
+ TpTestsTextChannelGroupPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif /* #ifndef __TEST_TEXT_CHANNEL_GROUP_H__ */
diff --git a/qt4/tests/lib/glib/textchan-null.c b/qt4/tests/lib/glib/textchan-null.c
new file mode 100644
index 000000000..793b75b99
--- /dev/null
+++ b/qt4/tests/lib/glib/textchan-null.c
@@ -0,0 +1,582 @@
+/*
+ * /dev/null as a text channel
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "textchan-null.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+static void text_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsTextChannelNull,
+ tp_tests_text_channel_null,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL))
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsPropsTextChannel,
+ tp_tests_props_text_channel,
+ TP_TESTS_TYPE_TEXT_CHANNEL_NULL,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init))
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsPropsGroupTextChannel,
+ tp_tests_props_group_text_channel,
+ TP_TESTS_TYPE_PROPS_TEXT_CHANNEL,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init))
+
+static const char *tp_tests_text_channel_null_interfaces[] = { NULL };
+
+/* type definition stuff */
+
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_CONNECTION,
+ PROP_INTERFACES,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ N_PROPS
+};
+
+struct _TpTestsTextChannelNullPrivate
+{
+ TpBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+
+ unsigned closed:1;
+ unsigned disposed:1;
+};
+
+static void
+tp_tests_text_channel_null_init (TpTestsTextChannelNull *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ TP_TESTS_TYPE_TEXT_CHANNEL_NULL, TpTestsTextChannelNullPrivate);
+}
+
+static void
+tp_tests_props_text_channel_init (TpTestsPropsTextChannel *self)
+{
+ self->dbus_property_interfaces_retrieved = g_hash_table_new (NULL, NULL);
+}
+
+static GObject *
+constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *object =
+ G_OBJECT_CLASS (tp_tests_text_channel_null_parent_class)->constructor (type,
+ n_props, props);
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_repo, self->priv->handle);
+
+ tp_dbus_daemon_register_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn),
+ self->priv->object_path, self);
+
+ tp_text_mixin_init (object, G_STRUCT_OFFSET (TpTestsTextChannelNull, text),
+ contact_repo);
+
+ tp_text_mixin_set_message_types (object,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+ G_MAXUINT);
+
+ return object;
+}
+
+static void
+get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+ TpTestsTextChannelNullClass *klass = TP_TESTS_TEXT_CHANNEL_NULL_GET_CLASS (self);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, self->priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, self->priv->handle);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->handle));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, self->priv->conn->self_handle);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value,
+ tp_handle_inspect (contact_repo, self->priv->conn->self_handle));
+ }
+ break;
+ case PROP_INTERFACES:
+ g_value_set_boxed (value, klass->interfaces);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+
+ switch (property_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_free (self->priv->object_path);
+ self->priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * contact repo yet - instead we ref it in the constructor.
+ */
+ self->priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ case PROP_CHANNEL_TYPE:
+ /* these properties are writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ self->priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+void
+tp_tests_text_channel_null_close (TpTestsTextChannelNull *self)
+{
+ if (!self->priv->closed)
+ {
+ self->priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ tp_dbus_daemon_unregister_object (
+ tp_base_connection_get_dbus_daemon (self->priv->conn), self);
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+
+ if (self->priv->disposed)
+ return;
+
+ self->priv->disposed = TRUE;
+ tp_tests_text_channel_null_close (self);
+
+ ((GObjectClass *) tp_tests_text_channel_null_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+ (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, self->priv->handle);
+ g_free (self->priv->object_path);
+
+ tp_text_mixin_finalize (object);
+
+ ((GObjectClass *) tp_tests_text_channel_null_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_text_channel_null_class_init (TpTestsTextChannelNullClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (TpTestsTextChannelNullPrivate));
+
+ klass->interfaces = tp_tests_text_channel_null_interfaces;
+
+ object_class->constructor = constructor;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+ "Connection object that owns this channel",
+ TP_TYPE_BASE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+ param_spec = g_param_spec_string ("target-id", "Peer's ID",
+ "The string obtained by inspecting the target handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact who initiated the channel",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+ "The string obtained by inspecting the initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ tp_text_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsTextChannelNullClass, text_class));
+}
+
+static void
+tp_tests_props_text_channel_getter_gobject_properties (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ TpTestsPropsTextChannel *self = TP_TESTS_PROPS_TEXT_CHANNEL (object);
+
+ g_hash_table_insert (self->dbus_property_interfaces_retrieved,
+ GUINT_TO_POINTER (interface), GUINT_TO_POINTER (interface));
+
+ tp_dbus_properties_mixin_getter_gobject_properties (object, interface, name,
+ value, getter_data);
+}
+
+static void
+props_finalize (GObject *object)
+{
+ TpTestsPropsTextChannel *self = TP_TESTS_PROPS_TEXT_CHANNEL (object);
+
+ g_hash_table_unref (self->dbus_property_interfaces_retrieved);
+
+ ((GObjectClass *) tp_tests_props_text_channel_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_props_text_channel_class_init (TpTestsPropsTextChannelClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "TargetID", "target-id", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_tests_props_text_channel_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ object_class->finalize = props_finalize;
+
+ klass->dbus_properties_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsPropsTextChannelClass, dbus_properties_class));
+}
+
+static const char *tp_tests_props_group_text_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ NULL
+};
+
+static void
+tp_tests_props_group_text_channel_init (TpTestsPropsGroupTextChannel *self)
+{
+}
+
+static void
+group_constructed (GObject *self)
+{
+ TpBaseConnection *conn = TP_TESTS_TEXT_CHANNEL_NULL (self)->priv->conn;
+ void (*chain_up) (GObject *) =
+ ((GObjectClass *) tp_tests_props_group_text_channel_parent_class)->constructed;
+
+ if (chain_up != NULL)
+ chain_up (self);
+
+ tp_group_mixin_init (self,
+ G_STRUCT_OFFSET (TpTestsPropsGroupTextChannel, group),
+ tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT),
+ tp_base_connection_get_self_handle (conn));
+ tp_group_mixin_change_flags (self, TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+}
+
+static void
+group_finalize (GObject *self)
+{
+ tp_group_mixin_finalize (self);
+
+ ((GObjectClass *) tp_tests_props_group_text_channel_parent_class)->finalize (self);
+}
+
+static gboolean
+dummy_add_remove_member (GObject *obj,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ return TRUE;
+}
+
+static void
+group_iface_props_getter (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ TpTestsPropsTextChannel *self = TP_TESTS_PROPS_TEXT_CHANNEL (object);
+
+ g_hash_table_insert (self->dbus_property_interfaces_retrieved,
+ GUINT_TO_POINTER (interface), GUINT_TO_POINTER (interface));
+
+ tp_group_mixin_get_dbus_property (object, interface, name, value, getter_data);
+}
+
+static void
+tp_tests_props_group_text_channel_class_init (TpTestsPropsGroupTextChannelClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ TpTestsTextChannelNullClass *null_class = (TpTestsTextChannelNullClass *) klass;
+ static TpDBusPropertiesMixinPropImpl group_props[] = {
+ { "GroupFlags", NULL, NULL },
+ { "HandleOwners", NULL, NULL },
+ { "LocalPendingMembers", NULL, NULL },
+ { "Members", NULL, NULL },
+ { "RemotePendingMembers", NULL, NULL },
+ { "SelfHandle", NULL, NULL },
+ { NULL }
+ };
+
+ null_class->interfaces = tp_tests_props_group_text_channel_interfaces;
+
+ object_class->constructed = group_constructed;
+ object_class->finalize = group_finalize;
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (TpTestsPropsGroupTextChannelClass, group_class),
+ dummy_add_remove_member,
+ dummy_add_remove_member);
+ tp_dbus_properties_mixin_implement_interface (object_class,
+ TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP, group_iface_props_getter, NULL,
+ group_props);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+ tp_tests_text_channel_null_close (self);
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+ self->get_channel_type_called++;
+
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+ self->get_handle_called++;
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+ TpTestsTextChannelNullClass *klass = TP_TESTS_TEXT_CHANNEL_NULL_GET_CLASS (self);
+
+ self->get_interfaces_called++;
+
+ tp_svc_channel_return_from_get_interfaces (context,
+ klass->interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+text_send (TpSvcChannelTypeText *iface,
+ guint type,
+ const gchar *text,
+ DBusGMethodInvocation *context)
+{
+ /* silently swallow the message */
+ tp_svc_channel_type_text_return_from_send (context);
+}
+
+static void
+text_iface_init (gpointer iface,
+ gpointer data)
+{
+ TpSvcChannelTypeTextClass *klass = iface;
+
+ tp_text_mixin_iface_init (iface, data);
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass, text_##x)
+ IMPLEMENT (send);
+#undef IMPLEMENT
+}
+
+GHashTable *
+tp_tests_text_channel_get_props (TpTestsTextChannelNull *self)
+{
+ GHashTable *props;
+ TpHandleType handle_type;
+ TpHandle handle;
+ gchar *target_id;
+ gboolean requested;
+ TpHandle initiator_handle;
+ gchar *initiator_id;
+ GStrv interfaces;
+
+ g_object_get (self,
+ "handle-type", &handle_type,
+ "handle", &handle,
+ "target-id", &target_id,
+ "requested", &requested,
+ "initiator-handle", &initiator_handle,
+ "initiator-id", &initiator_id,
+ "interfaces", &interfaces,
+ NULL);
+
+ props = tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, handle_type,
+ TP_PROP_CHANNEL_TARGET_HANDLE, G_TYPE_UINT, handle,
+ TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, target_id,
+ TP_PROP_CHANNEL_REQUESTED, G_TYPE_BOOLEAN, requested,
+ TP_PROP_CHANNEL_INITIATOR_HANDLE, G_TYPE_UINT, initiator_handle,
+ TP_PROP_CHANNEL_INITIATOR_ID, G_TYPE_STRING, initiator_id,
+ TP_PROP_CHANNEL_INTERFACES, G_TYPE_STRV, interfaces,
+ NULL);
+
+ g_free (target_id);
+ g_free (initiator_id);
+ g_strfreev (interfaces);
+ return props;
+}
diff --git a/qt4/tests/lib/glib/textchan-null.h b/qt4/tests/lib/glib/textchan-null.h
new file mode 100644
index 000000000..5be9c00b0
--- /dev/null
+++ b/qt4/tests/lib/glib/textchan-null.h
@@ -0,0 +1,139 @@
+/*
+ * /dev/null as a text channel
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_TEXT_CHANNEL_NULL_H__
+#define __TP_TESTS_TEXT_CHANNEL_NULL_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/text-mixin.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsTextChannelNull TpTestsTextChannelNull;
+typedef struct _TpTestsTextChannelNullClass TpTestsTextChannelNullClass;
+typedef struct _TpTestsTextChannelNullPrivate TpTestsTextChannelNullPrivate;
+
+GType tp_tests_text_channel_null_get_type (void);
+
+#define TP_TESTS_TYPE_TEXT_CHANNEL_NULL \
+ (tp_tests_text_channel_null_get_type ())
+#define TP_TESTS_TEXT_CHANNEL_NULL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_NULL, \
+ TpTestsTextChannelNull))
+#define TP_TESTS_TEXT_CHANNEL_NULL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_TEXT_CHANNEL_NULL, \
+ TpTestsTextChannelNullClass))
+#define TP_TESTS_IS_TEXT_CHANNEL_NULL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_NULL))
+#define TP_TESTS_IS_TEXT_CHANNEL_NULL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_TEXT_CHANNEL_NULL))
+#define TP_TESTS_TEXT_CHANNEL_NULL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_NULL, \
+ TpTestsTextChannelNullClass))
+
+struct _TpTestsTextChannelNullClass {
+ GObjectClass parent_class;
+
+ const gchar **interfaces;
+
+ TpTextMixinClass text_class;
+};
+
+struct _TpTestsTextChannelNull {
+ GObject parent;
+ TpTextMixin text;
+
+ guint get_handle_called;
+ guint get_interfaces_called;
+ guint get_channel_type_called;
+
+ TpTestsTextChannelNullPrivate *priv;
+};
+
+/* Subclass with D-Bus properties */
+
+typedef struct _TestPropsTextChannel TpTestsPropsTextChannel;
+typedef struct _TestPropsTextChannelClass TpTestsPropsTextChannelClass;
+
+struct _TestPropsTextChannel {
+ TpTestsTextChannelNull parent;
+
+ GHashTable *dbus_property_interfaces_retrieved;
+};
+
+struct _TestPropsTextChannelClass {
+ TpTestsTextChannelNullClass parent;
+
+ TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+GType tp_tests_props_text_channel_get_type (void);
+
+#define TP_TESTS_TYPE_PROPS_TEXT_CHANNEL \
+ (tp_tests_props_text_channel_get_type ())
+#define TP_TESTS_PROPS_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL, \
+ TpTestsPropsTextChannel))
+#define TP_TESTS_PROPS_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL, \
+ TpTestsPropsTextChannelClass))
+#define TP_TESTS_IS_PROPS_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL))
+#define TP_TESTS_IS_PROPS_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL))
+#define TP_TESTS_PROPS_TEXT_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL, \
+ TpTestsPropsTextChannelClass))
+
+/* Subclass with D-Bus properties and Group */
+
+typedef struct _TestPropsGroupTextChannel TpTestsPropsGroupTextChannel;
+typedef struct _TestPropsGroupTextChannelClass TpTestsPropsGroupTextChannelClass;
+
+struct _TestPropsGroupTextChannel {
+ TpTestsPropsTextChannel parent;
+
+ TpGroupMixin group;
+};
+
+struct _TestPropsGroupTextChannelClass {
+ TpTestsPropsTextChannelClass parent;
+
+ TpGroupMixinClass group_class;
+};
+
+GType tp_tests_props_group_text_channel_get_type (void);
+
+#define TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL \
+ (tp_tests_props_group_text_channel_get_type ())
+#define TP_TESTS_PROPS_GROUP_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
+ TpTestsPropsGroupTextChannel))
+#define TP_TESTS_PROPS_GROUP_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
+ TpTestsPropsGroupTextChannelClass))
+#define TP_TESTS_IS_PROPS_GROUP_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL))
+#define TP_TESTS_IS_PROPS_GROUP_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL))
+#define TP_TESTS_PROPS_GROUP_TEXT_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
+ TpTestsPropsGroupTextChannelClass))
+
+void tp_tests_text_channel_null_close (TpTestsTextChannelNull *self);
+
+GHashTable * tp_tests_text_channel_get_props (TpTestsTextChannelNull *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_TEXT_CHANNEL_NULL_H__ */
diff --git a/qt4/tests/lib/glib/util.c b/qt4/tests/lib/glib/util.c
new file mode 100644
index 000000000..4dbec7bb2
--- /dev/null
+++ b/qt4/tests/lib/glib/util.c
@@ -0,0 +1,249 @@
+/* Simple utility code used by the regression tests.
+ *
+ * Copyright © 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "util.h"
+
+void
+tp_tests_proxy_run_until_prepared (gpointer proxy,
+ const GQuark *features)
+{
+ GError *error = NULL;
+
+ tp_tests_proxy_run_until_prepared_or_failed (proxy, features, &error);
+ g_assert_no_error (error);
+}
+
+static void
+prepared_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GAsyncResult **result = user_data;
+
+ *result = g_object_ref (res);
+}
+
+gboolean
+tp_tests_proxy_run_until_prepared_or_failed (gpointer proxy,
+ const GQuark *features,
+ GError **error)
+{
+ GAsyncResult *result = NULL;
+
+ tp_proxy_prepare_async (proxy, features, prepared_cb, &result);
+ /* not synchronous */
+ g_assert (result == NULL);
+
+ while (result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ return tp_proxy_prepare_finish (proxy, result, error);
+}
+
+TpDBusDaemon *
+tp_tests_dbus_daemon_dup_or_die (void)
+{
+ TpDBusDaemon *d = tp_dbus_daemon_dup (NULL);
+
+ /* In a shared library, this would be very bad (see fd.o #18832), but in a
+ * regression test that's going to be run under a temporary session bus,
+ * it's just what we want. */
+ if (d == NULL)
+ {
+ g_error ("Unable to connect to session bus");
+ }
+
+ return d;
+}
+
+static void
+introspect_cb (TpProxy *proxy G_GNUC_UNUSED,
+ const gchar *xml G_GNUC_UNUSED,
+ const GError *error G_GNUC_UNUSED,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ g_main_loop_quit (user_data);
+}
+
+void
+tp_tests_proxy_run_until_dbus_queue_processed (gpointer proxy)
+{
+ GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+
+ tp_cli_dbus_introspectable_call_introspect (proxy, -1, introspect_cb,
+ loop, NULL, NULL);
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+}
+
+typedef struct {
+ GMainLoop *loop;
+ TpHandle handle;
+} HandleRequestResult;
+
+static void
+handles_requested_cb (TpConnection *connection G_GNUC_UNUSED,
+ TpHandleType handle_type G_GNUC_UNUSED,
+ guint n_handles,
+ const TpHandle *handles,
+ const gchar * const *ids G_GNUC_UNUSED,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object G_GNUC_UNUSED)
+{
+ HandleRequestResult *result = user_data;
+
+ g_assert_no_error ((GError *) error);
+ g_assert_cmpuint (n_handles, ==, 1);
+ result->handle = handles[0];
+}
+
+static void
+handle_request_result_finish (gpointer r)
+{
+ HandleRequestResult *result = r;
+
+ g_main_loop_quit (result->loop);
+}
+
+TpHandle
+tp_tests_connection_run_request_contact_handle (TpConnection *connection,
+ const gchar *id)
+{
+ HandleRequestResult result = { g_main_loop_new (NULL, FALSE), 0 };
+ const gchar * const ids[] = { id, NULL };
+
+ tp_connection_request_handles (connection, -1, TP_HANDLE_TYPE_CONTACT, ids,
+ handles_requested_cb, &result, handle_request_result_finish, NULL);
+ g_main_loop_run (result.loop);
+ g_main_loop_unref (result.loop);
+ return result.handle;
+}
+
+void
+_test_assert_empty_strv (const char *file,
+ int line,
+ gconstpointer strv)
+{
+ const gchar * const *strings = strv;
+
+ if (strv != NULL && strings[0] != NULL)
+ {
+ guint i;
+
+ g_message ("%s:%d: expected empty strv, but got:", file, line);
+
+ for (i = 0; strings[i] != NULL; i++)
+ {
+ g_message ("* \"%s\"", strings[i]);
+ }
+
+ g_error ("%s:%d: strv wasn't empty (see above for contents",
+ file, line);
+ }
+}
+
+void
+_tp_tests_assert_strv_equals (const char *file,
+ int line,
+ const char *expected_desc,
+ gconstpointer expected_strv,
+ const char *actual_desc,
+ gconstpointer actual_strv)
+{
+ const gchar * const *expected = expected_strv;
+ const gchar * const *actual = actual_strv;
+ guint i;
+
+ g_assert (expected != NULL);
+ g_assert (actual != NULL);
+
+ for (i = 0; expected[i] != NULL || actual[i] != NULL; i++)
+ {
+ if (expected[i] == NULL)
+ {
+ g_error ("%s:%d: assertion failed: (%s)[%u] == (%s)[%u]: "
+ "NULL == %s", file, line, expected_desc, i,
+ actual_desc, i, actual[i]);
+ }
+ else if (actual[i] == NULL)
+ {
+ g_error ("%s:%d: assertion failed: (%s)[%u] == (%s)[%u]: "
+ "%s == NULL", file, line, expected_desc, i,
+ actual_desc, i, expected[i]);
+ }
+ else if (tp_strdiff (expected[i], actual[i]))
+ {
+ g_error ("%s:%d: assertion failed: (%s)[%u] == (%s)[%u]: "
+ "%s == %s", file, line, expected_desc, i,
+ actual_desc, i, expected[i], actual[i]);
+ }
+ }
+}
+
+void
+tp_tests_create_and_connect_conn (GType conn_type,
+ const gchar *account,
+ TpBaseConnection **service_conn,
+ TpConnection **client_conn)
+{
+ TpDBusDaemon *dbus;
+ gchar *name;
+ gchar *conn_path;
+ GError *error = NULL;
+ GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 };
+
+ g_assert (service_conn != NULL);
+ g_assert (client_conn != NULL);
+
+ dbus = tp_tests_dbus_daemon_dup_or_die ();
+
+ *service_conn = tp_tests_object_new_static_class (
+ conn_type,
+ "account", account,
+ "protocol", "simple",
+ NULL);
+ g_assert (*service_conn != NULL);
+
+ g_assert (tp_base_connection_register (*service_conn, "simple",
+ &name, &conn_path, &error));
+ g_assert_no_error (error);
+
+ *client_conn = tp_connection_new (dbus, name, conn_path,
+ &error);
+ g_assert (*client_conn != NULL);
+ g_assert_no_error (error);
+
+ tp_cli_connection_call_connect (*client_conn, -1, NULL, NULL, NULL, NULL);
+ tp_tests_proxy_run_until_prepared (*client_conn, conn_features);
+
+ g_free (name);
+ g_free (conn_path);
+
+ g_object_unref (dbus);
+}
+
+/* This object exists solely so that tests/tests.supp can ignore "leaked"
+ * classes. */
+gpointer
+tp_tests_object_new_static_class (GType type,
+ ...)
+{
+ va_list ap;
+ GObject *object;
+ const gchar *first_property;
+
+ va_start (ap, type);
+ first_property = va_arg (ap, const gchar *);
+ object = g_object_new_valist (type, first_property, ap);
+ va_end (ap);
+ return object;
+}
diff --git a/qt4/tests/lib/glib/util.h b/qt4/tests/lib/glib/util.h
new file mode 100644
index 000000000..d3433ccf9
--- /dev/null
+++ b/qt4/tests/lib/glib/util.h
@@ -0,0 +1,51 @@
+/* Simple utility code used by the regression tests.
+ *
+ * Copyright © 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright © 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_LIB_UTIL_H__
+#define __TP_TESTS_LIB_UTIL_H__
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-glib/base-connection.h>
+
+TpDBusDaemon *tp_tests_dbus_daemon_dup_or_die (void);
+
+void tp_tests_proxy_run_until_dbus_queue_processed (gpointer proxy);
+
+TpHandle tp_tests_connection_run_request_contact_handle (
+ TpConnection *connection,
+ const gchar *id);
+
+void tp_tests_proxy_run_until_prepared (gpointer proxy,
+ const GQuark *features);
+gboolean tp_tests_proxy_run_until_prepared_or_failed (gpointer proxy,
+ const GQuark *features,
+ GError **error);
+
+#define test_assert_empty_strv(strv) \
+ _test_assert_empty_strv (__FILE__, __LINE__, strv)
+void _test_assert_empty_strv (const char *file, int line, gconstpointer strv);
+
+#define tp_tests_assert_strv_equals(actual, expected) \
+ _tp_tests_assert_strv_equals (__FILE__, __LINE__, \
+ #actual, actual, \
+ #expected, expected)
+void _tp_tests_assert_strv_equals (const char *file, int line,
+ const char *actual_desc, gconstpointer actual_strv,
+ const char *expected_desc, gconstpointer expected_strv);
+
+void tp_tests_create_and_connect_conn (GType conn_type,
+ const gchar *account,
+ TpBaseConnection **service_conn,
+ TpConnection **client_conn);
+
+gpointer tp_tests_object_new_static_class (GType type,
+ ...) G_GNUC_NULL_TERMINATED;
+
+#endif /* #ifndef __TP_TESTS_LIB_UTIL_H__ */
diff --git a/qt4/tests/lib/python/account-manager.py b/qt4/tests/lib/python/account-manager.py
new file mode 100755
index 000000000..0f1fd9f9b
--- /dev/null
+++ b/qt4/tests/lib/python/account-manager.py
@@ -0,0 +1,394 @@
+#!/usr/bin/python
+#
+# A small implementation of a Telepathy AccountManager.
+
+import sys
+import re
+
+import dbus
+from dbus.bus import NAME_FLAG_DO_NOT_QUEUE, REQUEST_NAME_REPLY_EXISTS
+from dbus.mainloop.glib import DBusGMainLoop
+from dbus.service import Object, method, signal
+from gobject import MainLoop
+
+TP = 'org.freedesktop.Telepathy'
+
+AM_IFACE = TP + '.AccountManager'
+AM_BUS_NAME = AM_IFACE
+AM_OBJECT_PATH = '/' + AM_IFACE.replace('.', '/')
+
+ACCOUNT_IFACE = TP + '.Account'
+ACCOUNT_IFACE_AVATAR_IFACE = ACCOUNT_IFACE + '.Interface.Avatar'
+ACCOUNT_OBJECT_PATH_BASE = '/' + ACCOUNT_IFACE.replace('.', '/') + '/'
+
+
+Connection_Status_Connected = dbus.UInt32(0)
+Connection_Status_Connecting = dbus.UInt32(1)
+Connection_Status_Disconnected = dbus.UInt32(2)
+Connection_Status_Reason_None_Specified = dbus.UInt32(0)
+Connection_Status_Reason_Requested = dbus.UInt32(1)
+Connection_Status_Reason_Network_Error = dbus.UInt32(2)
+Connection_Presence_Type_Offline = dbus.UInt32(1)
+Connection_Presence_Type_Available = dbus.UInt32(2)
+
+
+VALID_CONNECTION_MANAGER_NAME = re.compile(r'^[A-Za-z0-9][_A-Za-z0-9]+$')
+VALID_PROTOCOL_NAME = re.compile(r'^[A-Za-z0-9][-A-Za-z0-9]+$')
+
+TELEPATHY_ERROR = "org.freedesktop.Telepathy.Error"
+TELEPATHY_ERROR_DISCONNECTED = TELEPATHY_ERROR + ".Disconnected"
+TELEPATHY_ERROR_CANCELLED = TELEPATHY_ERROR + ".Cancelled"
+TELEPATHY_ERROR_NETWORK_ERROR = TELEPATHY_ERROR + ".NetworkError"
+
+class AccountManager(Object):
+ def __init__(self, bus=None):
+ #: map from object path to Account
+ self._valid_accounts = {}
+ #: map from object path to Account
+ self._invalid_accounts = {}
+
+ if bus is None:
+ bus = dbus.SessionBus()
+
+ ret = bus.request_name(AM_BUS_NAME, NAME_FLAG_DO_NOT_QUEUE)
+ if ret == REQUEST_NAME_REPLY_EXISTS:
+ raise dbus.NameExistsException(AM_BUS_NAME)
+
+ Object.__init__(self, bus, AM_OBJECT_PATH)
+
+ def _am_props(self):
+ return dbus.Dictionary({
+ 'Interfaces': dbus.Array([], signature='s'),
+ 'ValidAccounts': dbus.Array(self._valid_accounts.keys(),
+ signature='o'),
+ 'InvalidAccounts': dbus.Array(self._invalid_accounts.keys(),
+ signature='o'),
+ 'SupportedAccountProperties': dbus.Array([ACCOUNT_IFACE + '.Enabled'], signature='s'),
+ }, signature='sv')
+
+ @method(dbus.PROPERTIES_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, iface):
+ if iface == AM_IFACE:
+ return self._am_props()
+ else:
+ raise ValueError('No such interface')
+
+ @method(dbus.PROPERTIES_IFACE,
+ in_signature='ss',
+ out_signature='v')
+ def Get(self, iface, prop):
+ if iface == AM_IFACE:
+ props = self._am_props()
+ else:
+ raise ValueError('No such interface')
+
+ if prop in props:
+ return props[prop]
+ else:
+ raise ValueError('No such property')
+
+ @method(dbus.PROPERTIES_IFACE,
+ in_signature='ssv')
+ def Set(self, iface, prop, value):
+ raise NotImplementedError('No mutable properties')
+
+ @signal(AM_IFACE, signature='ob')
+ def AccountValidityChanged(self, path, valid):
+ if valid:
+ assert path in self._invalid_accounts
+ assert path not in self._valid_accounts
+ self._valid_accounts[path] = self._invalid_accounts.pop(path)
+ else:
+ assert path in self._valid_accounts
+ assert path not in self._invalid_accounts
+ self._invalid_accounts[path] = self._valid_accounts.pop(path)
+ print "Emitting AccountValidityChanged(%s, %s)" % (path, valid)
+
+ @signal(AM_IFACE, signature='o')
+ def AccountRemoved(self, path):
+ assert path in self._valid_accounts or path in self._invalid_accounts
+ self._valid_accounts.pop(path, None)
+ self._invalid_accounts.pop(path, None)
+ print "Emitting AccountRemoved(%s)" % path
+
+ @method(AM_IFACE, in_signature='sssa{sv}a{sv}', out_signature='o')
+ def CreateAccount(self, cm, protocol, display_name, parameters,
+ properties):
+
+ if not VALID_CONNECTION_MANAGER_NAME.match(cm):
+ raise ValueError('Invalid CM name')
+
+ if not VALID_PROTOCOL_NAME.match(protocol):
+ raise ValueError('Invalid protocol name')
+
+ if properties:
+ raise ValueError('This AM does not support setting properties at'
+ 'account creation')
+
+ base = ACCOUNT_OBJECT_PATH_BASE + cm + '/' + protocol.replace('-', '_')
+
+ # FIXME: This is a stupid way to generate the paths - we should
+ # incorporate the display name somehow. However, it's spec-compliant
+ i = 0
+ while 1:
+ path = '%s/Account%d' % (base, i)
+
+ if (path not in self._valid_accounts and
+ path not in self._invalid_accounts):
+ account = Account(self, path,
+ '%s (account %d)' % (display_name, i), parameters)
+
+ # put it in the wrong set and move it to the right one -
+ # that's probably the simplest implementation
+ if account._is_valid():
+ self._invalid_accounts[path] = account
+ self.AccountValidityChanged(path, True)
+ assert path not in self._invalid_accounts
+ assert path in self._valid_accounts
+ else:
+ self._valid_accounts[path] = account
+ self.AccountValidityChanged(path, False)
+ assert path not in self._valid_accounts
+ assert path in self._invalid_accounts
+
+ return path
+
+ i += 1
+
+ raise AssertionError('Not reached')
+
+class Account(Object):
+ def __init__(self, am, path, display_name, parameters):
+ Object.__init__(self, am.connection, path)
+ self._am = am
+
+ self._connection = dbus.ObjectPath('/')
+ self._connection_status = Connection_Status_Disconnected
+ self._connection_status_reason = Connection_Status_Reason_None_Specified
+ self._connection_error = u''
+ self._connection_error_details = dbus.Dictionary({}, signature='sv')
+ self._service = u''
+ self._display_name = display_name
+ self._icon = u'bob.png'
+ self._enabled = True
+ self._nickname = u'Bob'
+ self._parameters = parameters
+ self._has_been_online = False
+ self._connect_automatically = False
+ self._normalized_name = u'bob'
+ self._automatic_presence = dbus.Struct(
+ (Connection_Presence_Type_Available, 'available', ''),
+ signature='uss')
+ self._current_presence = dbus.Struct(
+ (Connection_Presence_Type_Offline, 'offline', ''),
+ signature='uss')
+ self._requested_presence = dbus.Struct(
+ (Connection_Presence_Type_Offline, 'offline', ''),
+ signature='uss')
+ self._avatar = dbus.Struct(
+ (dbus.ByteArray(''), 'image/png'),
+ signature='ays')
+ self._interfaces = [ACCOUNT_IFACE_AVATAR_IFACE,]
+
+ def _is_valid(self):
+ return True
+
+ @method(ACCOUNT_IFACE, in_signature='a{sv}as', out_signature='as')
+ def UpdateParameters(self, set_, unset):
+ print ("%s: entering UpdateParameters(\n %r,\n %r \n)"
+ % (self.__dbus_object_path__, set_, unset))
+ for (key, value) in set_.iteritems():
+ self._parameters[key] = value
+ for key in unset:
+ self._parameters.pop(key, None)
+ print ("%s: UpdateParameters(...) -> success"
+ % self.__dbus_object_path__)
+
+ self.AccountPropertyChanged({'Parameters': self._parameters})
+
+ return []
+
+ @signal(ACCOUNT_IFACE, signature='a{sv}')
+ def AccountPropertyChanged(self, delta):
+ print ("%s: emitting AccountPropertyChanged(\n %r \n)"
+ % (self.__dbus_object_path__, delta))
+
+ @signal(ACCOUNT_IFACE_AVATAR_IFACE, signature='')
+ def AvatarChanged(self):
+ print ("%s: emitting AvatarChanged"
+ % (self.__dbus_object_path__))
+
+ @method(ACCOUNT_IFACE, in_signature='', out_signature='')
+ def Remove(self):
+ print "%s: entering Remove()" % self.__dbus_object_path__
+ self.Removed()
+ self.remove_from_connection()
+ print "%s: Remove() -> success" % self.__dbus_object_path__
+
+ @signal(ACCOUNT_IFACE, signature='')
+ def Removed(self):
+ self._am.AccountRemoved(self.__dbus_object_path__)
+ print "%s: Emitting Removed()" % self.__dbus_object_path__
+
+ def _account_props(self):
+ return dbus.Dictionary({
+ 'Interfaces': dbus.Array(self._interfaces, signature='s'),
+ 'Service': self._service,
+ 'DisplayName': self._display_name,
+ 'Icon': self._icon,
+ 'Valid': self._is_valid(),
+ 'Enabled': self._enabled,
+ 'Nickname': self._nickname,
+ 'Parameters': self._parameters,
+ 'AutomaticPresence': self._automatic_presence,
+ 'CurrentPresence': self._current_presence,
+ 'RequestedPresence': self._requested_presence,
+ 'HasBeenOnline': self._has_been_online,
+ 'ConnectAutomatically': self._connect_automatically,
+ 'Connection': self._connection,
+ 'ConnectionStatus': self._connection_status,
+ 'ConnectionStatusReason': self._connection_status_reason,
+ 'ConnectionError': self._connection_error,
+ 'ConnectionErrorDetails': self._connection_error_details,
+ 'NormalizedName': self._normalized_name,
+ }, signature='sv')
+
+ def _account_avatar_props(self):
+ return dbus.Dictionary({
+ 'Avatar': self._avatar
+ }, signature='sv')
+
+ @method(dbus.PROPERTIES_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, iface):
+ if iface == ACCOUNT_IFACE:
+ return self._account_props()
+ elif iface == ACCOUNT_IFACE_AVATAR_IFACE:
+ return self._account_avatar_props()
+ else:
+ raise ValueError('No such interface')
+
+ @method(dbus.PROPERTIES_IFACE,
+ in_signature='ss',
+ out_signature='v')
+ def Get(self, iface, prop):
+ if iface == ACCOUNT_IFACE:
+ props = self._account_props()
+ elif iface == ACCOUNT_IFACE_AVATAR_IFACE:
+ props = self._account_avatar_props()
+ else:
+ raise ValueError('No such interface')
+
+ if prop in props:
+ return props[prop]
+ else:
+ raise ValueError('No such property')
+
+ @method(dbus.PROPERTIES_IFACE,
+ in_signature='ssv', byte_arrays=True)
+ def Set(self, iface, prop, value):
+ if iface == ACCOUNT_IFACE:
+ props = {}
+ if prop == 'Service':
+ self._service = unicode(value)
+ elif prop == 'DisplayName':
+ self._display_name = unicode(value)
+ elif prop == 'Icon':
+ self._icon = unicode(value)
+ elif prop == 'Enabled':
+ self._enabled = bool(value)
+ elif prop == 'Nickname':
+ self._nickname = unicode(value)
+ elif prop == 'AutomaticPresence':
+ self._automatic_presence = dbus.Struct(
+ (dbus.UInt32(value[0]), unicode(value[1]),
+ unicode(value[2])),
+ signature='uss')
+ elif prop == 'RequestedPresence':
+ self._requested_presence = dbus.Struct(
+ (dbus.UInt32(value[0]), unicode(value[1]),
+ unicode(value[2])),
+ signature='uss')
+ # pretend to put the account online, if the presence != offline
+ if value[0] != Connection_Presence_Type_Offline:
+ # simulate that we are connecting/changing presence
+ props["ChangingPresence"] = True
+
+ if self._connection_status == Connection_Status_Disconnected:
+ self._connection_status = Connection_Status_Connecting
+ props["ConnectionStatus"] = self._connection_status
+
+ props[prop] = self._account_props()[prop]
+ self.AccountPropertyChanged(props)
+
+ props["ChangingPresence"] = False
+ if "(deny)" in self._requested_presence[2]:
+ self._connection_status = Connection_Status_Disconnected
+ self._connection_status_reason = Connection_Status_Reason_Network_Error
+ self._connection_error = TELEPATHY_ERROR_NETWORK_ERROR
+ self._connection_error_details = dbus.Dictionary(
+ {'debug-message': u'You asked for it'},
+ signature='sv')
+ self._current_presence = dbus.Struct(
+ (Connection_Presence_Type_Offline, 'offline', ''),
+ signature='uss')
+ else:
+ self._connection_status = Connection_Status_Connected
+ self._connection_status_reason = Connection_Status_Reason_None_Specified
+ self._connection_error = u''
+ self._connection_error_details = dbus.Dictionary({}, signature='sv')
+ self._current_presence = self._requested_presence
+ if self._has_been_online == False:
+ self._has_been_online = True
+ props["HasBeenOnline"] = self._has_been_online
+ else:
+ self._connection_status = Connection_Status_Disconnected
+ self._connection_status_reason = Connection_Status_Reason_Requested
+ self._connection_error = TELEPATHY_ERROR_CANCELLED
+ self._connection_error_details = dbus.Dictionary(
+ {'debug-message': u'You asked for it'},
+ signature='sv')
+ self._current_presence = dbus.Struct(
+ (Connection_Presence_Type_Offline, 'offline', ''),
+ signature='uss')
+
+ props["ConnectionStatus"] = self._connection_status
+ props["ConnectionStatusReason"] = self._connection_status_reason
+ props["ConnectionError"] = self._connection_error
+ props["ConnectionErrorDetails"] = self._connection_error_details
+ props["CurrentPresence"] = self._current_presence
+ elif prop == 'ConnectAutomatically':
+ self._connect_automatically = bool(value)
+ elif prop == 'Connection':
+ self._connection = dbus.ObjectPath(value)
+ else:
+ raise ValueError('Read-only or nonexistent property')
+
+ props[prop] = self._account_props()[prop]
+ self.AccountPropertyChanged(props)
+ elif iface == ACCOUNT_IFACE_AVATAR_IFACE:
+ if prop == 'Avatar':
+ self._avatar = dbus.Struct(
+ (dbus.ByteArray(value[0]), unicode(value[1])),
+ signature='ays')
+ self.AvatarChanged()
+ else:
+ raise ValueError('Nonexistent property')
+ else:
+ raise ValueError('No such interface')
+
+if __name__ == '__main__':
+ DBusGMainLoop(set_as_default=True)
+
+ try:
+ am = AccountManager()
+ except dbus.NameExistsException:
+ print >> sys.stderr, 'AccountManager already running'
+ sys.exit(1)
+
+ print "AccountManager running..."
+ mainloop = MainLoop()
+ mainloop.run()
diff --git a/qt4/tests/lib/test.cpp b/qt4/tests/lib/test.cpp
new file mode 100644
index 000000000..5fb394096
--- /dev/null
+++ b/qt4/tests/lib/test.cpp
@@ -0,0 +1,132 @@
+#include "tests/lib/test.h"
+
+#include <cstdlib>
+
+#include <QtCore/QTimer>
+
+#include <TelepathyQt4/Types>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/DBus>
+#include <TelepathyQt4/PendingVoid>
+#include <TelepathyQt4/SharedPtr>
+#include <TelepathyQt4/RefCounted>
+
+using Tp::PendingOperation;
+using Tp::PendingVoid;
+using Tp::Client::DBus::PeerInterface;
+
+Test::Test(QObject *parent)
+ : QObject(parent), mLoop(new QEventLoop(this))
+{
+ QTimer::singleShot(10 * 60 * 1000, this, SLOT(onWatchdog()));
+}
+
+Test::~Test()
+{
+ delete mLoop;
+}
+
+void Test::initTestCaseImpl()
+{
+ Tp::registerTypes();
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+
+ QVERIFY(QDBusConnection::sessionBus().isConnected());
+}
+
+void Test::initImpl()
+{
+}
+
+void Test::cleanupImpl()
+{
+}
+
+void Test::cleanupTestCaseImpl()
+{
+ // To allow for cleanup code to run (e.g. PendingOperation cleanup after they finish)
+ mLoop->processEvents();
+}
+
+void Test::expectSuccessfulCall(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning().nospace() << op->errorName()
+ << ": " << op->errorMessage();
+ mLoop->exit(1);
+ return;
+ }
+
+ mLoop->exit(0);
+}
+
+void Test::expectSuccessfulCall(QDBusPendingCallWatcher *watcher)
+{
+ if (watcher->isError()) {
+ qWarning().nospace() << watcher->error().name()
+ << ": " << watcher->error().message();
+ mLoop->exit(1);
+ return;
+ }
+
+ mLoop->exit(0);
+}
+
+void Test::expectFailure(PendingOperation *op)
+{
+ if (!op->isError()) {
+ qWarning() << "expectFailure(): should have been an error, but wasn't";
+ mLastError = QString();
+ mLastErrorMessage = QString();
+ mLoop->exit(1);
+ return;
+ }
+
+ mLastError = op->errorName();
+ mLastErrorMessage = op->errorMessage();
+
+ mLoop->exit(0);
+}
+
+void Test::expectSuccessfulProperty(PendingOperation *op)
+{
+ if (op->isError()) {
+ qWarning().nospace() << op->errorName()
+ << ": " << op->errorMessage();
+ mPropertyValue = QVariant();
+ mLoop->exit(1001);
+ } else {
+ Tp::PendingVariant *pv = qobject_cast<Tp::PendingVariant*>(op);
+ mPropertyValue = pv->result();
+ mLoop->exit(1000);
+ }
+}
+
+void Test::processDBusQueue(Tp::DBusProxy *proxy)
+{
+ // Call method Ping on the D-Bus Peer interface
+ PeerInterface peer(proxy);
+ PendingVoid *call = new PendingVoid(peer.Ping(), Tp::SharedPtr<Tp::RefCounted>());
+
+ // Wait for the reply to the Ping call
+ while (!call->isFinished()) {
+ mLoop->processEvents();
+ }
+
+ QVERIFY(call->isFinished());
+ QVERIFY(call->isValid());
+
+ // Do one more processEvents so the PendingVoid is always freed
+ mLoop->processEvents();
+}
+
+void Test::onWatchdog()
+{
+ // We can't use QFAIL because the test would then go to cleanup() and/or cleanupTestCase(),
+ // which would often hang too - so let's just use abort
+ qWarning() << "Test took over 10 minutes to finish, it's probably hung up - aborting";
+ std::abort();
+}
+
+#include "_gen/test.h.moc.hpp"
diff --git a/qt4/tests/lib/test.h b/qt4/tests/lib/test.h
new file mode 100644
index 000000000..39591542e
--- /dev/null
+++ b/qt4/tests/lib/test.h
@@ -0,0 +1,88 @@
+#ifndef _TelepathyQt4_tests_lib_test_h_HEADER_GUARD_
+#define _TelepathyQt4_tests_lib_test_h_HEADER_GUARD_
+
+#include <QtDBus>
+#include <QtTest>
+
+#include <TelepathyQt4/PendingOperation>
+#include <TelepathyQt4/PendingVariant>
+#include <TelepathyQt4/Constants>
+
+namespace Tp
+{
+class DBusProxy;
+}
+
+class Test : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ Test(QObject *parent = 0);
+
+ virtual ~Test();
+
+ QEventLoop *mLoop;
+ void processDBusQueue(Tp::DBusProxy *proxy);
+
+ // The last error received in expectFailure()
+ QString mLastError;
+ QString mLastErrorMessage;
+
+protected:
+ template<typename T> bool waitForProperty(Tp::PendingVariant *pv, T *value);
+
+protected Q_SLOTS:
+ void expectSuccessfulCall(QDBusPendingCallWatcher*);
+ void expectSuccessfulCall(Tp::PendingOperation*);
+ void expectFailure(Tp::PendingOperation*);
+ void expectSuccessfulProperty(Tp::PendingOperation *op);
+ void onWatchdog();
+
+ virtual void initTestCaseImpl();
+ virtual void initImpl();
+
+ virtual void cleanupImpl();
+ virtual void cleanupTestCaseImpl();
+
+private:
+ // The property retrieved by expectSuccessfulProperty()
+ QVariant mPropertyValue;
+};
+
+template<typename T>
+bool Test::waitForProperty(Tp::PendingVariant *pv, T *value)
+{
+ connect(pv,
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(expectSuccessfulProperty(Tp::PendingOperation*)));
+ if (mLoop->exec() == 1000) {
+ *value = qdbus_cast<T>(mPropertyValue);
+ return true;
+ }
+ else {
+ *value = T();
+ return false;
+ }
+}
+
+#define TEST_VERIFY_OP(op) \
+ if (!op->isFinished()) { \
+ qWarning() << "unfinished"; \
+ mLoop->exit(1); \
+ return; \
+ } \
+ if (op->isError()) { \
+ qWarning().nospace() << op->errorName() << ": " << op->errorMessage(); \
+ mLoop->exit(2); \
+ return; \
+ } \
+ if (!op->isValid()) { \
+ qWarning() << "inconsistent results"; \
+ mLoop->exit(3); \
+ return; \
+ } \
+ qDebug() << "finished";
+
+#endif // _TelepathyQt4_tests_lib_test_h_HEADER_GUARD_
diff --git a/qt4/tests/manager-file.cpp b/qt4/tests/manager-file.cpp
new file mode 100644
index 000000000..deed51702
--- /dev/null
+++ b/qt4/tests/manager-file.cpp
@@ -0,0 +1,227 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/ManagerFile>
+
+using namespace Tp;
+
+namespace
+{
+
+bool containsParam(const ParamSpecList &params, const char *name)
+{
+ Q_FOREACH (const ParamSpec &param, params) {
+ if (param.name == QLatin1String(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const ParamSpec *getParam(const ParamSpecList &params, const QString &name)
+{
+ Q_FOREACH (const ParamSpec &param, params) {
+ if (param.name == name) {
+ return &param;
+ }
+ }
+ return NULL;
+}
+
+PresenceSpec getPresenceSpec(const PresenceSpecList &specs, const QString &status)
+{
+ foreach (const PresenceSpec &spec, specs) {
+ if (spec.presence().status() == status) {
+ return spec;
+ }
+ }
+ return PresenceSpec();
+}
+
+}
+
+class TestManagerFile : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestManagerFile(QObject *parent = 0);
+
+private Q_SLOTS:
+ void testManagerFile();
+};
+
+TestManagerFile::TestManagerFile(QObject *parent)
+ : QObject(parent)
+{
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+}
+
+void TestManagerFile::testManagerFile()
+{
+ ManagerFile notFoundManagerFile(QLatin1String("test-manager-file-not-found"));
+ QCOMPARE(notFoundManagerFile.isValid(), false);
+
+ ManagerFile invalidManagerFile(QLatin1String("test-manager-file-malformed-keyfile"));
+ QCOMPARE(invalidManagerFile.isValid(), false);
+
+ ManagerFile invalidManagerFile2(QLatin1String("test-manager-file-invalid-signature"));
+ QCOMPARE(invalidManagerFile2.isValid(), false);
+
+ ManagerFile managerFile(QLatin1String("test-manager-file"));
+ QCOMPARE(managerFile.isValid(), true);
+
+ QStringList protocols = managerFile.protocols();
+ protocols.sort();
+ QCOMPARE(protocols,
+ QStringList() << QLatin1String("bar") << QLatin1String("foo") << QLatin1String("somewhat-pathological"));
+
+ ParamSpecList params = managerFile.parameters(QLatin1String("foo"));
+ QCOMPARE(containsParam(params, "account"), true);
+ QCOMPARE(containsParam(params, "encryption-key"), true);
+ QCOMPARE(containsParam(params, "password"), true);
+ QCOMPARE(containsParam(params, "port"), true);
+ QCOMPARE(containsParam(params, "register"), true);
+ QCOMPARE(containsParam(params, "server-list"), true);
+ QCOMPARE(containsParam(params, "non-existant"), false);
+
+ QCOMPARE(QLatin1String("x-foo"), managerFile.vcardField(QLatin1String("foo")));
+ QCOMPARE(QLatin1String("Foo"), managerFile.englishName(QLatin1String("foo")));
+ QCOMPARE(QLatin1String("im-foo"), managerFile.iconName(QLatin1String("foo")));
+
+ RequestableChannelClass ftRcc;
+ ftRcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER));
+ ftRcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ HandleTypeContact);
+ ftRcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType"),
+ FileHashTypeMD5);
+ ftRcc.allowedProperties.append(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"));
+ ftRcc.allowedProperties.append(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"));
+
+ RequestableChannelClass fooTextRcc;
+ fooTextRcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"),
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT));
+ fooTextRcc.fixedProperties.insert(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"),
+ HandleTypeContact);
+ fooTextRcc.allowedProperties.append(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"));
+ fooTextRcc.allowedProperties.append(
+ QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"));
+
+ RequestableChannelClassList expectedRccs;
+
+ expectedRccs.append(ftRcc);
+ expectedRccs.append(fooTextRcc);
+
+ QCOMPARE(expectedRccs.size(), managerFile.requestableChannelClasses(QLatin1String("foo")).size());
+
+ qDebug() << managerFile.requestableChannelClasses(QLatin1String("foo"))[0].fixedProperties;
+ qDebug() << managerFile.requestableChannelClasses(QLatin1String("foo"))[1].fixedProperties;
+
+ QCOMPARE(ftRcc, managerFile.requestableChannelClasses(QLatin1String("foo"))[0]);
+ QCOMPARE(fooTextRcc, managerFile.requestableChannelClasses(QLatin1String("foo"))[1]);
+
+ const ParamSpec *param;
+ param = getParam(params, QLatin1String("account"));
+ QCOMPARE(param->flags, (uint) ConnMgrParamFlagRequired | ConnMgrParamFlagHasDefault);
+ QCOMPARE(param->signature, QString(QLatin1String("s")));
+ param = getParam(params, QLatin1String("password"));
+ QCOMPARE(param->flags, (uint) ConnMgrParamFlagRequired | ConnMgrParamFlagSecret);
+ QCOMPARE(param->signature, QString(QLatin1String("s")));
+ param = getParam(params, QLatin1String("encryption-key"));
+ QCOMPARE(param->flags, (uint) ConnMgrParamFlagSecret);
+ QCOMPARE(param->signature, QString(QLatin1String("s")));
+
+ PresenceSpecList statuses = managerFile.allowedPresenceStatuses(QLatin1String("foo"));
+ QCOMPARE(statuses.size(), 3);
+
+ PresenceSpec spec;
+ spec = getPresenceSpec(statuses, QLatin1String("offline"));
+ QCOMPARE(spec.isValid(), true);
+ QVERIFY(spec.presence().type() == ConnectionPresenceTypeOffline);
+ QCOMPARE(spec.maySetOnSelf(), false);
+ QCOMPARE(spec.canHaveStatusMessage(), false);
+ spec = getPresenceSpec(statuses, QLatin1String("dnd"));
+ QCOMPARE(spec.isValid(), true);
+ QVERIFY(spec.presence().type() == ConnectionPresenceTypeBusy);
+ QCOMPARE(spec.maySetOnSelf(), true);
+ QCOMPARE(spec.canHaveStatusMessage(), false);
+ spec = getPresenceSpec(statuses, QLatin1String("available"));
+ QCOMPARE(spec.isValid(), true);
+ QVERIFY(spec.presence().type() == ConnectionPresenceTypeAvailable);
+ QCOMPARE(spec.maySetOnSelf(), true);
+ QCOMPARE(spec.canHaveStatusMessage(), true);
+
+ AvatarSpec avatarReqs = managerFile.avatarRequirements(QLatin1String("foo"));
+ QStringList supportedMimeTypes = avatarReqs.supportedMimeTypes();
+ supportedMimeTypes.sort();
+ QCOMPARE(supportedMimeTypes,
+ QStringList() << QLatin1String("image/gif") << QLatin1String("image/jpeg") <<
+ QLatin1String("image/png"));
+ QCOMPARE(avatarReqs.minimumHeight(), (uint) 32);
+ QCOMPARE(avatarReqs.maximumHeight(), (uint) 96);
+ QCOMPARE(avatarReqs.recommendedHeight(), (uint) 64);
+ QCOMPARE(avatarReqs.minimumWidth(), (uint) 32);
+ QCOMPARE(avatarReqs.maximumWidth(), (uint) 96);
+ QCOMPARE(avatarReqs.recommendedWidth(), (uint) 64);
+ QCOMPARE(avatarReqs.maximumBytes(), (uint) 8192);
+
+ params = managerFile.parameters(QLatin1String("somewhat-pathological"));
+ QCOMPARE(containsParam(params, "foo"), true);
+ QCOMPARE(containsParam(params, "semicolons"), true);
+ QCOMPARE(containsParam(params, "list"), true);
+ QCOMPARE(containsParam(params, "unterminated-list"), true);
+ QCOMPARE(containsParam(params, "spaces-in-list"), true);
+ QCOMPARE(containsParam(params, "escaped-semicolon-in-list"), true);
+ QCOMPARE(containsParam(params, "doubly-escaped-semicolon-in-list"), true);
+ QCOMPARE(containsParam(params, "triply-escaped-semicolon-in-list"), true);
+ QCOMPARE(containsParam(params, "empty-list"), true);
+ QCOMPARE(containsParam(params, "escaped-semicolon"), true);
+ QCOMPARE(containsParam(params, "object"), true);
+ QCOMPARE(containsParam(params, "non-existant"), false);
+
+ param = getParam(params, QLatin1String("foo"));
+ QCOMPARE(param->flags, (uint) ConnMgrParamFlagRequired | ConnMgrParamFlagHasDefault);
+ QCOMPARE(param->signature, QString(QLatin1String("s")));
+ param = getParam(params, QLatin1String("semicolons"));
+ QCOMPARE(param->flags, (uint) ConnMgrParamFlagSecret | ConnMgrParamFlagHasDefault);
+ QCOMPARE(param->signature, QString(QLatin1String("s")));
+ param = getParam(params, QLatin1String("list"));
+ QCOMPARE(param->signature, QString(QLatin1String("as")));
+ QCOMPARE(param->defaultValue.variant().toStringList(),
+ QStringList() << QLatin1String("list") << QLatin1String("of") << QLatin1String("misc"));
+ param = getParam(params, QLatin1String("escaped-semicolon-in-list"));
+ QCOMPARE(param->signature, QString(QLatin1String("as")));
+ QCOMPARE(param->defaultValue.variant().toStringList(),
+ QStringList() << QLatin1String("list;of") << QLatin1String("misc"));
+ param = getParam(params, QLatin1String("doubly-escaped-semicolon-in-list"));
+ QCOMPARE(param->signature, QString(QLatin1String("as")));
+ QCOMPARE(param->defaultValue.variant().toStringList(),
+ QStringList() << QLatin1String("list\\") << QLatin1String("of") << QLatin1String("misc"));
+ param = getParam(params, QLatin1String("triply-escaped-semicolon-in-list"));
+ QCOMPARE(param->signature, QString(QLatin1String("as")));
+ QCOMPARE(param->defaultValue.variant().toStringList(),
+ QStringList() << QLatin1String("list\\;of") << QLatin1String("misc"));
+ param = getParam(params, QLatin1String("empty-list"));
+ QCOMPARE(param->signature, QString(QLatin1String("as")));
+ QCOMPARE(param->defaultValue.variant().toStringList(),
+ QStringList());
+ param = getParam(params, QLatin1String("list-of-empty-string"));
+ QCOMPARE(param->signature, QString(QLatin1String("as")));
+ QCOMPARE(param->defaultValue.variant().toStringList(),
+ QStringList() << QString());
+}
+
+QTEST_MAIN(TestManagerFile)
+
+#include "_gen/manager-file.cpp.moc.hpp"
diff --git a/qt4/tests/presence.cpp b/qt4/tests/presence.cpp
new file mode 100644
index 000000000..c631c14a6
--- /dev/null
+++ b/qt4/tests/presence.cpp
@@ -0,0 +1,131 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Constants>
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Presence>
+#include <TelepathyQt4/Types>
+
+using namespace Tp;
+
+class TestPresence : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestPresence(QObject *parent = 0);
+
+private Q_SLOTS:
+ void testPresence();
+ void testPresenceSpec();
+};
+
+TestPresence::TestPresence(QObject *parent)
+ : QObject(parent)
+{
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+}
+
+#define TEST_PRESENCE(pr, prValid, prType, prStatus, prStatusMessage) \
+{ \
+ QVERIFY(pr.isValid() == prValid); \
+ QCOMPARE(pr.type(), prType); \
+ QCOMPARE(pr.status(), prStatus); \
+ QCOMPARE(pr.statusMessage(), prStatusMessage); \
+}
+
+void TestPresence::testPresence()
+{
+ Presence pr;
+ TEST_PRESENCE(pr, false, ConnectionPresenceTypeUnknown, QString(), QString());
+ pr.setStatus(ConnectionPresenceTypeAvailable, QLatin1String("available"),
+ QLatin1String("I am available"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAvailable, QLatin1String("available"), QLatin1String("I am available"));
+
+ pr = Presence::available();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAvailable, QLatin1String("available"), QString());
+ pr = Presence::available(QLatin1String("I am available"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAvailable, QLatin1String("available"), QLatin1String("I am available"));
+
+ pr = Presence::away();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAway, QLatin1String("away"), QString());
+ pr = Presence::away(QLatin1String("I am away"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAway, QLatin1String("away"), QLatin1String("I am away"));
+
+ pr = Presence::brb();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAway, QLatin1String("brb"), QString());
+ pr = Presence::brb(QLatin1String("I am brb"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeAway, QLatin1String("brb"), QLatin1String("I am brb"));
+
+ pr = Presence::busy();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeBusy, QLatin1String("busy"), QString());
+ pr = Presence::busy(QLatin1String("I am busy"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeBusy, QLatin1String("busy"), QLatin1String("I am busy"));
+
+ pr = Presence::xa();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeExtendedAway, QLatin1String("xa"), QString());
+ pr = Presence::xa(QLatin1String("I am xa"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeExtendedAway, QLatin1String("xa"), QLatin1String("I am xa"));
+
+ pr = Presence::hidden();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeHidden, QLatin1String("hidden"), QString());
+ pr = Presence::hidden(QLatin1String("I am hidden"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeHidden, QLatin1String("hidden"), QLatin1String("I am hidden"));
+
+ pr = Presence::offline();
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeOffline, QLatin1String("offline"), QString());
+ pr = Presence::offline(QLatin1String("I am offline"));
+ TEST_PRESENCE(pr, true, ConnectionPresenceTypeOffline, QLatin1String("offline"), QLatin1String("I am offline"));
+}
+
+#define TEST_PRESENCE_SPEC_FULL(specStatus, specType, specMaySetOnSelf, specCanHaveMessage) \
+{ \
+ SimpleStatusSpec bareSpec; \
+ bareSpec.type = specType; \
+ bareSpec.maySetOnSelf = specMaySetOnSelf; \
+ bareSpec.canHaveMessage = specCanHaveMessage; \
+\
+ PresenceSpec spec(specStatus, bareSpec); \
+ TEST_PRESENCE_SPEC(spec, true, specStatus, specType, specMaySetOnSelf, specCanHaveMessage); \
+}
+
+#define TEST_PRESENCE_SPEC(spec, specValid, specStatus, specType, specMaySetOnSelf, specCanHaveMessage) \
+{ \
+ QVERIFY(spec.isValid() == specValid); \
+ if (specValid) { \
+ QCOMPARE(spec.presence(), Presence(specType, specStatus, QString())); \
+ TEST_PRESENCE(spec.presence(), true, specType, specStatus, QString()); \
+ QCOMPARE(spec.presence(QLatin1String("test message")), Presence(specType, specStatus, QLatin1String("test message"))); \
+ TEST_PRESENCE(spec.presence(QLatin1String("test message")), true, specType, specStatus, QLatin1String("test message")); \
+ } else { \
+ QVERIFY(!spec.presence().isValid()); \
+ } \
+ QCOMPARE(spec.maySetOnSelf(), specMaySetOnSelf); \
+ QCOMPARE(spec.canHaveStatusMessage(), specCanHaveMessage); \
+\
+ if (specValid) { \
+ SimpleStatusSpec bareSpec; \
+ bareSpec.type = specType; \
+ bareSpec.maySetOnSelf = specMaySetOnSelf; \
+ bareSpec.canHaveMessage = specCanHaveMessage; \
+ QCOMPARE(spec.bareSpec(), bareSpec); \
+ } else { \
+ QCOMPARE(spec.bareSpec(), SimpleStatusSpec()); \
+ } \
+}
+
+void TestPresence::testPresenceSpec()
+{
+ PresenceSpec spec;
+ TEST_PRESENCE_SPEC(spec, false, QString(), ConnectionPresenceTypeUnknown, false, false);
+
+ TEST_PRESENCE_SPEC_FULL(QLatin1String("available"), ConnectionPresenceTypeAvailable, true, true);
+ TEST_PRESENCE_SPEC_FULL(QLatin1String("brb"), ConnectionPresenceTypeAway, true, true);
+ TEST_PRESENCE_SPEC_FULL(QLatin1String("away"), ConnectionPresenceTypeAway, true, true);
+ TEST_PRESENCE_SPEC_FULL(QLatin1String("xa"), ConnectionPresenceTypeExtendedAway, false, false);
+ TEST_PRESENCE_SPEC_FULL(QLatin1String("offline"), ConnectionPresenceTypeOffline, true, false);
+}
+
+QTEST_MAIN(TestPresence)
+
+#include "_gen/presence.cpp.moc.hpp"
diff --git a/qt4/tests/profile.cpp b/qt4/tests/profile.cpp
new file mode 100644
index 000000000..7aeea6f72
--- /dev/null
+++ b/qt4/tests/profile.cpp
@@ -0,0 +1,135 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Debug>
+#include <TelepathyQt4/Profile>
+
+using namespace Tp;
+
+class TestProfile : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestProfile(QObject *parent = 0);
+
+private Q_SLOTS:
+ void testProfile();
+};
+
+TestProfile::TestProfile(QObject *parent)
+ : QObject(parent)
+{
+ Tp::enableDebug(true);
+ Tp::enableWarnings(true);
+}
+
+void TestProfile::testProfile()
+{
+ QString top_srcdir = QString::fromLocal8Bit(::getenv("abs_top_srcdir"));
+ if (!top_srcdir.isEmpty()) {
+ QDir::setCurrent(top_srcdir + QLatin1String("/tests"));
+ }
+
+ ProfilePtr profile = Profile::createForServiceName(QLatin1String("test-profile-file-not-found"));
+ QCOMPARE(profile->isValid(), false);
+
+ profile = Profile::createForServiceName(QLatin1String("test-profile-malformed"));
+ QCOMPARE(profile->isValid(), false);
+
+ profile = Profile::createForServiceName(QLatin1String("test-profile-invalid-service-id"));
+ QCOMPARE(profile->isValid(), false);
+
+ profile = Profile::createForServiceName(QLatin1String("test-profile-non-im-type"));
+ QCOMPARE(profile->isValid(), false);
+
+ profile = Profile::createForFileName(QLatin1String("telepathy/profiles/test-profile-non-im-type.profile"));
+ QCOMPARE(profile->isValid(), true);
+
+ profile = Profile::createForServiceName(QLatin1String("test-profile"));
+ QCOMPARE(profile->isValid(), true);
+
+ QCOMPARE(profile->serviceName(), QLatin1String("test-profile"));
+ QCOMPARE(profile->type(), QLatin1String("IM"));
+ QCOMPARE(profile->provider(), QLatin1String("TestProfileProvider"));
+ QCOMPARE(profile->name(), QLatin1String("TestProfile"));
+ QCOMPARE(profile->cmName(), QLatin1String("testprofilecm"));
+ QCOMPARE(profile->protocolName(), QLatin1String("testprofileproto"));
+
+ QCOMPARE(profile->parameters().isEmpty(), false);
+ QCOMPARE(profile->parameters().count(), 2);
+
+ QCOMPARE(profile->hasParameter(QLatin1String("foo")), false);
+
+ QCOMPARE(profile->hasParameter(QLatin1String("server")), true);
+ Profile::Parameter param = profile->parameter(QLatin1String("server"));
+ QCOMPARE(param.name(), QLatin1String("server"));
+ QCOMPARE(param.dbusSignature(), QDBusSignature(QLatin1String("s")));
+ QCOMPARE(param.type(), QVariant::String);
+ QCOMPARE(param.value(), QVariant(QLatin1String("profile.com")));
+ QCOMPARE(param.label(), QString());
+ QCOMPARE(param.isMandatory(), true);
+
+ QCOMPARE(profile->hasParameter(QLatin1String("port")), true);
+ param = profile->parameter(QLatin1String("port"));
+ QCOMPARE(param.name(), QLatin1String("port"));
+ QCOMPARE(param.dbusSignature(), QDBusSignature(QLatin1String("u")));
+ QCOMPARE(param.type(), QVariant::UInt);
+ QCOMPARE(param.value(), QVariant(QLatin1String("1111")));
+ QCOMPARE(param.label(), QString());
+ QCOMPARE(param.isMandatory(), true);
+
+ QCOMPARE(profile->presences().isEmpty(), false);
+ QCOMPARE(profile->presences().count(), 5);
+
+ QCOMPARE(profile->hasPresence(QLatin1String("foo")), false);
+
+ QCOMPARE(profile->hasPresence(QLatin1String("available")), true);
+ Profile::Presence presence = profile->presence(QLatin1String("available"));
+ QCOMPARE(presence.id(), QLatin1String("available"));
+ QCOMPARE(presence.label(), QLatin1String("Online"));
+ QCOMPARE(presence.iconName(), QLatin1String("online"));
+ QCOMPARE(presence.isDisabled(), false);
+
+ QCOMPARE(profile->hasPresence(QLatin1String("offline")), true);
+ presence = profile->presence(QLatin1String("offline"));
+ QCOMPARE(presence.id(), QLatin1String("offline"));
+ QCOMPARE(presence.label(), QLatin1String("Offline"));
+ QCOMPARE(presence.iconName(), QString());
+ QCOMPARE(presence.isDisabled(), false);
+
+ QCOMPARE(profile->hasPresence(QLatin1String("away")), true);
+ presence = profile->presence(QLatin1String("away"));
+ QCOMPARE(presence.id(), QLatin1String("away"));
+ QCOMPARE(presence.label(), QLatin1String("Gone"));
+ QCOMPARE(presence.iconName(), QString());
+ QCOMPARE(presence.isDisabled(), false);
+
+ QCOMPARE(profile->hasPresence(QLatin1String("hidden")), true);
+ presence = profile->presence(QLatin1String("hidden"));
+ QCOMPARE(presence.id(), QLatin1String("hidden"));
+ QCOMPARE(presence.label(), QString());
+ QCOMPARE(presence.iconName(), QString());
+ QCOMPARE(presence.isDisabled(), true);
+
+ QCOMPARE(profile->unsupportedChannelClassSpecs().isEmpty(), false);
+ QCOMPARE(profile->unsupportedChannelClassSpecs().count(), 2);
+
+ RequestableChannelClassSpec rccSpec = profile->unsupportedChannelClassSpecs().first();
+ QCOMPARE(rccSpec.hasTargetHandleType(), true);
+ QCOMPARE(rccSpec.targetHandleType(), HandleTypeContact);
+ QCOMPARE(rccSpec.channelType(), QLatin1String("org.freedesktop.Telepathy.Channel.Type.Text"));
+
+ profile = Profile::createForServiceName(QLatin1String("test-profile-no-icon-and-provider"));
+ QCOMPARE(profile->isValid(), true);
+
+ QCOMPARE(profile->serviceName(), QLatin1String("test-profile-no-icon-and-provider"));
+ QCOMPARE(profile->type(), QLatin1String("IM"));
+ QCOMPARE(profile->provider().isEmpty(), true);
+ QCOMPARE(profile->cmName(), QLatin1String("testprofilecm"));
+ QCOMPARE(profile->protocolName(), QLatin1String("testprofileproto"));
+ QCOMPARE(profile->iconName().isEmpty(), true);
+}
+
+QTEST_MAIN(TestProfile)
+
+#include "_gen/profile.cpp.moc.hpp"
diff --git a/qt4/tests/ptr.cpp b/qt4/tests/ptr.cpp
new file mode 100644
index 000000000..faf8c22c0
--- /dev/null
+++ b/qt4/tests/ptr.cpp
@@ -0,0 +1,151 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/SharedPtr>
+
+using namespace Tp;
+
+class TestSharedPtr : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testSharedPtrDict();
+ void testSharedPtrBoolConversion();
+};
+
+class Data;
+typedef SharedPtr<Data> DataPtr;
+
+class Data : public QObject,
+ public RefCounted
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Data);
+
+public:
+ static DataPtr create() { return DataPtr(new Data()); }
+ static DataPtr createNull() { return DataPtr(0); }
+
+private:
+ Data() {}
+};
+
+void TestSharedPtr::testSharedPtrDict()
+{
+ QHash<DataPtr, int> dict;
+ DataPtr nullPtr = Data::createNull();
+ dict[nullPtr] = 1;
+ QCOMPARE(dict.size(), 1);
+ QCOMPARE(dict[nullPtr], 1);
+
+ DataPtr validPtr1 = Data::create();
+ QCOMPARE(qHash(validPtr1.data()), qHash(validPtr1));
+ dict[validPtr1] = 2;
+ QCOMPARE(dict.size(), 2);
+ QCOMPARE(dict[nullPtr], 1);
+ QCOMPARE(dict[validPtr1], 2);
+
+ DataPtr validPtr2 = validPtr1;
+ QCOMPARE(validPtr1.data(), validPtr2.data());
+ QCOMPARE(qHash(validPtr1), qHash(validPtr2));
+ dict[validPtr2] = 3;
+ QCOMPARE(dict.size(), 2);
+ QCOMPARE(dict[nullPtr], 1);
+ QCOMPARE(dict[validPtr1], 3);
+ QCOMPARE(dict[validPtr2], 3);
+
+ DataPtr validPtrAlternative = Data::create();
+ QVERIFY(validPtr1.data() != validPtrAlternative.data());
+ QVERIFY(validPtr1 != validPtrAlternative);
+ QVERIFY(qHash(validPtr1) != qHash(validPtrAlternative));
+ dict[validPtrAlternative] = 4;
+ QCOMPARE(dict.size(), 3);
+ QCOMPARE(dict[nullPtr], 1);
+ QCOMPARE(dict[validPtr1], 3);
+ QCOMPARE(dict[validPtr2], 3);
+ QCOMPARE(dict[validPtrAlternative], 4);
+}
+
+void TestSharedPtr::testSharedPtrBoolConversion()
+{
+ DataPtr nullPtr1;
+ DataPtr nullPtr2 = Data::createNull();
+ DataPtr validPtr1 = Data::create();
+ DataPtr validPtr2 = validPtr1;
+ DataPtr validPtrAlternative = Data::create();
+
+ // Boolean conditions
+ QVERIFY(!validPtr1.isNull());
+ QVERIFY(nullPtr1.isNull());
+ QVERIFY(validPtr1 ? true : false);
+ QVERIFY(!validPtr1 ? false : true);
+ QVERIFY(nullPtr1 ? false : true);
+ QVERIFY(!nullPtr1 ? true : false);
+ QVERIFY(validPtr1);
+ QVERIFY(!!validPtr1);
+ QVERIFY(!nullPtr1);
+
+ // Supported operators
+ QVERIFY(nullPtr1 == nullPtr1);
+ QVERIFY(nullPtr1 == nullPtr2);
+
+ QVERIFY(validPtr1 == validPtr1);
+ QVERIFY(validPtr1 == validPtr2);
+ QVERIFY(validPtr1 != validPtrAlternative);
+ QCOMPARE(validPtr1 == validPtrAlternative, false);
+
+ QVERIFY(validPtr1 != nullPtr1);
+ QCOMPARE(validPtr1 == nullPtr1, false);
+
+ // Supported conversions, constructors and copy operators
+ bool trueBool1 = validPtr1;
+ QVERIFY(trueBool1);
+ bool trueBool2(validPtr2);
+ QVERIFY(trueBool2);
+ trueBool1 = validPtrAlternative;
+ QVERIFY(trueBool1);
+
+ bool falseBool1 = nullPtr1;
+ QVERIFY(!falseBool1);
+ bool falseBool2(nullPtr2);
+ QVERIFY(!falseBool2);
+ falseBool1 = nullPtr1;
+ QVERIFY(!falseBool1);
+
+#if 0
+ // Unsupported operators, this should not compile
+ bool condition;
+ condition = validPtr1 > nullPtr1;
+ condition = validPtr1 + nullPtr1;
+
+ // Unsupported conversions, this should not compile
+ int validInt1 = validPtr1;
+ int validInt2(validPtr1);
+ validInt1 = validPtr1;
+
+ int nullInt1 = nullPtr1;
+ int nullInt2(nullPtr1);
+ nullInt1 = nullPtr1;
+
+ float validFloat1 = validPtr1;
+ float validFloat2(validPtr1);
+ validFloat1 = validPtr1;
+
+ float nullFloat1 = nullPtr1;
+ float nullFloat2(nullPtr1);
+ nullFloat1 = nullPtr1;
+
+ Q_UNUSED(validInt1);
+ Q_UNUSED(validInt2);
+ Q_UNUSED(nullInt1);
+ Q_UNUSED(nullInt2);
+ Q_UNUSED(validFloat1);
+ Q_UNUSED(validFloat2);
+ Q_UNUSED(nullFloat1);
+ Q_UNUSED(nullFloat2);
+#endif
+}
+
+QTEST_MAIN(TestSharedPtr)
+
+#include "_gen/ptr.cpp.moc.hpp"
diff --git a/qt4/tests/rccspec.cpp b/qt4/tests/rccspec.cpp
new file mode 100644
index 000000000..d4e8b1d4e
--- /dev/null
+++ b/qt4/tests/rccspec.cpp
@@ -0,0 +1,141 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/RequestableChannelClassSpec>
+
+using namespace Tp;
+
+class TestRCCSpec : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testRCCSpec();
+};
+
+void TestRCCSpec::testRCCSpec()
+{
+ RequestableChannelClassSpec spec;
+
+ spec = RequestableChannelClassSpec::textChat();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QCOMPARE(spec.targetHandleType(), HandleTypeContact);
+ QCOMPARE(spec.fixedProperties().size(), 2);
+ QCOMPARE(spec.allowedProperties().isEmpty(), true);
+
+ spec = RequestableChannelClassSpec::textChatroom();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QCOMPARE(spec.targetHandleType(), HandleTypeRoom);
+ QCOMPARE(spec.fixedProperties().size(), 2);
+ QCOMPARE(spec.allowedProperties().isEmpty(), true);
+
+ spec = RequestableChannelClassSpec::streamedMediaCall();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ QCOMPARE(spec.targetHandleType(), HandleTypeContact);
+ QCOMPARE(spec.fixedProperties().size(), 2);
+ QCOMPARE(spec.allowedProperties().isEmpty(), true);
+
+ spec = RequestableChannelClassSpec::streamedMediaAudioCall();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ QCOMPARE(spec.targetHandleType(), HandleTypeContact);
+ QCOMPARE(spec.fixedProperties().size(), 2);
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialAudio")), true);
+
+ spec = RequestableChannelClassSpec::streamedMediaVideoCall();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ QCOMPARE(spec.targetHandleType(), HandleTypeContact);
+ QCOMPARE(spec.fixedProperties().size(), 2);
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialVideo")), true);
+
+ spec = RequestableChannelClassSpec::streamedMediaVideoCallWithAudio();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ QCOMPARE(spec.targetHandleType(), HandleTypeContact);
+ QCOMPARE(spec.fixedProperties().size(), 2);
+ QCOMPARE(spec.allowedProperties().size(), 2);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialAudio")), true);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialVideo")), true);
+
+ spec = RequestableChannelClassSpec::conferenceTextChat();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QVERIFY(!spec.hasTargetHandleType());
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")), true);
+
+ spec = RequestableChannelClassSpec::conferenceTextChatWithInvitees();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QVERIFY(!spec.hasTargetHandleType());
+ QCOMPARE(spec.allowedProperties().size(), 2);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")), true);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")), true);
+
+ spec = RequestableChannelClassSpec::conferenceTextChatroom();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QCOMPARE(spec.targetHandleType(), HandleTypeRoom);
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")), true);
+
+ spec = RequestableChannelClassSpec::conferenceTextChatroomWithInvitees();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ QCOMPARE(spec.targetHandleType(), HandleTypeRoom);
+ QCOMPARE(spec.allowedProperties().size(), 2);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")), true);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")), true);
+
+ spec = RequestableChannelClassSpec::conferenceStreamedMediaCall();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ QVERIFY(!spec.hasTargetHandleType());
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")), true);
+
+ spec = RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ QVERIFY(!spec.hasTargetHandleType());
+ QCOMPARE(spec.allowedProperties().size(), 2);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")), true);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")), true);
+
+ spec = RequestableChannelClassSpec::contactSearch();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ QCOMPARE(spec.fixedProperties().size(), 1);
+ QCOMPARE(spec.allowedProperties().isEmpty(), true);
+
+ spec = RequestableChannelClassSpec::contactSearchWithSpecificServer();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ QCOMPARE(spec.fixedProperties().size(), 1);
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Server")), true);
+
+ spec = RequestableChannelClassSpec::contactSearchWithLimit();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ QCOMPARE(spec.fixedProperties().size(), 1);
+ QCOMPARE(spec.allowedProperties().size(), 1);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Limit")), true);
+
+ spec = RequestableChannelClassSpec::contactSearchWithSpecificServerAndLimit();
+ QCOMPARE(spec.channelType(), TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH);
+ QCOMPARE(spec.fixedProperties().size(), 1);
+ QCOMPARE(spec.allowedProperties().size(), 2);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Server")), true);
+ QCOMPARE(spec.allowsProperty(TP_QT4_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Limit")), true);
+
+ QCOMPARE(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio().supports(
+ RequestableChannelClassSpec::streamedMediaVideoCall()), true);
+ QCOMPARE(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio().supports(
+ RequestableChannelClassSpec::streamedMediaAudioCall()), true);
+ QCOMPARE(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio().supports(
+ RequestableChannelClassSpec::streamedMediaCall()), true);
+
+ QCOMPARE(RequestableChannelClassSpec::textChat() == RequestableChannelClassSpec::textChatroom(), false);
+
+ RequestableChannelClass rcc;
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".ChannelType"),
+ TP_QT4_IFACE_CHANNEL_TYPE_TEXT);
+ rcc.fixedProperties.insert(TP_QT4_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
+ (uint) HandleTypeContact);
+ spec = RequestableChannelClassSpec(rcc);
+ QCOMPARE(RequestableChannelClassSpec::textChat() == spec, true);
+}
+
+QTEST_MAIN(TestRCCSpec)
+#include "_gen/rccspec.cpp.moc.hpp"
diff --git a/qt4/tests/telepathy/managers/spurious.manager b/qt4/tests/telepathy/managers/spurious.manager
new file mode 100644
index 000000000..1ae67c066
--- /dev/null
+++ b/qt4/tests/telepathy/managers/spurious.manager
@@ -0,0 +1,23 @@
+[ConnectionManager]
+
+[Protocol normal]
+param-account=s required register
+param-password=s required register secret
+param-register=b
+default-register=true
+
+RequestableChannelClasses=text
+
+status-available=2 settable message
+status-offline=1 settable
+status-away=3 settable
+status-xa=4
+
+[text]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
+
+[Protocol weird]
+param-com.example.Bork.Bork.Bork=u dbus-property
+default-com.example.Bork.Bork.Bork=42
diff --git a/qt4/tests/telepathy/managers/test-manager-file-invalid-signature.manager b/qt4/tests/telepathy/managers/test-manager-file-invalid-signature.manager
new file mode 100644
index 000000000..5c1434fc0
--- /dev/null
+++ b/qt4/tests/telepathy/managers/test-manager-file-invalid-signature.manager
@@ -0,0 +1,86 @@
+[Protocol foo]
+param-account = s required
+param-password = s required
+param-encryption-key = s secret
+param-port = q
+param-register = b
+param-server-list = as
+default-account = foo@default
+default-port = 1234
+default-server-list = foo;bar;
+
+[Protocol bar]
+param-account = s required
+param-encryption-key = s required secret
+param-password = s required
+param-port = q
+param-register = b
+param-server-list = as
+default-account = bar@default
+default-port = 4321
+default-server-list = bar;foo;
+
+[Protocol somewhat-pathological]
+# the value is "hello world"
+param-foo = s required
+default-foo = hello world
+
+# the value is "list;of;misc;" (it's not parsed as a list)
+param-semicolons=s secret
+default-semicolons=list;of;misc;
+
+# the values is a list ["list", "of", "misc"]
+param-list = as
+default-list = list;of;misc;
+
+# the spec says this is invalid but we should probably be permissive
+param-unterminated-list = as
+default-unterminated-list = list;of;misc
+
+# the value is a list ["list", " of", " misc "] (spaces significant)
+param-spaces-in-list = as
+default-spaces-in-list = list; of; misc ;
+
+# the value is a list ["list;of", "misc"]
+param-escaped-semicolon-in-list = as
+default-escaped-semicolon-in-list = list\;of;misc;
+
+# the value is a list ["list\", "of", "misc"]
+param-doubly-escaped-semicolon-in-list = as
+default-doubly-escaped-semicolon-in-list = list\\;of;misc;
+
+# the value is a list ["list\;of", "misc"]
+param-triply-escaped-semicolon-in-list = as
+default-triply-escaped-semicolon-in-list = list\\\;of;misc;
+
+# the value is an empty list
+param-empty-list = as
+default-empty-list =
+
+# the value is a list of empty string
+param-list-of-empty-string = as
+default-list-of-empty-string = ;
+
+# this is probably technically a Desktop Entry spec violation?
+# we should be permissive, interpreting this as either "foo\;bar" or "foo;bar"
+# seems reasonable
+param-escaped-semicolon = s
+default-escaped-semicolon = foo\;bar
+
+# all the other types
+param-object = o
+default-object = /misc
+param-q = q
+default-q = 42
+param-u = u
+default-u = 42
+param-t = t
+default-t = 42
+param-n = n
+default-n = -42
+param-i = i
+default-i = -42
+param-x = x
+default-x = -42
+param-d = 42.0
+default-d = 42.0
diff --git a/qt4/tests/telepathy/managers/test-manager-file-malformed-keyfile.manager b/qt4/tests/telepathy/managers/test-manager-file-malformed-keyfile.manager
new file mode 100644
index 000000000..6195fc820
--- /dev/null
+++ b/qt4/tests/telepathy/managers/test-manager-file-malformed-keyfile.manager
@@ -0,0 +1,4 @@
+[Protocol foo
+
+[Protocol bar
+param-account = s required = =
diff --git a/qt4/tests/telepathy/managers/test-manager-file.manager b/qt4/tests/telepathy/managers/test-manager-file.manager
new file mode 100644
index 000000000..af8384ebc
--- /dev/null
+++ b/qt4/tests/telepathy/managers/test-manager-file.manager
@@ -0,0 +1,115 @@
+[Protocol foo]
+param-account = s required
+param-password = s required
+param-encryption-key = s secret
+param-port = q
+param-register = b
+param-server-list = as
+default-account = foo@default
+default-port = 1234
+default-server-list = foo;bar;
+
+status-offline=1
+status-dnd=6 settable
+status-available=2 settable message
+
+SupportedAvatarMIMETypes=image/png;image/jpeg;image/gif;
+MinimumAvatarHeight=32
+RecommendedAvatarHeight=64
+MaximumAvatarHeight=96
+MinimumAvatarWidth=32
+RecommendedAvatarWidth=64
+MaximumAvatarWidth=96
+MaximumAvatarBytes=8192
+
+VCardField=x-foo
+EnglishName=Foo
+Icon=im-foo
+RequestableChannelClasses=ft;foo text;
+
+[ft]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.FileTransfer
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Type.FileTransfer.ContentHashType u=1
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
+
+[foo text]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Text
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+allowed=org.freedesktop.Telepathy.Channel.TargetHandle;org.freedesktop.Telepathy.Channel.TargetID;
+
+[Protocol bar]
+param-account = s required
+param-encryption-key = s required secret
+param-password = s required
+param-port = q
+param-register = b
+param-server-list = as
+default-account = bar@default
+default-port = 4321
+default-server-list = bar;foo;
+
+[Protocol somewhat-pathological]
+# the value is "hello world"
+param-foo = s required
+default-foo = hello world
+
+# the value is "list;of;misc;" (it's not parsed as a list)
+param-semicolons=s secret
+default-semicolons=list;of;misc;
+
+# the values is a list ["list", "of", "misc"]
+param-list = as
+default-list = list;of;misc;
+
+# the spec says this is invalid but we should probably be permissive
+param-unterminated-list = as
+default-unterminated-list = list;of;misc
+
+# the value is a list ["list", " of", " misc "] (spaces significant)
+param-spaces-in-list = as
+default-spaces-in-list = list; of; misc ;
+
+# the value is a list ["list;of", "misc"]
+param-escaped-semicolon-in-list = as
+default-escaped-semicolon-in-list = list\;of;misc;
+
+# the value is a list ["list\", "of", "misc"]
+param-doubly-escaped-semicolon-in-list = as
+default-doubly-escaped-semicolon-in-list = list\\;of;misc;
+
+# the value is a list ["list\;of", "misc"]
+param-triply-escaped-semicolon-in-list = as
+default-triply-escaped-semicolon-in-list = list\\\;of;misc;
+
+# the value is an empty list
+param-empty-list = as
+default-empty-list =
+
+# the value is a list of empty string
+param-list-of-empty-string = as
+default-list-of-empty-string = ;
+
+# this is probably technically a Desktop Entry spec violation?
+# we should be permissive, interpreting this as either "foo\;bar" or "foo;bar"
+# seems reasonable
+param-escaped-semicolon = s
+default-escaped-semicolon = foo\;bar
+
+# all the other types
+param-object = o
+default-object = /misc
+param-q = q
+default-q = 42
+param-u = u
+default-u = 42
+param-t = t
+default-t = 42
+param-n = n
+default-n = -42
+param-i = i
+default-i = -42
+param-x = x
+default-x = -42
+param-d = d
+default-d = 42.0
diff --git a/qt4/tests/telepathy/profiles/test-profile-invalid-service-id.profile b/qt4/tests/telepathy/profiles/test-profile-invalid-service-id.profile
new file mode 100644
index 000000000..ec6dfe121
--- /dev/null
+++ b/qt4/tests/telepathy/profiles/test-profile-invalid-service-id.profile
@@ -0,0 +1,33 @@
+<service xmlns="http://telepathy.freedesktop.org/wiki/service-profile-v1"
+ id="another-test-profile"
+ type="IM"
+ provider="TestProfileProvider"
+ manager="testprofilecm"
+ protocol="testprofileproto"
+ icon="test-profile-icon">
+
+ <name>TestProfile</name>
+
+ <parameters>
+ <parameter name="server" type="s" mandatory="1">profile.com</parameter>
+ <parameter name="port" type="u" mandatory="1">1111</parameter>
+ </parameters>
+
+ <presences allow-others="1">
+ <presence id="available" label="Online" icon="online"/>
+ <presence id="offline" label="Offline"/>
+ <presence id="away" label="Gone"/>
+ <presence id="hidden" disabled="1"/>
+ </presences>
+
+ <unsupported-channel-classes>
+ <!-- this service doesn't support text roomlists -->
+ <channel-class>
+ <property name="org.freedesktop.Telepathy.Channel.TargetHandleType"
+ type="u">3</property>
+ <property name="org.freedesktop.Telepathy.Channel.ChannelType"
+ type="s">org.freedesktop.Telepathy.Channel.Type.Text</property>
+ </channel-class>
+ </unsupported-channel-classes>
+
+</service>
diff --git a/qt4/tests/telepathy/profiles/test-profile-malformed.profile b/qt4/tests/telepathy/profiles/test-profile-malformed.profile
new file mode 100644
index 000000000..a3ff45148
--- /dev/null
+++ b/qt4/tests/telepathy/profiles/test-profile-malformed.profile
@@ -0,0 +1,4 @@
+<service xmlns="http://telepathy.freedesktop.org/wiki/service-profile-v1"
+ id="test-profile-malformed">
+
+</service
diff --git a/qt4/tests/telepathy/profiles/test-profile-no-icon-and-provider.profile b/qt4/tests/telepathy/profiles/test-profile-no-icon-and-provider.profile
new file mode 100644
index 000000000..17c444c96
--- /dev/null
+++ b/qt4/tests/telepathy/profiles/test-profile-no-icon-and-provider.profile
@@ -0,0 +1,7 @@
+<service xmlns="http://telepathy.freedesktop.org/wiki/service-profile-v1"
+ id="test-profile-no-icon-and-provider"
+ type="IM"
+ manager="testprofilecm"
+ protocol="testprofileproto">
+
+</service>
diff --git a/qt4/tests/telepathy/profiles/test-profile-non-im-type.profile b/qt4/tests/telepathy/profiles/test-profile-non-im-type.profile
new file mode 100644
index 000000000..f77a12036
--- /dev/null
+++ b/qt4/tests/telepathy/profiles/test-profile-non-im-type.profile
@@ -0,0 +1,11 @@
+<service xmlns="http://telepathy.freedesktop.org/wiki/service-profile-v1"
+ id="test-profile-non-im-type"
+ type="AnotherType"
+ provider="TestProfileProvider"
+ manager="testprofilecm"
+ protocol="testprofileproto"
+ icon="test-profile-icon">
+
+ <name>TestProfile</name>
+
+</service>
diff --git a/qt4/tests/telepathy/profiles/test-profile.profile b/qt4/tests/telepathy/profiles/test-profile.profile
new file mode 100644
index 000000000..45ee8730c
--- /dev/null
+++ b/qt4/tests/telepathy/profiles/test-profile.profile
@@ -0,0 +1,39 @@
+<service xmlns="http://telepathy.freedesktop.org/wiki/service-profile-v1"
+ id="test-profile"
+ type="IM"
+ provider="TestProfileProvider"
+ manager="testprofilecm"
+ protocol="testprofileproto"
+ icon="test-profile-icon">
+ <name>TestProfile</name>
+
+ <parameters>
+ <parameter name="server" type="s" mandatory="1">profile.com</parameter>
+ <parameter name="port" type="u" mandatory="1">1111</parameter>
+ </parameters>
+
+ <presences allow-others="1">
+ <presence id="available" label="Online" icon="online" message="true"/>
+ <presence id="offline" label="Offline"/>
+ <presence id="away" label="Gone" message="true"/>
+ <presence id="xa" label="Extended Away"/>
+ <presence id="hidden" disabled="1"/>
+ </presences>
+
+ <unsupported-channel-classes>
+ <!-- this service doesn't support text chats and roomlists -->
+ <channel-class>
+ <property name="org.freedesktop.Telepathy.Channel.TargetHandleType"
+ type="u">1</property>
+ <property name="org.freedesktop.Telepathy.Channel.ChannelType"
+ type="s">org.freedesktop.Telepathy.Channel.Type.Text</property>
+ </channel-class>
+ <channel-class>
+ <property name="org.freedesktop.Telepathy.Channel.TargetHandleType"
+ type="u">3</property>
+ <property name="org.freedesktop.Telepathy.Channel.ChannelType"
+ type="s">org.freedesktop.Telepathy.Channel.Type.Text</property>
+ </channel-class>
+ </unsupported-channel-classes>
+
+</service>
diff --git a/qt4/tests/test-key-file-format-error.ini b/qt4/tests/test-key-file-format-error.ini
new file mode 100644
index 000000000..5197c5778
--- /dev/null
+++ b/qt4/tests/test-key-file-format-error.ini
@@ -0,0 +1,4 @@
+a 1
+b=2
+
+[test group 1
diff --git a/qt4/tests/test-key-file.ini b/qt4/tests/test-key-file.ini
new file mode 100644
index 000000000..e38f70f60
--- /dev/null
+++ b/qt4/tests/test-key-file.ini
@@ -0,0 +1,9 @@
+a=1
+b=2
+
+[test group 1]
+c=\s\t\n\r\\
+d=true
+
+[test group 2]
+e = space
diff --git a/qt4/tests/utils.cpp b/qt4/tests/utils.cpp
new file mode 100644
index 000000000..30732bd87
--- /dev/null
+++ b/qt4/tests/utils.cpp
@@ -0,0 +1,34 @@
+#include <QtTest/QtTest>
+
+#include <TelepathyQt4/Utils>
+
+using namespace Tp;
+
+class TestUtils : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testUtils();
+};
+
+void TestUtils::testUtils()
+{
+ QString res;
+
+ res = escapeAsIdentifier(QString::fromLatin1(""));
+ QCOMPARE(res, QString::fromLatin1("_"));
+
+ res = escapeAsIdentifier(QString::fromLatin1("badger"));
+ QCOMPARE(res, QString::fromLatin1("badger"));
+
+ res = escapeAsIdentifier(QString::fromLatin1("0123abc_xyz"));
+ QCOMPARE(res, QString::fromLatin1("_30123abc_5fxyz"));
+
+ res = escapeAsIdentifier(QString::fromUtf8("©"));
+ QCOMPARE(res, QString::fromLatin1("_c2_a9"));
+}
+
+QTEST_MAIN(TestUtils)
+
+#include "_gen/utils.cpp.moc.hpp"
diff --git a/qt4/tools/CMakeLists.txt b/qt4/tools/CMakeLists.txt
new file mode 100644
index 000000000..3c023216a
--- /dev/null
+++ b/qt4/tools/CMakeLists.txt
@@ -0,0 +1,124 @@
+# Some useful commands
+add_custom_command(OUTPUT FIXME.out
+
+ COMMAND egrep
+
+ ARGS -A 5 '[F]IXME|[T]ODO|[X]XX' ${CMAKE_SOURCE_DIR}/TelepathyQt4/*.[ch]*
+ ${CMAKE_SOURCE_DIR}/TelepathyQt4/*.[ch]*
+ > FIXME.out || true)
+add_custom_target(check-local DEPENDS FIXME.out)
+
+execute_process(COMMAND ${SH} tools/git-which-branch.sh misc | tr -d '\n' | tr -C "[:alnum:]" _
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_BRANCH_CURRENT)
+
+if (GIT_BRANCH_CURRENT)
+ string(LENGTH ${GIT_BRANCH_CURRENT} HAVE_GIT_BRANCH)
+
+ if (HAVE_GIT_BRANCH)
+ string(REPLACE "\n" "" GIT_BRANCH_CURRENT ${GIT_BRANCH_CURRENT})
+
+ set(UPLOAD_BRANCH_TO people.freedesktop.org:public_html/telepathy-qt4)
+
+ add_custom_target(upload-branch-docs rsync -rtzvPp --chmod=a+rX doc/html/ ${UPLOAD_BRANCH_TO}-${GIT_BRANCH_CURRENT}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+ add_dependencies(upload-branch-docs doxygen-doc)
+ endif (HAVE_GIT_BRANCH)
+endif (GIT_BRANCH_CURRENT)
+
+if (PERL_FOUND)
+ add_custom_target(maintainer-fix-qt-links-in-docs
+ ${PERL_EXECUTABLE} doc/html/installdox -l qt.tags@http://doc.qt.nokia.com/latest/ doc/html/*.html
+
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+ add_dependencies(maintainer-fix-qt-links-in-docs doxygen-doc _maintainer-upload-release-check)
+endif (PERL_FOUND)
+
+add_custom_target(maintainer-upload-release-docs
+ rsync -rtOvzPp --chmod=Dg+s,ug+rwX,o=rX doc/html/ telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/doc/telepathy-qt4/
+
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+
+if (PERL_FOUND)
+ add_dependencies(maintainer-upload-release-docs maintainer-fix-qt-links-in-docs)
+else (PERL_FOUND)
+ add_dependencies(maintainer-upload-release-docs doxygen-doc _maintainer-upload-release-check)
+endif (PERL_FOUND)
+
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/maintainer-upload-release-check.sh "
+#!/bin/sh
+case ${PACKAGE_VERSION} in
+ (*.*.*.*)
+ echo \"${PACKAGE_VERSION} is not a release\" >&2;
+ exit 2;
+ ;;
+esac
+test -f ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+if ! test -f ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz.asc; then
+ gpg --detach-sign -a ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz;
+fi;
+gpg --verify ${CMAKE_BINARY_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz.asc
+")
+
+add_custom_target(_maintainer-upload-release-check ${SH} ${CMAKE_CURRENT_BINARY_DIR}/maintainer-upload-release-check.sh)
+
+add_custom_target(maintainer-upload-release
+ rsync -vzP ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/${PACKAGE_NAME}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz
+ COMMAND
+ rsync -vzP ${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz.asc
+telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/${PACKAGE_NAME}/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz.asc
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+
+add_dependencies(maintainer-upload-release _maintainer-upload-release-check maintainer-upload-release-docs)
+
+
+set(toolchain_files
+ c-constants-gen.py
+ check-misc.sh
+ check-whitespace.sh
+ git-which-branch.sh
+ glib-ginterface-gen.py
+ glib-gtypes-generator.py
+ glib-interfaces-gen.py
+ glib-signals-marshal-gen.py
+ libtpcodegen.py
+ libglibcodegen.py
+ libqt4codegen.py
+ qt4-client-gen.py
+ qt4-constants-gen.py
+ qt4-types-gen.py
+ manager-file.py
+ with-session-bus.sh
+ xincludator.py
+)
+
+string(REPLACE "." " " sh_toolchain_files ${toolchain_files})
+
+set(TELEPATHY_SPEC_SRCDIR ${CMAKE_SOURCE_DIR}/../telepathy-spec)
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/maintainer-update-from-telepathy-spec.sh "
+#!/bin/sh
+set -e
+cd ${CMAKE_SOURCE_DIR}
+for x in ${sh_toolchain_files}; do
+ if test -f ${TELEPATHY_SPEC_SRCDIR}/tools/$$x; then
+ cp ${TELEPATHY_SPEC_SRCDIR}/tools/$$x $$x;
+ fi;
+done
+")
+add_custom_target(maintainer-update-from-telepathy-spec ${SH} ${CMAKE_CURRENT_BINARY_DIR}/maintainer-update-from-telepathy-spec.sh
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
+
+set(TELEPATHY_GLIB_SRCDIR ${CMAKE_SOURCE_DIR}/../telepathy-glib)
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/maintainer-update-from-telepathy-glib.sh "
+#!/bin/sh
+set -e
+cd ${CMAKE_SOURCE_DIR}
+for x in ${sh_toolchain_files}; do
+ if test -f ${TELEPATHY_GLIB_SRCDIR}/tools/$$x; then
+ cp ${TELEPATHY_GLIB_SRCDIR}/tools/$$x $$x;
+ fi;
+done
+")
+add_custom_target(maintainer-update-from-telepathy-glib ${SH} ${CMAKE_CURRENT_BINARY_DIR}/maintainer-update-from-telepathy-glib.sh
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
diff --git a/qt4/tools/c-constants-gen.py b/qt4/tools/c-constants-gen.py
new file mode 100644
index 000000000..8969ffdca
--- /dev/null
+++ b/qt4/tools/c-constants-gen.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+
+from sys import argv, stdout, stderr
+import xml.dom.minidom
+
+from libglibcodegen import NS_TP, get_docstring, \
+ get_descendant_text, get_by_path
+
+class Generator(object):
+ def __init__(self, prefix, dom):
+ self.prefix = prefix + '_'
+ self.spec = get_by_path(dom, "spec")[0]
+
+ def __call__(self):
+ self.do_header()
+ self.do_body()
+ self.do_footer()
+
+ def write(self, code):
+ stdout.write(code.encode('utf-8'))
+
+ # Header
+ def do_header(self):
+ self.write('/* Generated from ')
+ self.write(get_descendant_text(get_by_path(self.spec, 'title')))
+ version = get_by_path(self.spec, "version")
+ if version:
+ self.write(', version ' + get_descendant_text(version))
+ self.write('\n\n')
+ for copyright in get_by_path(self.spec, 'copyright'):
+ self.write(get_descendant_text(copyright))
+ self.write('\n')
+ self.write(get_descendant_text(get_by_path(self.spec, 'license')))
+ self.write('\n')
+ self.write(get_descendant_text(get_by_path(self.spec, 'docstring')))
+ self.write("""
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+\n""")
+
+ # Body
+ def do_body(self):
+ for elem in self.spec.getElementsByTagNameNS(NS_TP, '*'):
+ if elem.localName == 'flags':
+ self.do_flags(elem)
+ elif elem.localName == 'enum':
+ self.do_enum(elem)
+
+ def do_flags(self, flags):
+ name = flags.getAttribute('plural') or flags.getAttribute('name')
+ value_prefix = flags.getAttribute('singular') or \
+ flags.getAttribute('value-prefix') or \
+ flags.getAttribute('name')
+ self.write("""\
+/**
+ *
+%s:
+""" % (self.prefix + name).replace('_', ''))
+ for flag in get_by_path(flags, 'flag'):
+ self.do_gtkdoc(flag, value_prefix)
+ self.write(' *\n')
+ docstrings = get_by_path(flags, 'docstring')
+ if docstrings:
+ self.write("""\
+ * <![CDATA[%s]]>
+ *
+""" % get_descendant_text(docstrings).replace('\n', ' '))
+ self.write("""\
+ * Bitfield/set of flags generated from the Telepathy specification.
+ */
+typedef enum {
+""")
+ for flag in get_by_path(flags, 'flag'):
+ self.do_val(flag, value_prefix)
+ self.write("""\
+} %s;
+
+""" % (self.prefix + name).replace('_', ''))
+
+ def do_enum(self, enum):
+ name = enum.getAttribute('singular') or enum.getAttribute('name')
+ value_prefix = enum.getAttribute('singular') or \
+ enum.getAttribute('value-prefix') or \
+ enum.getAttribute('name')
+ name_plural = enum.getAttribute('plural') or \
+ enum.getAttribute('name') + 's'
+ self.write("""\
+/**
+ *
+%s:
+""" % (self.prefix + name).replace('_', ''))
+ vals = get_by_path(enum, 'enumvalue')
+ for val in vals:
+ self.do_gtkdoc(val, value_prefix)
+ self.write(' *\n')
+ docstrings = get_by_path(enum, 'docstring')
+ if docstrings:
+ self.write("""\
+ * <![CDATA[%s]]>
+ *
+""" % get_descendant_text(docstrings).replace('\n', ' '))
+ self.write("""\
+ * Bitfield/set of flags generated from the Telepathy specification.
+ */
+typedef enum {
+""")
+ for val in vals:
+ self.do_val(val, value_prefix)
+ self.write("""\
+} %(mixed-name)s;
+
+/**
+ * NUM_%(upper-plural)s:
+ *
+ * 1 higher than the highest valid value of #%(mixed-name)s.
+ */
+#define NUM_%(upper-plural)s (%(last-val)s+1)
+
+""" % {'mixed-name' : (self.prefix + name).replace('_', ''),
+ 'upper-plural' : (self.prefix + name_plural).upper(),
+ 'last-val' : vals[-1].getAttribute('value')})
+
+ def do_val(self, val, value_prefix):
+ name = val.getAttribute('name')
+ suffix = val.getAttribute('suffix')
+ use_name = (self.prefix + value_prefix + '_' + \
+ (suffix or name)).upper()
+ assert not (name and suffix) or name == suffix, \
+ 'Flag/enumvalue name %s != suffix %s' % (name, suffix)
+ self.write(' %s = %s,\n' % (use_name, val.getAttribute('value')))
+
+ def do_gtkdoc(self, node, value_prefix):
+ self.write(' * @')
+ self.write((self.prefix + value_prefix + '_' +
+ node.getAttribute('suffix')).upper())
+ self.write(': <![CDATA[')
+ docstring = get_by_path(node, 'docstring')
+ self.write(get_descendant_text(docstring).replace('\n', ' '))
+ self.write(']]>\n')
+
+ # Footer
+ def do_footer(self):
+ self.write("""
+#ifdef __cplusplus
+}
+#endif
+""")
+
+if __name__ == '__main__':
+ argv = argv[1:]
+ Generator(argv[0], xml.dom.minidom.parse(argv[1]))()
diff --git a/qt4/tools/check-misc.sh b/qt4/tools/check-misc.sh
new file mode 100644
index 000000000..89e8e871a
--- /dev/null
+++ b/qt4/tools/check-misc.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+fail=0
+
+( . "${tools_dir}"/check-whitespace.sh ) || fail=$?
+
+if egrep '(Free\s*Software\s*Foundation.*02139|02111-1307)' "$@"
+then
+ echo "^^^ The above files contain the FSF's old address in GPL headers"
+ fail=1
+fi
+
+exit $fail
diff --git a/qt4/tools/check-whitespace.sh b/qt4/tools/check-whitespace.sh
new file mode 100644
index 000000000..534833126
--- /dev/null
+++ b/qt4/tools/check-whitespace.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+fail=0
+
+if grep -n ' $' "$@"
+then
+ echo "^^^ The above files contain unwanted trailing spaces"
+ fail=1
+fi
+
+if grep -n ' ' "$@"
+then
+ echo "^^^ The above files contain tabs"
+ fail=1
+fi
+
+exit $fail
diff --git a/qt4/tools/git-which-branch.sh b/qt4/tools/git-which-branch.sh
new file mode 100644
index 000000000..b96b5d5e2
--- /dev/null
+++ b/qt4/tools/git-which-branch.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# git-which-branch.sh - output the name of the current git branch
+#
+# The canonical location of this program is the telepathy-spec tools/
+# directory, please synchronize any changes with that copy.
+#
+# Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+default="$1"
+if { ref="`git symbolic-ref HEAD 2>/dev/null`"; }; then
+ echo ${ref#refs/heads/}
+ exit 0
+fi
+
+if test -n "$default"; then
+ echo "$default" >/dev/null
+ exit 0
+fi
+
+echo "no git branch found" >&2
+exit 1
diff --git a/qt4/tools/glib-ginterface-gen.py b/qt4/tools/glib-ginterface-gen.py
new file mode 100644
index 000000000..13f7f69eb
--- /dev/null
+++ b/qt4/tools/glib-ginterface-gen.py
@@ -0,0 +1,802 @@
+#!/usr/bin/python
+
+# glib-ginterface-gen.py: service-side interface generator
+#
+# Generate dbus-glib 0.x service GInterfaces from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import os.path
+import xml.dom.minidom
+
+from libglibcodegen import Signature, type_to_gtype, cmp_by_name, \
+ NS_TP, dbus_gutils_wincaps_to_uscore, \
+ signal_to_marshal_name, method_to_glue_marshal_name
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+class Generator(object):
+
+ def __init__(self, dom, prefix, basename, signal_marshal_prefix,
+ headers, end_headers, not_implemented_func,
+ allow_havoc):
+ self.dom = dom
+ self.__header = []
+ self.__body = []
+
+ assert prefix.endswith('_')
+ assert not signal_marshal_prefix.endswith('_')
+
+ # The main_prefix, sub_prefix thing is to get:
+ # FOO_ -> (FOO_, _)
+ # FOO_SVC_ -> (FOO_, _SVC_)
+ # but
+ # FOO_BAR/ -> (FOO_BAR_, _)
+ # FOO_BAR/SVC_ -> (FOO_BAR_, _SVC_)
+
+ if '/' in prefix:
+ main_prefix, sub_prefix = prefix.upper().split('/', 1)
+ prefix = prefix.replace('/', '_')
+ else:
+ main_prefix, sub_prefix = prefix.upper().split('_', 1)
+
+ self.MAIN_PREFIX_ = main_prefix + '_'
+ self._SUB_PREFIX_ = '_' + sub_prefix
+
+ self.Prefix_ = prefix
+ self.Prefix = prefix.replace('_', '')
+ self.prefix_ = prefix.lower()
+ self.PREFIX_ = prefix.upper()
+
+ self.basename = basename
+ self.signal_marshal_prefix = signal_marshal_prefix
+ self.headers = headers
+ self.end_headers = end_headers
+ self.not_implemented_func = not_implemented_func
+ self.allow_havoc = allow_havoc
+
+ def h(self, s):
+ self.__header.append(s)
+
+ def b(self, s):
+ self.__body.append(s)
+
+ def do_node(self, node):
+ node_name = node.getAttribute('name').replace('/', '')
+ node_name_mixed = self.node_name_mixed = node_name.replace('_', '')
+ node_name_lc = self.node_name_lc = node_name.lower()
+ node_name_uc = self.node_name_uc = node_name.upper()
+
+ interfaces = node.getElementsByTagName('interface')
+ assert len(interfaces) == 1, interfaces
+ interface = interfaces[0]
+ self.iface_name = interface.getAttribute('name')
+
+ tmp = interface.getAttribute('tp:implement-service')
+ if tmp == "no":
+ return
+
+ tmp = interface.getAttribute('tp:causes-havoc')
+ if tmp and not self.allow_havoc:
+ raise AssertionError('%s is %s' % (self.iface_name, tmp))
+
+ self.b('static const DBusGObjectInfo _%s%s_object_info;'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ methods = interface.getElementsByTagName('method')
+ signals = interface.getElementsByTagName('signal')
+ properties = interface.getElementsByTagName('property')
+ # Don't put properties in dbus-glib glue
+ glue_properties = []
+
+ self.b('struct _%s%sClass {' % (self.Prefix, node_name_mixed))
+ self.b(' GTypeInterface parent_class;')
+ for method in methods:
+ self.b(' %s %s;' % self.get_method_impl_names(method))
+ self.b('};')
+ self.b('')
+
+ if signals:
+ self.b('enum {')
+ for signal in signals:
+ self.b(' %s,' % self.get_signal_const_entry(signal))
+ self.b(' N_%s_SIGNALS' % node_name_uc)
+ self.b('};')
+ self.b('static guint %s_signals[N_%s_SIGNALS] = {0};'
+ % (node_name_lc, node_name_uc))
+ self.b('')
+
+ self.b('static void %s%s_base_init (gpointer klass);'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ self.b('GType')
+ self.b('%s%s_get_type (void)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static GType type = 0;')
+ self.b('')
+ self.b(' if (G_UNLIKELY (type == 0))')
+ self.b(' {')
+ self.b(' static const GTypeInfo info = {')
+ self.b(' sizeof (%s%sClass),' % (self.Prefix, node_name_mixed))
+ self.b(' %s%s_base_init, /* base_init */'
+ % (self.prefix_, node_name_lc))
+ self.b(' NULL, /* base_finalize */')
+ self.b(' NULL, /* class_init */')
+ self.b(' NULL, /* class_finalize */')
+ self.b(' NULL, /* class_data */')
+ self.b(' 0,')
+ self.b(' 0, /* n_preallocs */')
+ self.b(' NULL /* instance_init */')
+ self.b(' };')
+ self.b('')
+ self.b(' type = g_type_register_static (G_TYPE_INTERFACE,')
+ self.b(' "%s%s", &info, 0);' % (self.Prefix, node_name_mixed))
+ self.b(' }')
+ self.b('')
+ self.b(' return type;')
+ self.b('}')
+ self.b('')
+
+ self.h('/**')
+ self.h(' * %s%s:' % (self.Prefix, node_name_mixed))
+ self.h(' *')
+ self.h(' * Dummy typedef representing any implementation of this '
+ 'interface.')
+ self.h(' */')
+ self.h('typedef struct _%s%s %s%s;'
+ % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
+ self.h('')
+ self.h('/**')
+ self.h(' * %s%sClass:' % (self.Prefix, node_name_mixed))
+ self.h(' *')
+ self.h(' * The class of %s%s.' % (self.Prefix, node_name_mixed))
+
+ if methods:
+ self.h(' *')
+ self.h(' * In a full implementation of this interface (i.e. all')
+ self.h(' * methods implemented), the interface initialization')
+ self.h(' * function used in G_IMPLEMENT_INTERFACE() would')
+ self.h(' * typically look like this:')
+ self.h(' *')
+ self.h(' * <programlisting>')
+ self.h(' * static void')
+ self.h(' * implement_%s (gpointer klass,' % self.node_name_lc)
+ self.h(' * gpointer unused G_GNUC_UNUSED)')
+ self.h(' * {')
+ # "#" is special to gtkdoc under some circumstances; it appears
+ # that escaping "##" as "#<!---->#" or "&#35;&#35;" doesn't work,
+ # but adding an extra hash symbol does. Thanks, gtkdoc :-(
+ self.h(' * #define IMPLEMENT(x) %s%s_implement_###x (\\'
+ % (self.prefix_, self.node_name_lc))
+ self.h(' * klass, my_object_###x)')
+
+ for method in methods:
+ class_member_name = method.getAttribute('tp:name-for-bindings')
+ class_member_name = class_member_name.lower()
+ self.h(' * IMPLEMENT (%s);' % class_member_name)
+
+ self.h(' * #undef IMPLEMENT')
+ self.h(' * }')
+ self.h(' * </programlisting>')
+ else:
+ self.h(' * This interface has no D-Bus methods, so an')
+ self.h(' * implementation can typically pass %NULL to')
+ self.h(' * G_IMPLEMENT_INTERFACE() as the interface')
+ self.h(' * initialization function.')
+
+ self.h(' */')
+
+ self.h('typedef struct _%s%sClass %s%sClass;'
+ % (self.Prefix, node_name_mixed, self.Prefix, node_name_mixed))
+ self.h('')
+ self.h('GType %s%s_get_type (void);'
+ % (self.prefix_, node_name_lc))
+
+ gtype = self.current_gtype = \
+ self.MAIN_PREFIX_ + 'TYPE' + self._SUB_PREFIX_ + node_name_uc
+ classname = self.Prefix + node_name_mixed
+
+ self.h('#define %s \\\n (%s%s_get_type ())'
+ % (gtype, self.prefix_, node_name_lc))
+ self.h('#define %s%s(obj) \\\n'
+ ' (G_TYPE_CHECK_INSTANCE_CAST((obj), %s, %s))'
+ % (self.PREFIX_, node_name_uc, gtype, classname))
+ self.h('#define %sIS%s%s(obj) \\\n'
+ ' (G_TYPE_CHECK_INSTANCE_TYPE((obj), %s))'
+ % (self.MAIN_PREFIX_, self._SUB_PREFIX_, node_name_uc, gtype))
+ self.h('#define %s%s_GET_CLASS(obj) \\\n'
+ ' (G_TYPE_INSTANCE_GET_INTERFACE((obj), %s, %sClass))'
+ % (self.PREFIX_, node_name_uc, gtype, classname))
+ self.h('')
+ self.h('')
+
+ base_init_code = []
+
+ for method in methods:
+ self.do_method(method)
+
+ for signal in signals:
+ base_init_code.extend(self.do_signal(signal))
+
+ self.b('static inline void')
+ self.b('%s%s_base_init_once (gpointer klass G_GNUC_UNUSED)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+
+ if properties:
+ self.b(' static TpDBusPropertiesMixinPropInfo properties[%d] = {'
+ % (len(properties) + 1))
+
+ for m in properties:
+ access = m.getAttribute('access')
+ assert access in ('read', 'write', 'readwrite')
+
+ if access == 'read':
+ flags = 'TP_DBUS_PROPERTIES_MIXIN_FLAG_READ'
+ elif access == 'write':
+ flags = 'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE'
+ else:
+ flags = ('TP_DBUS_PROPERTIES_MIXIN_FLAG_READ | '
+ 'TP_DBUS_PROPERTIES_MIXIN_FLAG_WRITE')
+
+ self.b(' { 0, %s, "%s", 0, NULL, NULL }, /* %s */'
+ % (flags, m.getAttribute('type'), m.getAttribute('name')))
+
+ self.b(' { 0, 0, NULL, 0, NULL, NULL }')
+ self.b(' };')
+ self.b(' static TpDBusPropertiesMixinIfaceInfo interface =')
+ self.b(' { 0, properties, NULL, NULL };')
+ self.b('')
+
+
+ self.b(' dbus_g_object_type_install_info (%s%s_get_type (),'
+ % (self.prefix_, node_name_lc))
+ self.b(' &_%s%s_object_info);'
+ % (self.prefix_, node_name_lc))
+ self.b('')
+
+ if properties:
+ self.b(' interface.dbus_interface = g_quark_from_static_string '
+ '("%s");' % self.iface_name)
+
+ for i, m in enumerate(properties):
+ self.b(' properties[%d].name = g_quark_from_static_string ("%s");'
+ % (i, m.getAttribute('name')))
+ self.b(' properties[%d].type = %s;'
+ % (i, type_to_gtype(m.getAttribute('type'))[1]))
+
+ self.b(' tp_svc_interface_set_dbus_properties_info (%s, &interface);'
+ % self.current_gtype)
+
+ self.b('')
+
+ for s in base_init_code:
+ self.b(s)
+ self.b('}')
+
+ self.b('static void')
+ self.b('%s%s_base_init (gpointer klass)'
+ % (self.prefix_, node_name_lc))
+ self.b('{')
+ self.b(' static gboolean initialized = FALSE;')
+ self.b('')
+ self.b(' if (!initialized)')
+ self.b(' {')
+ self.b(' initialized = TRUE;')
+ self.b(' %s%s_base_init_once (klass);'
+ % (self.prefix_, node_name_lc))
+ self.b(' }')
+ # insert anything we need to do per implementation here
+ self.b('}')
+
+ self.h('')
+
+ self.b('static const DBusGMethodInfo _%s%s_methods[] = {'
+ % (self.prefix_, node_name_lc))
+
+ method_blob, offsets = self.get_method_glue(methods)
+
+ for method, offset in zip(methods, offsets):
+ self.do_method_glue(method, offset)
+
+ if len(methods) == 0:
+ # empty arrays are a gcc extension, so put in a dummy member
+ self.b(" { NULL, NULL, 0 }")
+
+ self.b('};')
+ self.b('')
+
+ self.b('static const DBusGObjectInfo _%s%s_object_info = {'
+ % (self.prefix_, node_name_lc))
+ self.b(' 0,') # version
+ self.b(' _%s%s_methods,' % (self.prefix_, node_name_lc))
+ self.b(' %d,' % len(methods))
+ self.b('"' + method_blob.replace('\0', '\\0') + '",')
+ self.b('"' + self.get_signal_glue(signals).replace('\0', '\\0') + '",')
+ self.b('"' +
+ self.get_property_glue(glue_properties).replace('\0', '\\0') +
+ '",')
+ self.b('};')
+ self.b('')
+
+ self.node_name_mixed = None
+ self.node_name_lc = None
+ self.node_name_uc = None
+
+ def get_method_glue(self, methods):
+ info = []
+ offsets = []
+
+ for method in methods:
+ offsets.append(len(''.join(info)))
+
+ info.append(self.iface_name + '\0')
+ info.append(method.getAttribute('name') + '\0')
+
+ info.append('A\0') # async
+
+ counter = 0
+ for arg in method.getElementsByTagName('arg'):
+ out = arg.getAttribute('direction') == 'out'
+
+ name = arg.getAttribute('name')
+ if not name:
+ assert out
+ name = 'arg%u' % counter
+ counter += 1
+
+ info.append(name + '\0')
+
+ if out:
+ info.append('O\0')
+ else:
+ info.append('I\0')
+
+ if out:
+ info.append('F\0') # not const
+ info.append('N\0') # not error or return
+ info.append(arg.getAttribute('type') + '\0')
+
+ info.append('\0')
+
+ return ''.join(info) + '\0', offsets
+
+ def do_method_glue(self, method, offset):
+ lc_name = method.getAttribute('tp:name-for-bindings')
+ if method.getAttribute('name') != lc_name.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (method.getAttribute('name'), lc_name))
+ lc_name = lc_name.lower()
+
+ marshaller = method_to_glue_marshal_name(method,
+ self.signal_marshal_prefix)
+ wrapper = self.prefix_ + self.node_name_lc + '_' + lc_name
+
+ self.b(" { (GCallback) %s, %s, %d }," % (wrapper, marshaller, offset))
+
+ def get_signal_glue(self, signals):
+ info = []
+
+ for signal in signals:
+ info.append(self.iface_name)
+ info.append(signal.getAttribute('name'))
+
+ return '\0'.join(info) + '\0\0'
+
+ # the implementation can be the same
+ get_property_glue = get_signal_glue
+
+ def get_method_impl_names(self, method):
+ dbus_method_name = method.getAttribute('name')
+
+ class_member_name = method.getAttribute('tp:name-for-bindings')
+ if dbus_method_name != class_member_name.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (dbus_method_name, class_member_name))
+ class_member_name = class_member_name.lower()
+
+ stub_name = (self.prefix_ + self.node_name_lc + '_' +
+ class_member_name)
+ return (stub_name + '_impl', class_member_name)
+
+ def do_method(self, method):
+ assert self.node_name_mixed is not None
+
+ in_class = []
+
+ # Examples refer to Thing.DoStuff (su) -> ii
+
+ # DoStuff
+ dbus_method_name = method.getAttribute('name')
+ # do_stuff
+ class_member_name = method.getAttribute('tp:name-for-bindings')
+ if dbus_method_name != class_member_name.replace('_', ''):
+ raise AssertionError('Method %s tp:name-for-bindings (%s) does '
+ 'not match' % (dbus_method_name, class_member_name))
+ class_member_name = class_member_name.lower()
+
+ # void tp_svc_thing_do_stuff (TpSvcThing *, const char *, guint,
+ # DBusGMethodInvocation *);
+ stub_name = (self.prefix_ + self.node_name_lc + '_' +
+ class_member_name)
+ # typedef void (*tp_svc_thing_do_stuff_impl) (TpSvcThing *,
+ # const char *, guint, DBusGMethodInvocation);
+ impl_name = stub_name + '_impl'
+ # void tp_svc_thing_return_from_do_stuff (DBusGMethodInvocation *,
+ # gint, gint);
+ ret_name = (self.prefix_ + self.node_name_lc + '_return_from_' +
+ class_member_name)
+
+ # Gather arguments
+ in_args = []
+ out_args = []
+ for i in method.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ direction = i.getAttribute('direction') or 'in'
+ dtype = i.getAttribute('type')
+
+ assert direction in ('in', 'out')
+
+ if name:
+ name = direction + '_' + name
+ elif direction == 'in':
+ name = direction + str(len(in_args))
+ else:
+ name = direction + str(len(out_args))
+
+ ctype, gtype, marshaller, pointer = type_to_gtype(dtype)
+
+ if pointer:
+ ctype = 'const ' + ctype
+
+ struct = (ctype, name)
+
+ if direction == 'in':
+ in_args.append(struct)
+ else:
+ out_args.append(struct)
+
+ # Implementation type declaration (in header, docs in body)
+ self.b('/**')
+ self.b(' * %s:' % impl_name)
+ self.b(' * @self: The object implementing this interface')
+ for (ctype, name) in in_args:
+ self.b(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.b(' * @context: Used to return values or throw an error')
+ self.b(' *')
+ self.b(' * The signature of an implementation of the D-Bus method')
+ self.b(' * %s on interface %s.' % (dbus_method_name, self.iface_name))
+ self.b(' */')
+ self.h('typedef void (*%s) (%s%s *self,'
+ % (impl_name, self.Prefix, self.node_name_mixed))
+ for (ctype, name) in in_args:
+ self.h(' %s%s,' % (ctype, name))
+ self.h(' DBusGMethodInvocation *context);')
+
+ # Class member (in class definition)
+ in_class.append(' %s %s;' % (impl_name, class_member_name))
+
+ # Stub definition (in body only - it's static)
+ self.b('static void')
+ self.b('%s (%s%s *self,'
+ % (stub_name, self.Prefix, self.node_name_mixed))
+ for (ctype, name) in in_args:
+ self.b(' %s%s,' % (ctype, name))
+ self.b(' DBusGMethodInvocation *context)')
+ self.b('{')
+ self.b(' %s impl = (%s%s_GET_CLASS (self)->%s);'
+ % (impl_name, self.PREFIX_, self.node_name_uc, class_member_name))
+ self.b('')
+ self.b(' if (impl != NULL)')
+ tmp = ['self'] + [name for (ctype, name) in in_args] + ['context']
+ self.b(' {')
+ self.b(' (impl) (%s);' % ',\n '.join(tmp))
+ self.b(' }')
+ self.b(' else')
+ self.b(' {')
+ if self.not_implemented_func:
+ self.b(' %s (context);' % self.not_implemented_func)
+ else:
+ self.b(' GError e = { DBUS_GERROR, ')
+ self.b(' DBUS_GERROR_UNKNOWN_METHOD,')
+ self.b(' "Method not implemented" };')
+ self.b('')
+ self.b(' dbus_g_method_return_error (context, &e);')
+ self.b(' }')
+ self.b('}')
+ self.b('')
+
+ # Implementation registration (in both header and body)
+ self.h('void %s%s_implement_%s (%s%sClass *klass, %s impl);'
+ % (self.prefix_, self.node_name_lc, class_member_name,
+ self.Prefix, self.node_name_mixed, impl_name))
+
+ self.b('/**')
+ self.b(' * %s%s_implement_%s:'
+ % (self.prefix_, self.node_name_lc, class_member_name))
+ self.b(' * @klass: A class whose instances implement this interface')
+ self.b(' * @impl: A callback used to implement the %s D-Bus method'
+ % dbus_method_name)
+ self.b(' *')
+ self.b(' * Register an implementation for the %s method in the vtable'
+ % dbus_method_name)
+ self.b(' * of an implementation of this interface. To be called from')
+ self.b(' * the interface init function.')
+ self.b(' */')
+ self.b('void')
+ self.b('%s%s_implement_%s (%s%sClass *klass, %s impl)'
+ % (self.prefix_, self.node_name_lc, class_member_name,
+ self.Prefix, self.node_name_mixed, impl_name))
+ self.b('{')
+ self.b(' klass->%s = impl;' % class_member_name)
+ self.b('}')
+ self.b('')
+
+ # Return convenience function (static inline, in header)
+ self.h('/**')
+ self.h(' * %s:' % ret_name)
+ self.h(' * @context: The D-Bus method invocation context')
+ for (ctype, name) in out_args:
+ self.h(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.h(' *')
+ self.h(' * Return successfully by calling dbus_g_method_return().')
+ self.h(' * This inline function exists only to provide type-safety.')
+ self.h(' */')
+ tmp = (['DBusGMethodInvocation *context'] +
+ [ctype + name for (ctype, name) in out_args])
+ self.h('static inline')
+ self.h('/* this comment is to stop gtkdoc realising this is static */')
+ self.h(('void %s (' % ret_name) + (',\n '.join(tmp)) + ');')
+ self.h('static inline void')
+ self.h(('%s (' % ret_name) + (',\n '.join(tmp)) + ')')
+ self.h('{')
+ tmp = ['context'] + [name for (ctype, name) in out_args]
+ self.h(' dbus_g_method_return (' + ',\n '.join(tmp) + ');')
+ self.h('}')
+ self.h('')
+
+ return in_class
+
+ def get_signal_const_entry(self, signal):
+ assert self.node_name_uc is not None
+ return ('SIGNAL_%s_%s'
+ % (self.node_name_uc, signal.getAttribute('name')))
+
+ def do_signal(self, signal):
+ assert self.node_name_mixed is not None
+
+ in_base_init = []
+
+ # for signal: Thing::StuffHappened (s, u)
+ # we want to emit:
+ # void tp_svc_thing_emit_stuff_happened (gpointer instance,
+ # const char *arg0, guint arg1);
+
+ dbus_name = signal.getAttribute('name')
+
+ ugly_name = signal.getAttribute('tp:name-for-bindings')
+ if dbus_name != ugly_name.replace('_', ''):
+ raise AssertionError('Signal %s tp:name-for-bindings (%s) does '
+ 'not match' % (dbus_name, ugly_name))
+
+ stub_name = (self.prefix_ + self.node_name_lc + '_emit_' +
+ ugly_name.lower())
+
+ const_name = self.get_signal_const_entry(signal)
+
+ # Gather arguments
+ args = []
+ for i in signal.getElementsByTagName('arg'):
+ name = i.getAttribute('name')
+ dtype = i.getAttribute('type')
+ tp_type = i.getAttribute('tp:type')
+
+ if name:
+ name = 'arg_' + name
+ else:
+ name = 'arg' + str(len(args))
+
+ ctype, gtype, marshaller, pointer = type_to_gtype(dtype)
+
+ if pointer:
+ ctype = 'const ' + ctype
+
+ struct = (ctype, name, gtype)
+ args.append(struct)
+
+ tmp = (['gpointer instance'] +
+ [ctype + name for (ctype, name, gtype) in args])
+
+ self.h(('void %s (' % stub_name) + (',\n '.join(tmp)) + ');')
+
+ # FIXME: emit docs
+
+ self.b('/**')
+ self.b(' * %s:' % stub_name)
+ self.b(' * @instance: The object implementing this interface')
+ for (ctype, name, gtype) in args:
+ self.b(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ self.b(' *')
+ self.b(' * Type-safe wrapper around g_signal_emit to emit the')
+ self.b(' * %s signal on interface %s.'
+ % (dbus_name, self.iface_name))
+ self.b(' */')
+
+ self.b('void')
+ self.b(('%s (' % stub_name) + (',\n '.join(tmp)) + ')')
+ self.b('{')
+ self.b(' g_assert (instance != NULL);')
+ self.b(' g_assert (G_TYPE_CHECK_INSTANCE_TYPE (instance, %s));'
+ % (self.current_gtype))
+ tmp = (['instance', '%s_signals[%s]' % (self.node_name_lc, const_name),
+ '0'] + [name for (ctype, name, gtype) in args])
+ self.b(' g_signal_emit (' + ',\n '.join(tmp) + ');')
+ self.b('}')
+ self.b('')
+
+ signal_name = dbus_gutils_wincaps_to_uscore(dbus_name).replace('_',
+ '-')
+ in_base_init.append(' /**')
+ in_base_init.append(' * %s%s::%s:'
+ % (self.Prefix, self.node_name_mixed, signal_name))
+ for (ctype, name, gtype) in args:
+ in_base_init.append(' * @%s: %s (FIXME, generate documentation)'
+ % (name, ctype))
+ in_base_init.append(' *')
+ in_base_init.append(' * The %s D-Bus signal is emitted whenever '
+ 'this GObject signal is.' % dbus_name)
+ in_base_init.append(' */')
+ in_base_init.append(' %s_signals[%s] ='
+ % (self.node_name_lc, const_name))
+ in_base_init.append(' g_signal_new ("%s",' % signal_name)
+ in_base_init.append(' G_OBJECT_CLASS_TYPE (klass),')
+ in_base_init.append(' G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED,')
+ in_base_init.append(' 0,')
+ in_base_init.append(' NULL, NULL,')
+ in_base_init.append(' %s,'
+ % signal_to_marshal_name(signal, self.signal_marshal_prefix))
+ in_base_init.append(' G_TYPE_NONE,')
+ tmp = ['%d' % len(args)] + [gtype for (ctype, name, gtype) in args]
+ in_base_init.append(' %s);' % ',\n '.join(tmp))
+ in_base_init.append('')
+
+ return in_base_init
+
+ def have_properties(self, nodes):
+ for node in nodes:
+ interface = node.getElementsByTagName('interface')[0]
+ if interface.getElementsByTagName('property'):
+ return True
+ return False
+
+ def __call__(self):
+ nodes = self.dom.getElementsByTagName('node')
+ nodes.sort(cmp_by_name)
+
+ self.h('#include <glib-object.h>')
+ self.h('#include <dbus/dbus-glib.h>')
+
+ if self.have_properties(nodes):
+ self.h('#include <telepathy-glib/dbus-properties-mixin.h>')
+
+ self.h('')
+ self.h('G_BEGIN_DECLS')
+ self.h('')
+
+ self.b('#include "%s.h"' % self.basename)
+ self.b('')
+ for header in self.headers:
+ self.b('#include %s' % header)
+ self.b('')
+
+ for node in nodes:
+ self.do_node(node)
+
+ self.h('')
+ self.h('G_END_DECLS')
+
+ self.b('')
+ for header in self.end_headers:
+ self.b('#include %s' % header)
+
+ self.h('')
+ self.b('')
+ open(self.basename + '.h', 'w').write('\n'.join(self.__header))
+ open(self.basename + '.c', 'w').write('\n'.join(self.__body))
+
+
+def cmdline_error():
+ print """\
+usage:
+ gen-ginterface [OPTIONS] xmlfile Prefix_
+options:
+ --include='<header.h>' (may be repeated)
+ --include='"header.h"' (ditto)
+ --include-end='"header.h"' (ditto)
+ Include extra headers in the generated .c file
+ --signal-marshal-prefix='prefix'
+ Use the given prefix on generated signal marshallers (default is
+ prefix.lower()).
+ --filename='BASENAME'
+ Set the basename for the output files (default is prefix.lower()
+ + 'ginterfaces')
+ --not-implemented-func='symbol'
+ Set action when methods not implemented in the interface vtable are
+ called. symbol must have signature
+ void symbol (DBusGMethodInvocation *context)
+ and return some sort of "not implemented" error via
+ dbus_g_method_return_error (context, ...)
+"""
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ from getopt import gnu_getopt
+
+ options, argv = gnu_getopt(sys.argv[1:], '',
+ ['filename=', 'signal-marshal-prefix=',
+ 'include=', 'include-end=',
+ 'allow-unstable',
+ 'not-implemented-func='])
+
+ try:
+ prefix = argv[1]
+ except IndexError:
+ cmdline_error()
+
+ basename = prefix.lower() + 'ginterfaces'
+ signal_marshal_prefix = prefix.lower().rstrip('_')
+ headers = []
+ end_headers = []
+ not_implemented_func = ''
+ allow_havoc = False
+
+ for option, value in options:
+ if option == '--filename':
+ basename = value
+ elif option == '--signal-marshal-prefix':
+ signal_marshal_prefix = value
+ elif option == '--include':
+ if value[0] not in '<"':
+ value = '"%s"' % value
+ headers.append(value)
+ elif option == '--include-end':
+ if value[0] not in '<"':
+ value = '"%s"' % value
+ end_headers.append(value)
+ elif option == '--not-implemented-func':
+ not_implemented_func = value
+ elif option == '--allow-unstable':
+ allow_havoc = True
+
+ try:
+ dom = xml.dom.minidom.parse(argv[0])
+ except IndexError:
+ cmdline_error()
+
+ Generator(dom, prefix, basename, signal_marshal_prefix, headers,
+ end_headers, not_implemented_func, allow_havoc)()
diff --git a/qt4/tools/glib-gtypes-generator.py b/qt4/tools/glib-gtypes-generator.py
new file mode 100644
index 000000000..ebc2ad4c9
--- /dev/null
+++ b/qt4/tools/glib-gtypes-generator.py
@@ -0,0 +1,291 @@
+#!/usr/bin/python
+
+# Generate GLib GInterfaces from the Telepathy specification.
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (C) 2006, 2007 Collabora Limited
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import xml.dom.minidom
+
+from libglibcodegen import escape_as_identifier, \
+ get_docstring, \
+ NS_TP, \
+ Signature, \
+ type_to_gtype, \
+ xml_escape
+
+
+def types_to_gtypes(types):
+ return [type_to_gtype(t)[1] for t in types]
+
+
+class GTypesGenerator(object):
+ def __init__(self, dom, output, mixed_case_prefix):
+ self.dom = dom
+ self.Prefix = mixed_case_prefix
+ self.PREFIX_ = self.Prefix.upper() + '_'
+ self.prefix_ = self.Prefix.lower() + '_'
+
+ self.header = open(output + '.h', 'w')
+ self.body = open(output + '-body.h', 'w')
+
+ for f in (self.header, self.body):
+ f.write('/* Auto-generated, do not edit.\n *\n'
+ ' * This file may be distributed under the same terms\n'
+ ' * as the specification from which it was generated.\n'
+ ' */\n\n')
+
+ # keys are e.g. 'sv', values are the key escaped
+ self.need_mappings = {}
+ # keys are the contents of the struct (e.g. 'sssu'), values are the
+ # key escaped
+ self.need_structs = {}
+ # keys are the contents of the struct (e.g. 'sssu'), values are the
+ # key escaped
+ self.need_struct_arrays = {}
+
+ # keys are the contents of the array (unlike need_struct_arrays!),
+ # values are the key escaped
+ self.need_other_arrays = {}
+
+ def h(self, code):
+ self.header.write(code.encode("utf-8"))
+
+ def c(self, code):
+ self.body.write(code.encode("utf-8"))
+
+ def do_mapping_header(self, mapping):
+ members = mapping.getElementsByTagNameNS(NS_TP, 'member')
+ assert len(members) == 2
+
+ impl_sig = ''.join([elt.getAttribute('type')
+ for elt in members])
+
+ esc_impl_sig = escape_as_identifier(impl_sig)
+
+ name = (self.PREFIX_ + 'HASH_TYPE_' +
+ mapping.getAttribute('name').upper())
+ impl = self.prefix_ + 'type_dbus_hash_' + esc_impl_sig
+
+ docstring = get_docstring(mapping) or '(Undocumented)'
+
+ self.h('/**\n * %s:\n *\n' % name)
+ self.h(' * %s\n' % xml_escape(docstring))
+ self.h(' *\n')
+ self.h(' * This macro expands to a call to a function\n')
+ self.h(' * that returns the #GType of a #GHashTable\n')
+ self.h(' * appropriate for representing a D-Bus\n')
+ self.h(' * dictionary of signature\n')
+ self.h(' * <literal>a{%s}</literal>.\n' % impl_sig)
+ self.h(' *\n')
+
+ key, value = members
+
+ self.h(' * Keys (D-Bus type <literal>%s</literal>,\n'
+ % key.getAttribute('type'))
+ tp_type = key.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.h(' * type <literal>%s</literal>,\n' % tp_type)
+ self.h(' * named <literal>%s</literal>):\n'
+ % key.getAttribute('name'))
+ docstring = get_docstring(key) or '(Undocumented)'
+ self.h(' * %s\n' % xml_escape(docstring))
+ self.h(' *\n')
+
+ self.h(' * Values (D-Bus type <literal>%s</literal>,\n'
+ % value.getAttribute('type'))
+ tp_type = value.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.h(' * type <literal>%s</literal>,\n' % tp_type)
+ self.h(' * named <literal>%s</literal>):\n'
+ % value.getAttribute('name'))
+ docstring = get_docstring(value) or '(Undocumented)'
+ self.h(' * %s\n' % xml_escape(docstring))
+ self.h(' *\n')
+
+ self.h(' */\n')
+
+ self.h('#define %s (%s ())\n\n' % (name, impl))
+ self.need_mappings[impl_sig] = esc_impl_sig
+
+ array_name = mapping.getAttribute('array-name')
+ if array_name:
+ gtype_name = self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper()
+ contents_sig = 'a{' + impl_sig + '}'
+ esc_contents_sig = escape_as_identifier(contents_sig)
+ impl = self.prefix_ + 'type_dbus_array_of_' + esc_contents_sig
+ self.h('/**\n * %s:\n\n' % gtype_name)
+ self.h(' * Expands to a call to a function\n')
+ self.h(' * that returns the #GType of a #GPtrArray\n')
+ self.h(' * of #%s.\n' % name)
+ self.h(' */\n')
+ self.h('#define %s (%s ())\n\n' % (gtype_name, impl))
+ self.need_other_arrays[contents_sig] = esc_contents_sig
+
+ def do_struct_header(self, struct):
+ members = struct.getElementsByTagNameNS(NS_TP, 'member')
+ impl_sig = ''.join([elt.getAttribute('type') for elt in members])
+ esc_impl_sig = escape_as_identifier(impl_sig)
+
+ name = (self.PREFIX_ + 'STRUCT_TYPE_' +
+ struct.getAttribute('name').upper())
+ impl = self.prefix_ + 'type_dbus_struct_' + esc_impl_sig
+ docstring = struct.getElementsByTagNameNS(NS_TP, 'docstring')
+ if docstring:
+ docstring = docstring[0].toprettyxml()
+ if docstring.startswith('<tp:docstring>'):
+ docstring = docstring[14:]
+ if docstring.endswith('</tp:docstring>\n'):
+ docstring = docstring[:-16]
+ if docstring.strip() in ('<tp:docstring/>', ''):
+ docstring = '(Undocumented)'
+ else:
+ docstring = '(Undocumented)'
+ self.h('/**\n * %s:\n\n' % name)
+ self.h(' * %s\n' % xml_escape(docstring))
+ self.h(' *\n')
+ self.h(' * This macro expands to a call to a function\n')
+ self.h(' * that returns the #GType of a #GValueArray\n')
+ self.h(' * appropriate for representing a D-Bus struct\n')
+ self.h(' * with signature <literal>(%s)</literal>.\n'
+ % impl_sig)
+ self.h(' *\n')
+
+ for i, member in enumerate(members):
+ self.h(' * Member %d (D-Bus type '
+ '<literal>%s</literal>,\n'
+ % (i, member.getAttribute('type')))
+ tp_type = member.getAttributeNS(NS_TP, 'type')
+ if tp_type:
+ self.h(' * type <literal>%s</literal>,\n' % tp_type)
+ self.h(' * named <literal>%s</literal>):\n'
+ % member.getAttribute('name'))
+ docstring = get_docstring(member) or '(Undocumented)'
+ self.h(' * %s\n' % xml_escape(docstring))
+ self.h(' *\n')
+
+ self.h(' */\n')
+ self.h('#define %s (%s ())\n\n' % (name, impl))
+
+ array_name = struct.getAttribute('array-name')
+ if array_name != '':
+ array_name = (self.PREFIX_ + 'ARRAY_TYPE_' + array_name.upper())
+ impl = self.prefix_ + 'type_dbus_array_' + esc_impl_sig
+ self.h('/**\n * %s:\n\n' % array_name)
+ self.h(' * Expands to a call to a function\n')
+ self.h(' * that returns the #GType of a #GPtrArray\n')
+ self.h(' * of #%s.\n' % name)
+ self.h(' */\n')
+ self.h('#define %s (%s ())\n\n' % (array_name, impl))
+ self.need_struct_arrays[impl_sig] = esc_impl_sig
+
+ self.need_structs[impl_sig] = esc_impl_sig
+
+ def __call__(self):
+ mappings = self.dom.getElementsByTagNameNS(NS_TP, 'mapping')
+ structs = self.dom.getElementsByTagNameNS(NS_TP, 'struct')
+
+ for mapping in mappings:
+ self.do_mapping_header(mapping)
+
+ for sig in self.need_mappings:
+ self.h('GType %stype_dbus_hash_%s (void);\n\n' %
+ (self.prefix_, self.need_mappings[sig]))
+ self.c('GType\n%stype_dbus_hash_%s (void)\n{\n' %
+ (self.prefix_, self.need_mappings[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+ # FIXME: translate sig into two GTypes
+ items = tuple(Signature(sig))
+ gtypes = types_to_gtypes(items)
+ self.c(' t = dbus_g_type_get_map ("GHashTable", '
+ '%s, %s);\n' % (gtypes[0], gtypes[1]))
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+ for struct in structs:
+ self.do_struct_header(struct)
+
+ for sig in self.need_structs:
+ self.h('GType %stype_dbus_struct_%s (void);\n\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.c('GType\n%stype_dbus_struct_%s (void)\n{\n' %
+ (self.prefix_, self.need_structs[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+ self.c(' t = dbus_g_type_get_struct ("GValueArray",\n')
+ items = tuple(Signature(sig))
+ gtypes = types_to_gtypes(items)
+ for gtype in gtypes:
+ self.c(' %s,\n' % gtype)
+ self.c(' G_TYPE_INVALID);\n')
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+ for sig in self.need_struct_arrays:
+ self.h('GType %stype_dbus_array_%s (void);\n\n' %
+ (self.prefix_, self.need_struct_arrays[sig]))
+ self.c('GType\n%stype_dbus_array_%s (void)\n{\n' %
+ (self.prefix_, self.need_struct_arrays[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+ self.c(' t = dbus_g_type_get_collection ("GPtrArray", '
+ '%stype_dbus_struct_%s ());\n' %
+ (self.prefix_, self.need_struct_arrays[sig]))
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+ for sig in self.need_other_arrays:
+ self.h('GType %stype_dbus_array_of_%s (void);\n\n' %
+ (self.prefix_, self.need_other_arrays[sig]))
+ self.c('GType\n%stype_dbus_array_of_%s (void)\n{\n' %
+ (self.prefix_, self.need_other_arrays[sig]))
+ self.c(' static GType t = 0;\n\n')
+ self.c(' if (G_UNLIKELY (t == 0))\n')
+
+ if sig[:2] == 'a{' and sig[-1:] == '}':
+ # array of mappings
+ self.c(' t = dbus_g_type_get_collection ('
+ '"GPtrArray", '
+ '%stype_dbus_hash_%s ());\n' %
+ (self.prefix_, escape_as_identifier(sig[2:-1])))
+ elif sig[:2] == 'a(' and sig[-1:] == ')':
+ # array of arrays of struct
+ self.c(' t = dbus_g_type_get_collection ('
+ '"GPtrArray", '
+ '%stype_dbus_array_%s ());\n' %
+ (self.prefix_, escape_as_identifier(sig[2:-1])))
+ elif sig[:1] == 'a':
+ # array of arrays of non-struct
+ self.c(' t = dbus_g_type_get_collection ('
+ '"GPtrArray", '
+ '%stype_dbus_array_of_%s ());\n' %
+ (self.prefix_, escape_as_identifier(sig[1:])))
+ else:
+ raise AssertionError("array of '%s' not supported" % sig)
+
+ self.c(' return t;\n')
+ self.c('}\n\n')
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+
+ dom = xml.dom.minidom.parse(argv[0])
+
+ GTypesGenerator(dom, argv[1], argv[2])()
diff --git a/qt4/tools/glib-interfaces-gen.py b/qt4/tools/glib-interfaces-gen.py
new file mode 100644
index 000000000..95439687e
--- /dev/null
+++ b/qt4/tools/glib-interfaces-gen.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+
+from sys import argv, stdout, stderr
+import xml.dom.minidom
+
+from libglibcodegen import NS_TP, get_docstring, \
+ get_descendant_text, get_by_path
+
+class Generator(object):
+ def __init__(self, prefix, implfile, declfile, dom):
+ self.prefix = prefix + '_'
+ self.impls = open(implfile, 'w')
+ self.decls = open(declfile, 'w')
+ self.spec = get_by_path(dom, "spec")[0]
+
+ def h(self, code):
+ self.decls.write(code.encode('utf-8'))
+
+ def c(self, code):
+ self.impls.write(code.encode('utf-8'))
+
+ def __call__(self):
+ for f in self.h, self.c:
+ self.do_header(f)
+ self.do_body()
+
+ # Header
+ def do_header(self, f):
+ f('/* Generated from: ')
+ f(get_descendant_text(get_by_path(self.spec, 'title')))
+ version = get_by_path(self.spec, "version")
+ if version:
+ f(' version ' + get_descendant_text(version))
+ f('\n\n')
+ for copyright in get_by_path(self.spec, 'copyright'):
+ f(get_descendant_text(copyright))
+ f('\n')
+ f('\n')
+ f(get_descendant_text(get_by_path(self.spec, 'license')))
+ f(get_descendant_text(get_by_path(self.spec, 'docstring')))
+ f("""
+ */
+
+""")
+
+ # Body
+ def do_body(self):
+ for iface in self.spec.getElementsByTagName('interface'):
+ self.do_iface(iface)
+
+ def do_iface(self, iface):
+ parent_name = get_by_path(iface, '../@name')
+ self.h("""\
+/**
+ * %(IFACE_DEFINE)s:
+ *
+ * The interface name "%(name)s"
+ */
+#define %(IFACE_DEFINE)s \\
+"%(name)s"
+""" % {'IFACE_DEFINE' : (self.prefix + 'IFACE_' + \
+ parent_name).upper().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.h("""
+/**
+ * %(IFACE_QUARK_DEFINE)s:
+ *
+ * Expands to a call to a function that returns a quark for the interface \
+name "%(name)s"
+ */
+#define %(IFACE_QUARK_DEFINE)s \\
+ (%(iface_quark_func)s ())
+
+GQuark %(iface_quark_func)s (void);
+
+""" % {'IFACE_QUARK_DEFINE' : (self.prefix + 'IFACE_QUARK_' + \
+ parent_name).upper().replace('/', ''),
+ 'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+ parent_name).lower().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ self.c("""\
+GQuark
+%(iface_quark_func)s (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0))
+ {
+ quark = g_quark_from_static_string ("%(name)s");
+ }
+
+ return quark;
+}
+
+""" % {'iface_quark_func' : (self.prefix + 'iface_quark_' + \
+ parent_name).lower().replace('/', ''),
+ 'name' : iface.getAttribute('name')})
+
+ for prop in iface.getElementsByTagNameNS(None, 'property'):
+ self.decls.write("""
+/**
+ * %(IFACE_PREFIX)s_%(PROP_UC)s:
+ *
+ * The fully-qualified property name "%(name)s.%(prop)s"
+ */
+#define %(IFACE_PREFIX)s_%(PROP_UC)s \\
+"%(name)s.%(prop)s"
+""" % {'IFACE_PREFIX' : (self.prefix + 'PROP_' + \
+ parent_name).upper().replace('/', ''),
+ 'PROP_UC': prop.getAttributeNS(NS_TP, "name-for-bindings").upper(),
+ 'name' : iface.getAttribute('name'),
+ 'prop' : prop.getAttribute('name'),
+ })
+
+if __name__ == '__main__':
+ argv = argv[1:]
+ Generator(argv[0], argv[1], argv[2], xml.dom.minidom.parse(argv[3]))()
diff --git a/qt4/tools/glib-signals-marshal-gen.py b/qt4/tools/glib-signals-marshal-gen.py
new file mode 100644
index 000000000..0d02c1341
--- /dev/null
+++ b/qt4/tools/glib-signals-marshal-gen.py
@@ -0,0 +1,55 @@
+#!/usr/bin/python
+
+import sys
+import xml.dom.minidom
+from string import ascii_letters, digits
+
+
+from libglibcodegen import signal_to_marshal_name, method_to_glue_marshal_name
+
+
+class Generator(object):
+
+ def __init__(self, dom):
+ self.dom = dom
+ self.marshallers = {}
+
+ def do_method(self, method):
+ marshaller = method_to_glue_marshal_name(method, 'PREFIX')
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def do_signal(self, signal):
+ marshaller = signal_to_marshal_name(signal, 'PREFIX')
+
+ assert '__' in marshaller
+ rhs = marshaller.split('__', 1)[1].split('_')
+
+ self.marshallers[marshaller] = rhs
+
+ def __call__(self):
+ methods = self.dom.getElementsByTagName('method')
+
+ for method in methods:
+ self.do_method(method)
+
+ signals = self.dom.getElementsByTagName('signal')
+
+ for signal in signals:
+ self.do_signal(signal)
+
+ all = self.marshallers.keys()
+ all.sort()
+ for marshaller in all:
+ rhs = self.marshallers[marshaller]
+ if not marshaller.startswith('g_cclosure'):
+ print 'VOID:' + ','.join(rhs)
+
+if __name__ == '__main__':
+ argv = sys.argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+
+ Generator(dom)()
diff --git a/qt4/tools/libglibcodegen.py b/qt4/tools/libglibcodegen.py
new file mode 100644
index 000000000..6a9d21485
--- /dev/null
+++ b/qt4/tools/libglibcodegen.py
@@ -0,0 +1,172 @@
+"""Library code for GLib/D-Bus-related code generation.
+
+The master copy of this library is in the telepathy-glib repository -
+please make any changes there.
+"""
+
+# Copyright (C) 2006-2008 Collabora Limited
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from libtpcodegen import NS_TP, \
+ Signature, \
+ cmp_by_name, \
+ escape_as_identifier, \
+ get_by_path, \
+ get_descendant_text, \
+ get_docstring, \
+ xml_escape, \
+ get_deprecated
+
+def dbus_gutils_wincaps_to_uscore(s):
+ """Bug-for-bug compatible Python port of _dbus_gutils_wincaps_to_uscore
+ which gets sequences of capital letters wrong in the same way.
+ (e.g. in Telepathy, SendDTMF -> send_dt_mf)
+ """
+ ret = ''
+ for c in s:
+ if c >= 'A' and c <= 'Z':
+ length = len(ret)
+ if length > 0 and (length < 2 or ret[length-2] != '_'):
+ ret += '_'
+ ret += c.lower()
+ else:
+ ret += c
+ return ret
+
+
+def signal_to_marshal_type(signal):
+ """
+ return a list of strings indicating the marshalling type for this signal.
+ """
+
+ mtype=[]
+ for i in signal.getElementsByTagName("arg"):
+ name =i.getAttribute("name")
+ type = i.getAttribute("type")
+ mtype.append(type_to_gtype(type)[2])
+
+ return mtype
+
+
+_glib_marshallers = ['VOID', 'BOOLEAN', 'CHAR', 'UCHAR', 'INT',
+ 'STRING', 'UINT', 'LONG', 'ULONG', 'ENUM', 'FLAGS', 'FLOAT',
+ 'DOUBLE', 'STRING', 'PARAM', 'BOXED', 'POINTER', 'OBJECT',
+ 'UINT_POINTER']
+
+
+def signal_to_marshal_name(signal, prefix):
+
+ mtype = signal_to_marshal_type(signal)
+ if len(mtype):
+ name = '_'.join(mtype)
+ else:
+ name = 'VOID'
+
+ if name in _glib_marshallers:
+ return 'g_cclosure_marshal_VOID__' + name
+ else:
+ return prefix + '_marshal_VOID__' + name
+
+
+def method_to_glue_marshal_name(method, prefix):
+
+ mtype = []
+ for i in method.getElementsByTagName("arg"):
+ if i.getAttribute("direction") != "out":
+ type = i.getAttribute("type")
+ mtype.append(type_to_gtype(type)[2])
+
+ mtype.append('POINTER')
+
+ name = '_'.join(mtype)
+
+ if name in _glib_marshallers:
+ return 'g_cclosure_marshal_VOID__' + name
+ else:
+ return prefix + '_marshal_VOID__' + name
+
+
+def type_to_gtype(s):
+ if s == 'y': #byte
+ return ("guchar ", "G_TYPE_UCHAR","UCHAR", False)
+ elif s == 'b': #boolean
+ return ("gboolean ", "G_TYPE_BOOLEAN","BOOLEAN", False)
+ elif s == 'n': #int16
+ return ("gint ", "G_TYPE_INT","INT", False)
+ elif s == 'q': #uint16
+ return ("guint ", "G_TYPE_UINT","UINT", False)
+ elif s == 'i': #int32
+ return ("gint ", "G_TYPE_INT","INT", False)
+ elif s == 'u': #uint32
+ return ("guint ", "G_TYPE_UINT","UINT", False)
+ elif s == 'x': #int64
+ return ("gint64 ", "G_TYPE_INT64","INT64", False)
+ elif s == 't': #uint64
+ return ("guint64 ", "G_TYPE_UINT64","UINT64", False)
+ elif s == 'd': #double
+ return ("gdouble ", "G_TYPE_DOUBLE","DOUBLE", False)
+ elif s == 's': #string
+ return ("gchar *", "G_TYPE_STRING", "STRING", True)
+ elif s == 'g': #signature - FIXME
+ return ("gchar *", "DBUS_TYPE_G_SIGNATURE", "STRING", True)
+ elif s == 'o': #object path
+ return ("gchar *", "DBUS_TYPE_G_OBJECT_PATH", "BOXED", True)
+ elif s == 'v': #variant
+ return ("GValue *", "G_TYPE_VALUE", "BOXED", True)
+ elif s == 'as': #array of strings
+ return ("gchar **", "G_TYPE_STRV", "BOXED", True)
+ elif s == 'ay': #byte array
+ return ("GArray *",
+ "dbus_g_type_get_collection (\"GArray\", G_TYPE_UCHAR)", "BOXED",
+ True)
+ elif s == 'au': #uint array
+ return ("GArray *", "DBUS_TYPE_G_UINT_ARRAY", "BOXED", True)
+ elif s == 'ai': #int array
+ return ("GArray *", "DBUS_TYPE_G_INT_ARRAY", "BOXED", True)
+ elif s == 'ax': #int64 array
+ return ("GArray *", "DBUS_TYPE_G_INT64_ARRAY", "BOXED", True)
+ elif s == 'at': #uint64 array
+ return ("GArray *", "DBUS_TYPE_G_UINT64_ARRAY", "BOXED", True)
+ elif s == 'ad': #double array
+ return ("GArray *", "DBUS_TYPE_G_DOUBLE_ARRAY", "BOXED", True)
+ elif s == 'ab': #boolean array
+ return ("GArray *", "DBUS_TYPE_G_BOOLEAN_ARRAY", "BOXED", True)
+ elif s == 'ao': #object path array
+ return ("GPtrArray *",
+ 'dbus_g_type_get_collection ("GPtrArray",'
+ ' DBUS_TYPE_G_OBJECT_PATH)',
+ "BOXED", True)
+ elif s == 'a{ss}': #hash table of string to string
+ return ("GHashTable *", "DBUS_TYPE_G_STRING_STRING_HASHTABLE", "BOXED", False)
+ elif s[:2] == 'a{': #some arbitrary hash tables
+ if s[2] not in ('y', 'b', 'n', 'q', 'i', 'u', 's', 'o', 'g'):
+ raise Exception, "can't index a hashtable off non-basic type " + s
+ first = type_to_gtype(s[2])
+ second = type_to_gtype(s[3:-1])
+ return ("GHashTable *", "(dbus_g_type_get_map (\"GHashTable\", " + first[1] + ", " + second[1] + "))", "BOXED", False)
+ elif s[:2] in ('a(', 'aa'): # array of structs or arrays, recurse
+ gtype = type_to_gtype(s[1:])[1]
+ return ("GPtrArray *", "(dbus_g_type_get_collection (\"GPtrArray\", "+gtype+"))", "BOXED", True)
+ elif s[:1] == '(': #struct
+ gtype = "(dbus_g_type_get_struct (\"GValueArray\", "
+ for subsig in Signature(s[1:-1]):
+ gtype = gtype + type_to_gtype(subsig)[1] + ", "
+ gtype = gtype + "G_TYPE_INVALID))"
+ return ("GValueArray *", gtype, "BOXED", True)
+
+ # we just don't know ..
+ raise Exception, "don't know the GType for " + s
diff --git a/qt4/tools/libqt4codegen.py b/qt4/tools/libqt4codegen.py
new file mode 100644
index 000000000..dd2237891
--- /dev/null
+++ b/qt4/tools/libqt4codegen.py
@@ -0,0 +1,499 @@
+"""Library code for Qt4 D-Bus-related code generation.
+
+The master copy of this library is in the telepathy-qt4 repository -
+please make any changes there.
+"""
+
+# Copyright (C) 2008 Collabora Limited <http://www.collabora.co.uk>
+# Copyright (C) 2008 Nokia Corporation
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sys import maxint, stderr
+import re
+from libtpcodegen import get_by_path, get_descendant_text, NS_TP, xml_escape
+
+class Xzibit(Exception):
+ def __init__(self, parent, child):
+ self.parent = parent
+ self.child = child
+
+ def __str__(self):
+ print """
+ Nested <%s>s are forbidden.
+ Parent:
+ %s...
+ Child:
+ %s...
+ """ % (self.parent.nodeName, self.parent.toxml()[:100],
+ self.child.toxml()[:100])
+
+class _Qt4TypeBinding:
+ def __init__(self, val, inarg, outarg, array_val, custom_type, array_of,
+ array_depth=None):
+ self.val = val
+ self.inarg = inarg
+ self.outarg = outarg
+ self.array_val = array_val
+ self.custom_type = custom_type
+ self.array_of = array_of
+ self.array_depth = array_depth
+
+ if array_depth is None:
+ self.array_depth = int(bool(array_val))
+ elif array_depth >= 1:
+ assert array_val
+ else:
+ assert not array_val
+
+class RefTarget(object):
+ KIND_INTERFACE, KIND_METHOD, KIND_SIGNAL, KIND_PROPERTY = 'node', 'method', 'signal', 'property'
+
+ def __init__(self, el):
+ self.kind = el.localName
+ assert self.kind in (self.KIND_INTERFACE, self.KIND_METHOD, self.KIND_SIGNAL, self.KIND_PROPERTY)
+
+ if self.kind == self.KIND_INTERFACE:
+ self.dbus_text = el.getAttribute('name').lstrip('/').replace('_', '') + 'Interface'
+ else:
+ self.member_text = el.getAttribute('name')
+
+ assert el.parentNode.parentNode.localName == self.KIND_INTERFACE
+ host_class = el.parentNode.parentNode.getAttribute('name').lstrip('/').replace('_', '') + 'Interface'
+
+ if self.kind == self.KIND_PROPERTY:
+ self.member_link = 'requestProperty%s()' % (self.member_text)
+ self.dbus_link = '%s::%s' % (host_class, self.member_link)
+ else:
+ self.member_text = '%s()' % self.member_text
+
+ self.dbus_text = '%s::%s' % (host_class, self.member_text)
+
+class RefRegistry(object):
+ def __init__(self, spec):
+ self.targets = {}
+ for node in spec.getElementsByTagName('node'):
+ iface, = get_by_path(node, 'interface')
+ iface_name = iface.getAttribute('name')
+
+ self.targets[iface_name] = RefTarget(node)
+
+ for method in iface.getElementsByTagName(RefTarget.KIND_METHOD):
+ self.targets[iface_name + '.' + method.getAttribute('name')] = RefTarget(method)
+
+ for signal in iface.getElementsByTagName(RefTarget.KIND_SIGNAL):
+ self.targets[iface_name + '.' + signal.getAttribute('name')] = RefTarget(signal)
+
+ for prop in iface.getElementsByTagName(RefTarget.KIND_PROPERTY):
+ self.targets[iface_name + '.' + prop.getAttribute('name')] = RefTarget(prop)
+
+ def process(self, ref):
+ assert ref.namespaceURI == NS_TP
+
+ def get_closest_parent(el, needle):
+ node = el
+ while node is not None and node.localName != needle:
+ node = node.parentNode
+ return node
+
+ local = get_descendant_text(ref).strip()
+ if ref.localName == 'member-ref':
+ ns = get_closest_parent(ref, 'interface').getAttribute('name')
+ path = ns + '.' + local.strip()
+ else:
+ if ref.hasAttribute('namespace'):
+ ns = ref.getAttribute('namespace').replace('ofdT', 'org.freedesktop.Telepathy')
+ path = ns + '.' + local.strip()
+ else:
+ path = local
+
+ target = self.targets.get(path)
+
+ if target is None:
+ parent = get_closest_parent(ref, 'interface') or get_closest_parent(ref, 'error')
+ parent_name = parent.getAttribute('name')
+ if (path + parent_name).find('.DRAFT') == -1 and (path + parent_name).find('.FUTURE') == -1:
+ print >> stderr, 'WARNING: Failed to resolve %s to "%s" in "%s"' % (
+ ref.localName, path, parent_name)
+ return path
+
+ if ref.localName == 'member-ref':
+ if target.kind == target.KIND_PROPERTY:
+ return '\\link %s %s \\endlink' % (target.member_link, target.member_text)
+ else:
+ return target.member_text
+ else:
+ if target.kind == target.KIND_PROPERTY:
+ return '\\link %s %s \\endlink' % (target.dbus_link, target.dbus_text)
+ else:
+ return target.dbus_text
+
+def binding_from_usage(sig, tptype, custom_lists, external=False, explicit_own_ns=None):
+ # 'signature' : ('qt-type', 'pass-by-reference', 'array-type')
+ natives = {
+ 'y' : ('uchar', False, None),
+ 'b' : ('bool', False, 'BoolList'),
+ 'n' : ('short', False, 'ShortList'),
+ 'q' : ('ushort', False, 'UShortList'),
+ 'i' : ('int', False, 'IntList'),
+ 'u' : ('uint', False, 'UIntList'),
+ 'x' : ('qlonglong', False, 'LongLongList'),
+ 't' : ('qulonglong', False, 'ULongLongList'),
+ 'd' : ('double', False, 'DoubleList'),
+ 's' : ('QString', True, None),
+ 'v' : ('QDBusVariant', True, None),
+ 'o' : ('QDBusObjectPath', True, 'ObjectPathList'),
+ 'g' : ('QDBusSignature', True, 'SignatureList'),
+ 'as' : ('QStringList', True, "StringListList"),
+ 'ay' : ('QByteArray', True, "ByteArrayList"),
+ 'av' : ('QVariantList', True, "VariantListList"),
+ 'a{sv}' : ('QVariantMap', True, None)
+ }
+
+ val, inarg = None, None
+ custom_type = False
+ array_of = None
+
+ if natives.has_key(sig):
+ typename, pass_by_ref, array_name = natives[sig]
+ val = typename
+ inarg = (pass_by_ref and ('const %s&' % val)) or val
+ elif sig[0] == 'a' and natives.has_key(sig[1:]) and natives[sig[1:]][2]:
+ val = natives[sig[1:]][2]
+ if explicit_own_ns:
+ val = explicit_own_ns + '::' + val
+ inarg = 'const %s&' % val
+ array_of = natives[sig[1:]][0]
+ elif tptype:
+ tptype = tptype.replace('_', '')
+ custom_type = True
+
+ if external:
+ tptype = 'Tp::' + tptype
+ elif explicit_own_ns:
+ tptype = explicit_own_ns + '::' + tptype
+
+ if tptype.endswith('[]'):
+ tptype = tptype[:-2]
+ extra_list_nesting = 0
+
+ while tptype.endswith('[]'):
+ extra_list_nesting += 1
+ tptype = tptype[:-2]
+
+ assert custom_lists.has_key(tptype), ('No array version of custom type %s in the spec, but array version used' % tptype)
+ val = custom_lists[tptype] + 'List' * extra_list_nesting
+ else:
+ val = tptype
+
+ inarg = 'const %s&' % val
+ else:
+ assert False, 'Don\'t know how to map type (%s, %s)' % (sig, tptype)
+
+ outarg = val + '&'
+ return _Qt4TypeBinding(val, inarg, outarg, None, custom_type, array_of)
+
+def binding_from_decl(name, array_name, array_depth=None, external=False, explicit_own_ns=''):
+ val = name.replace('_', '')
+ if external:
+ val = 'Tp::' + val
+ elif explicit_own_ns:
+ val = explicit_own_ns + '::' + val
+ inarg = 'const %s&' % val
+ outarg = '%s&' % val
+ return _Qt4TypeBinding(val, inarg, outarg, array_name.replace('_', ''), True, None, array_depth)
+
+def extract_arg_or_member_info(els, custom_lists, externals, typesns, refs, docstring_indent=' * ', docstring_brackets=None, docstring_maxwidth=80):
+ names = []
+ docstrings = []
+ bindings = []
+
+ for el in els:
+ names.append(get_qt4_name(el))
+ docstrings.append(format_docstring(el, refs, docstring_indent, docstring_brackets, docstring_maxwidth))
+
+ sig = el.getAttribute('type')
+ tptype = el.getAttributeNS(NS_TP, 'type')
+ bindings.append(binding_from_usage(sig, tptype, custom_lists, (sig, tptype) in externals, typesns))
+
+ return names, docstrings, bindings
+
+def format_docstring(el, refs, indent=' * ', brackets=None, maxwidth=80):
+ docstring_el = None
+
+ for x in el.childNodes:
+ if x.namespaceURI == NS_TP and x.localName == 'docstring':
+ docstring_el = x
+
+ if not docstring_el:
+ return ''
+
+ lines = []
+
+ # escape backslashes, so they won't be interpreted starting doxygen commands and we can later
+ # insert doxygen commands we actually want
+ def escape_slashes(x):
+ if x.nodeType == x.TEXT_NODE:
+ x.data = x.data.replace('\\', '\\\\')
+ elif x.nodeType == x.ELEMENT_NODE:
+ for y in x.childNodes:
+ escape_slashes(y)
+ else:
+ return
+
+ escape_slashes(docstring_el)
+ doc = docstring_el.ownerDocument
+
+ for n in docstring_el.getElementsByTagNameNS(NS_TP, 'rationale'):
+ nested = n.getElementsByTagNameNS(NS_TP, 'rationale')
+ if nested:
+ raise Xzibit(n, nested[0])
+
+ div = doc.createElement('div')
+ div.setAttribute('class', 'rationale')
+
+ for rationale_body in n.childNodes:
+ div.appendChild(rationale_body.cloneNode(True))
+
+ n.parentNode.replaceChild(div, n)
+
+ if docstring_el.getAttribute('xmlns') == 'http://www.w3.org/1999/xhtml':
+ for ref in docstring_el.getElementsByTagNameNS(NS_TP, 'member-ref') + docstring_el.getElementsByTagNameNS(NS_TP, 'dbus-ref'):
+ nested = ref.getElementsByTagNameNS(NS_TP, 'member-ref') + ref.getElementsByTagNameNS(NS_TP, 'dbus-ref')
+ if nested:
+ raise Xzibit(n, nested[0])
+
+ text = doc.createTextNode(' \\endhtmlonly ')
+ text.data += refs.process(ref)
+ text.data += ' \\htmlonly '
+
+ ref.parentNode.replaceChild(text, ref)
+
+ splitted = ''.join([el.toxml() for el in docstring_el.childNodes]).strip(' ').strip('\n').split('\n')
+ level = min([not match and maxint or match.end() - 1 for match in [re.match('^ *[^ ]', line) for line in splitted]])
+ assert level != maxint
+ lines = ['\\htmlonly'] + [line[level:] for line in splitted] + ['\\endhtmlonly']
+ else:
+ content = xml_escape(get_descendant_text(docstring_el).replace('\n', ' ').strip())
+
+ while content.find(' ') != -1:
+ content = content.replace(' ', ' ')
+
+ left = maxwidth - len(indent) - 1
+ line = ''
+
+ while content:
+ step = (content.find(' ') + 1) or len(content)
+
+ if step > left:
+ lines.append(line)
+ line = ''
+ left = maxwidth - len(indent) - 1
+
+ left = left - step
+ line = line + content[:step]
+ content = content[step:]
+
+ if line:
+ lines.append(line)
+
+ output = []
+
+ if lines:
+ if brackets:
+ output.append(brackets[0])
+ else:
+ output.append(indent)
+
+ output.append('\n')
+
+ for line in lines:
+ output.append(indent)
+ output.append(line)
+ output.append('\n')
+
+ if lines and brackets:
+ output.append(brackets[1])
+ output.append('\n')
+
+ return ''.join(output)
+
+def gather_externals(spec):
+ externals = []
+
+ for ext in spec.getElementsByTagNameNS(NS_TP, 'external-type'):
+ sig = ext.getAttribute('type')
+ tptype = ext.getAttribute('name')
+ externals.append((sig, tptype))
+
+ return externals
+
+def gather_custom_lists(spec, typesns):
+ custom_lists = {}
+ structs = [(provider, typesns) for provider in spec.getElementsByTagNameNS(NS_TP, 'struct')]
+ mappings = [(provider, typesns) for provider in spec.getElementsByTagNameNS(NS_TP, 'mapping')]
+ exts = [(provider, 'Telepathy') for provider in spec.getElementsByTagNameNS(NS_TP, 'external-type')]
+
+ for (provider, ns) in structs + mappings + exts:
+ tptype = provider.getAttribute('name').replace('_', '')
+ array_val = provider.getAttribute('array-name').replace('_', '')
+ array_depth = provider.getAttribute('array-depth')
+ if array_depth:
+ array_depth = int(array_depth)
+ else:
+ array_depth = None
+
+ if array_val:
+ custom_lists[tptype] = array_val
+ custom_lists[ns + '::' + tptype] = ns + '::' + array_val
+ if array_depth >= 2:
+ for i in xrange(array_depth):
+ custom_lists[tptype + ('[]' * (i+1))] = (
+ array_val + ('List' * i))
+ custom_lists[ns + '::' + tptype + ('[]' * (i+1))] = (
+ ns + '::' + array_val + ('List' * i))
+
+ return custom_lists
+
+def get_headerfile_cmd(realinclude, prettyinclude, indent=' * '):
+ prettyinclude = prettyinclude or realinclude
+ if realinclude:
+ return indent + ('\\headerfile %s <%s>\n' % (realinclude, prettyinclude))
+ else:
+ return ''
+
+def get_qt4_name(el):
+ name = el.getAttribute('name')
+
+ if el.localName in ('method', 'signal', 'property'):
+ bname = el.getAttributeNS(NS_TP, 'name-for-bindings')
+
+ if bname:
+ name = bname
+
+ if not name:
+ return None
+
+ if name[0].isupper() and name[1].islower():
+ name = name[0].lower() + name[1:]
+
+ return qt4_identifier_escape(name.replace('_', ''))
+
+def qt4_identifier_escape(str):
+ built = (str[0].isdigit() and ['_']) or []
+
+ for c in str:
+ if c.isalnum():
+ built.append(c)
+ else:
+ built.append('_')
+
+ str = ''.join(built)
+
+ # List of reserved identifiers
+ # Initial list from http://cs.smu.ca/~porter/csc/ref/cpp_keywords.html
+
+ # Keywords inherited from C90
+ reserved = ['auto',
+ 'const',
+ 'double',
+ 'float',
+ 'int',
+ 'short',
+ 'struct',
+ 'unsigned',
+ 'break',
+ 'continue',
+ 'else',
+ 'for',
+ 'long',
+ 'signed',
+ 'switch',
+ 'void',
+ 'case',
+ 'default',
+ 'enum',
+ 'goto',
+ 'register',
+ 'sizeof',
+ 'typedef',
+ 'volatile',
+ 'char',
+ 'do',
+ 'extern',
+ 'if',
+ 'return',
+ 'static',
+ 'union',
+ 'while',
+ # C++-only keywords
+ 'asm',
+ 'dynamic_cast',
+ 'namespace',
+ 'reinterpret_cast',
+ 'try',
+ 'bool',
+ 'explicit',
+ 'new',
+ 'static_cast',
+ 'typeid',
+ 'catch',
+ 'false',
+ 'operator',
+ 'template',
+ 'typename',
+ 'class',
+ 'friend',
+ 'private',
+ 'this',
+ 'using',
+ 'const_cast',
+ 'inline',
+ 'public',
+ 'throw',
+ 'virtual',
+ 'delete',
+ 'mutable',
+ 'protected',
+ 'true',
+ 'wchar_t',
+ # Operator replacements
+ 'and',
+ 'bitand',
+ 'compl',
+ 'not_eq',
+ 'or_eq',
+ 'xor_eq',
+ 'and_eq',
+ 'bitor',
+ 'not',
+ 'or',
+ 'xor',
+ # Predefined identifiers
+ 'INT_MIN',
+ 'INT_MAX',
+ 'MAX_RAND',
+ 'NULL',
+ # Qt
+ 'SIGNAL',
+ 'SLOT',
+ 'signals',
+ 'slots']
+
+ while str in reserved:
+ str = str + '_'
+
+ return str
+
diff --git a/qt4/tools/libtpcodegen.py b/qt4/tools/libtpcodegen.py
new file mode 100644
index 000000000..837ff2f74
--- /dev/null
+++ b/qt4/tools/libtpcodegen.py
@@ -0,0 +1,215 @@
+"""Library code for language-independent D-Bus-related code generation.
+
+The master copy of this library is in the telepathy-glib repository -
+please make any changes there.
+"""
+
+# Copyright (C) 2006-2008 Collabora Limited
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from string import ascii_letters, digits
+
+
+NS_TP = "http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"
+
+_ASCII_ALNUM = ascii_letters + digits
+
+
+def cmp_by_name(node1, node2):
+ return cmp(node1.getAttributeNode("name").nodeValue,
+ node2.getAttributeNode("name").nodeValue)
+
+
+def escape_as_identifier(identifier):
+ """Escape the given string to be a valid D-Bus object path or service
+ name component, using a reversible encoding to ensure uniqueness.
+
+ The reversible encoding is as follows:
+
+ * The empty string becomes '_'
+ * Otherwise, each non-alphanumeric character is replaced by '_' plus
+ two lower-case hex digits; the same replacement is carried out on
+ the first character, if it's a digit
+ """
+ # '' -> '_'
+ if not identifier:
+ return '_'
+
+ # A bit of a fast path for strings which are already OK.
+ # We deliberately omit '_' because, for reversibility, that must also
+ # be escaped.
+ if (identifier.strip(_ASCII_ALNUM) == '' and
+ identifier[0] in ascii_letters):
+ return identifier
+
+ # The first character may not be a digit
+ if identifier[0] not in ascii_letters:
+ ret = ['_%02x' % ord(identifier[0])]
+ else:
+ ret = [identifier[0]]
+
+ # Subsequent characters may be digits or ASCII letters
+ for c in identifier[1:]:
+ if c in _ASCII_ALNUM:
+ ret.append(c)
+ else:
+ ret.append('_%02x' % ord(c))
+
+ return ''.join(ret)
+
+
+def get_by_path(element, path):
+ branches = path.split('/')
+ branch = branches[0]
+
+ # Is the current branch an attribute, if so, return the attribute value
+ if branch[0] == '@':
+ return element.getAttribute(branch[1:])
+
+ # Find matching children for the branch
+ children = []
+ if branch == '..':
+ children.append(element.parentNode)
+ else:
+ for x in element.childNodes:
+ if x.localName == branch:
+ children.append(x)
+
+ ret = []
+ # If this is not the last path element, recursively gather results from
+ # children
+ if len(branches) > 1:
+ for x in children:
+ add = get_by_path(x, '/'.join(branches[1:]))
+ if isinstance(add, list):
+ ret += add
+ else:
+ return add
+ else:
+ ret = children
+
+ return ret
+
+
+def get_docstring(element):
+ docstring = None
+ for x in element.childNodes:
+ if x.namespaceURI == NS_TP and x.localName == 'docstring':
+ docstring = x
+ if docstring is not None:
+ docstring = docstring.toxml().replace('\n', ' ').strip()
+ if docstring.startswith('<tp:docstring>'):
+ docstring = docstring[14:].lstrip()
+ if docstring.endswith('</tp:docstring>'):
+ docstring = docstring[:-15].rstrip()
+ if docstring in ('<tp:docstring/>', ''):
+ docstring = ''
+ return docstring
+
+def get_deprecated(element):
+ text = []
+ for x in element.childNodes:
+ if hasattr(x, 'data'):
+ text.append(x.data.replace('\n', ' ').strip())
+ else:
+ # This caters for tp:dbus-ref elements, but little else.
+ if x.childNodes and hasattr(x.childNodes[0], 'data'):
+ text.append(x.childNodes[0].data.replace('\n', ' ').strip())
+ return ' '.join(text)
+
+def get_descendant_text(element_or_elements):
+ if not element_or_elements:
+ return ''
+ if isinstance(element_or_elements, list):
+ return ''.join(map(get_descendant_text, element_or_elements))
+ parts = []
+ for x in element_or_elements.childNodes:
+ if x.nodeType == x.TEXT_NODE:
+ parts.append(x.nodeValue)
+ elif x.nodeType == x.ELEMENT_NODE:
+ parts.append(get_descendant_text(x))
+ else:
+ pass
+ return ''.join(parts)
+
+
+class _SignatureIter:
+ """Iterator over a D-Bus signature. Copied from dbus-python 0.71 so we
+ can run genginterface in a limited environment with only Python
+ (like Scratchbox).
+ """
+ def __init__(self, string):
+ self.remaining = string
+
+ def next(self):
+ if self.remaining == '':
+ raise StopIteration
+
+ signature = self.remaining
+ block_depth = 0
+ block_type = None
+ end = len(signature)
+
+ for marker in range(0, end):
+ cur_sig = signature[marker]
+
+ if cur_sig == 'a':
+ pass
+ elif cur_sig == '{' or cur_sig == '(':
+ if block_type == None:
+ block_type = cur_sig
+
+ if block_type == cur_sig:
+ block_depth = block_depth + 1
+
+ elif cur_sig == '}':
+ if block_type == '{':
+ block_depth = block_depth - 1
+
+ if block_depth == 0:
+ end = marker
+ break
+
+ elif cur_sig == ')':
+ if block_type == '(':
+ block_depth = block_depth - 1
+
+ if block_depth == 0:
+ end = marker
+ break
+
+ else:
+ if block_depth == 0:
+ end = marker
+ break
+
+ end = end + 1
+ self.remaining = signature[end:]
+ return Signature(signature[0:end])
+
+
+class Signature(str):
+ """A string, iteration over which is by D-Bus single complete types
+ rather than characters.
+ """
+ def __iter__(self):
+ return _SignatureIter(self)
+
+
+def xml_escape(s):
+ s = s.replace('&', '&amp;').replace("'", '&apos;').replace('"', '&quot;')
+ return s.replace('<', '&lt;').replace('>', '&gt;')
diff --git a/qt4/tools/manager-file.py b/qt4/tools/manager-file.py
new file mode 100644
index 000000000..45f640403
--- /dev/null
+++ b/qt4/tools/manager-file.py
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+
+# manager-file.py: generate .manager files and TpCMParamSpec arrays from the
+# same data (should be suitable for all connection managers that don't have
+# plugins)
+#
+# The master copy of this program is in the telepathy-glib repository -
+# please make any changes there.
+#
+# Copyright (c) Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import re
+import sys
+
+_NOT_C_STR = re.compile(r'[^A-Za-z0-9_-]')
+
+def c_string(x):
+ # whitelist-based brute force and ignorance - escape nearly all punctuation
+ return '"' + _NOT_C_STR.sub(lambda c: r'\x%02x' % ord(c), x) + '"'
+
+def desktop_string(x):
+ return x.replace(' ', r'\s').replace('\n', r'\n').replace('\r', r'\r').replace('\t', r'\t')
+
+supported = list('sbuiqn')
+
+fdefaultencoders = {
+ 's': desktop_string,
+ 'b': (lambda b: b and '1' or '0'),
+ 'u': (lambda n: '%u' % n),
+ 'i': (lambda n: '%d' % n),
+ 'q': (lambda n: '%u' % n),
+ 'n': (lambda n: '%d' % n),
+ }
+for x in supported: assert x in fdefaultencoders
+
+gtypes = {
+ 's': 'G_TYPE_STRING',
+ 'b': 'G_TYPE_BOOLEAN',
+ 'u': 'G_TYPE_UINT',
+ 'i': 'G_TYPE_INT',
+ 'q': 'G_TYPE_UINT',
+ 'n': 'G_TYPE_INT',
+}
+for x in supported: assert x in gtypes
+
+gdefaultencoders = {
+ 's': c_string,
+ 'b': (lambda b: b and 'GINT_TO_POINTER (TRUE)' or 'GINT_TO_POINTER (FALSE)'),
+ 'u': (lambda n: 'GUINT_TO_POINTER (%u)' % n),
+ 'i': (lambda n: 'GINT_TO_POINTER (%d)' % n),
+ 'q': (lambda n: 'GUINT_TO_POINTER (%u)' % n),
+ 'n': (lambda n: 'GINT_TO_POINTER (%d)' % n),
+ }
+for x in supported: assert x in gdefaultencoders
+
+gdefaultdefaults = {
+ 's': 'NULL',
+ 'b': 'GINT_TO_POINTER (FALSE)',
+ 'u': 'GUINT_TO_POINTER (0)',
+ 'i': 'GINT_TO_POINTER (0)',
+ 'q': 'GUINT_TO_POINTER (0)',
+ 'n': 'GINT_TO_POINTER (0)',
+ }
+for x in supported: assert x in gdefaultdefaults
+
+gflags = {
+ 'has-default': 'TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT',
+ 'register': 'TP_CONN_MGR_PARAM_FLAG_REGISTER',
+ 'required': 'TP_CONN_MGR_PARAM_FLAG_REQUIRED',
+ 'secret': 'TP_CONN_MGR_PARAM_FLAG_SECRET',
+ 'dbus-property': 'TP_CONN_MGR_PARAM_FLAG_DBUS_PROPERTY',
+}
+
+def write_manager(f, manager, protos):
+ # pointless backwards compat section
+ print >> f, '[ConnectionManager]'
+ print >> f, 'BusName=org.freedesktop.Telepathy.ConnectionManager.' + manager
+ print >> f, 'ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/' + manager
+
+ # protocols
+ for proto, params in protos.iteritems():
+ print >> f
+ print >> f, '[Protocol %s]' % proto
+
+ defaults = {}
+
+ for param, info in params.iteritems():
+ dtype = info['dtype']
+ flags = info.get('flags', '').split()
+ struct_field = info.get('struct_field', param.replace('-', '_'))
+ filter = info.get('filter', 'NULL')
+ filter_data = info.get('filter_data', 'NULL')
+ setter_data = 'NULL'
+
+ if 'default' in info:
+ default = fdefaultencoders[dtype](info['default'])
+ defaults[param] = default
+
+ if flags:
+ flags = ' ' + ' '.join(flags)
+ else:
+ flags = ''
+
+ print >> f, 'param-%s=%s%s' % (param, desktop_string(dtype), flags)
+
+ for param, default in defaults.iteritems():
+ print >> f, 'default-%s=%s' % (param, default)
+
+def write_c_params(f, manager, proto, struct, params):
+ print >> f, "static const TpCMParamSpec %s_%s_params[] = {" % (manager, proto)
+
+ for param, info in params.iteritems():
+ dtype = info['dtype']
+ flags = info.get('flags', '').split()
+ struct_field = info.get('struct_field', param.replace('-', '_'))
+ filter = info.get('filter', 'NULL')
+ filter_data = info.get('filter_data', 'NULL')
+ setter_data = 'NULL'
+
+ if 'default' in info:
+ default = gdefaultencoders[dtype](info['default'])
+ else:
+ default = gdefaultdefaults[dtype]
+
+ if flags:
+ flags = ' | '.join([gflags[flag] for flag in flags])
+ else:
+ flags = '0'
+
+ if struct is None or struct_field is None:
+ struct_offset = '0'
+ else:
+ struct_offset = 'G_STRUCT_OFFSET (%s, %s)' % (struct, struct_field)
+
+ print >> f, (''' { %s, %s, %s,
+ %s,
+ %s, /* default */
+ %s, /* struct offset */
+ %s, /* filter */
+ %s, /* filter data */
+ %s /* setter data */ },''' %
+ (c_string(param), c_string(dtype), gtypes[dtype], flags,
+ default, struct_offset, filter, filter_data, setter_data))
+
+ print >> f, " { NULL }"
+ print >> f, "};"
+
+if __name__ == '__main__':
+ environment = {}
+ execfile(sys.argv[1], environment)
+
+ f = open('%s/%s.manager' % (sys.argv[2], environment['MANAGER']), 'w')
+ write_manager(f, environment['MANAGER'], environment['PARAMS'])
+ f.close()
+
+ f = open('%s/param-spec-struct.h' % sys.argv[2], 'w')
+ for protocol in environment['PARAMS']:
+ write_c_params(f, environment['MANAGER'], protocol,
+ environment['STRUCTS'][protocol],
+ environment['PARAMS'][protocol])
+ f.close()
diff --git a/qt4/tools/qt4-client-gen.py b/qt4/tools/qt4-client-gen.py
new file mode 100644
index 000000000..465029be4
--- /dev/null
+++ b/qt4/tools/qt4-client-gen.py
@@ -0,0 +1,547 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Collabora Limited <http://www.collabora.co.uk>
+# Copyright (C) 2008 Nokia Corporation
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sys import argv
+import xml.dom.minidom
+import codecs
+from getopt import gnu_getopt
+
+from libtpcodegen import NS_TP, get_descendant_text, get_by_path
+from libqt4codegen import binding_from_usage, extract_arg_or_member_info, format_docstring, gather_externals, gather_custom_lists, get_headerfile_cmd, get_qt4_name, qt4_identifier_escape, RefRegistry
+
+class Generator(object):
+ def __init__(self, opts):
+ try:
+ self.group = opts.get('--group', '')
+ self.headerfile = opts['--headerfile']
+ self.implfile = opts['--implfile']
+ self.namespace = opts['--namespace']
+ self.typesnamespace = opts['--typesnamespace']
+ self.realinclude = opts['--realinclude']
+ self.prettyinclude = opts.get('--prettyinclude')
+ self.extraincludes = opts.get('--extraincludes', None)
+ self.mainiface = opts.get('--mainiface', None)
+ self.must_define = opts.get('--must-define', None)
+ self.dbus_proxy = opts.get('--dbus-proxy',
+ 'Tp::DBusProxy')
+ self.visibility = opts.get('--visibility', '')
+ ifacedom = xml.dom.minidom.parse(opts['--ifacexml'])
+ specdom = xml.dom.minidom.parse(opts['--specxml'])
+ except KeyError, k:
+ assert False, 'Missing required parameter %s' % k.args[0]
+
+ self.hs = []
+ self.bs = []
+ self.ifacenodes = ifacedom.getElementsByTagName('node')
+ self.spec, = get_by_path(specdom, "spec")
+ self.custom_lists = gather_custom_lists(self.spec, self.typesnamespace)
+ self.externals = gather_externals(self.spec)
+ self.refs = RefRegistry(self.spec)
+
+ def __call__(self):
+ # Output info header and includes
+ self.h("""\
+/*
+ * This file contains D-Bus client proxy classes generated by qt4-client-gen.py.
+ *
+ * This file can be distributed under the same terms as the specification from
+ * which it was generated.
+ */
+""")
+
+ if self.must_define:
+ self.h('\n')
+ self.h('#ifndef %s\n' % self.must_define)
+ self.h('#error %s\n' % self.must_define)
+ self.h('#endif\n')
+
+ self.h('\n')
+
+ if self.extraincludes:
+ for include in self.extraincludes.split(','):
+ self.h('#include %s\n' % include)
+
+ self.h("""
+#include <QtGlobal>
+
+#include <QString>
+#include <QObject>
+#include <QVariant>
+
+#include <QDBusPendingReply>
+
+#include <TelepathyQt4/AbstractInterface>
+#include <TelepathyQt4/DBusProxy>
+#include <TelepathyQt4/Global>
+
+namespace Tp
+{
+class PendingVariant;
+class PendingOperation;
+}
+
+""")
+
+ if self.must_define:
+ self.b("""#define %s\n""" % (self.must_define))
+
+ self.b("""#include "%s"
+
+""" % self.realinclude)
+
+ # Begin namespace
+ for ns in self.namespace.split('::'):
+ self.hb("""\
+namespace %s
+{
+""" % ns)
+
+ # Output interface proxies
+ def ifacenodecmp(x, y):
+ xname, yname = [self.namespace + '::' + node.getAttribute('name').replace('/', '').replace('_', '') + 'Interface' for node in x, y]
+
+ if xname == self.mainiface:
+ return -1
+ elif yname == self.mainiface:
+ return 1
+ else:
+ return cmp(xname, yname)
+
+ self.ifacenodes.sort(cmp=ifacenodecmp)
+ for ifacenode in self.ifacenodes:
+ self.do_ifacenode(ifacenode)
+
+ # End namespace
+ self.hb(''.join(['}\n' for ns in self.namespace.split('::')]))
+
+ # Add metatype declaration - otherwise QTBUG #2151 might be triggered
+ for ifacenode in self.ifacenodes:
+ classname = ifacenode.getAttribute('name').replace('/', '').replace('_', '') + 'Interface'
+ self.h("Q_DECLARE_METATYPE(" + self.namespace + "::" + classname + "*)\n")
+
+ # Write output to files
+ (codecs.getwriter('utf-8')(open(self.headerfile, 'w'))).write(''.join(self.hs))
+ (codecs.getwriter('utf-8')(open(self.implfile, 'w'))).write(''.join(self.bs))
+
+ def do_ifacenode(self, ifacenode):
+ # Extract info
+ name = ifacenode.getAttribute('name').replace('/', '').replace('_', '') + 'Interface'
+ iface, = get_by_path(ifacenode, 'interface')
+ dbusname = iface.getAttribute('name')
+
+ # Begin class, constructors
+ self.h("""
+/**
+ * \\class %(name)s
+%(headercmd)s\
+%(groupcmd)s\
+ *
+ * Proxy class providing a 1:1 mapping of the D-Bus interface "%(dbusname)s."
+ */
+class %(visibility)s %(name)s : public Tp::AbstractInterface
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Returns the name of the interface "%(dbusname)s", which this class
+ * represents.
+ *
+ * \\return The D-Bus interface name.
+ */
+ static inline QLatin1String staticInterfaceName()
+ {
+ return QLatin1String("%(dbusname)s");
+ }
+
+ /**
+ * Creates a %(name)s associated with the given object on the session bus.
+ *
+ * \\param busName Name of the service the object is on.
+ * \\param objectPath Path to the object on the service.
+ * \\param parent Passed to the parent class constructor.
+ */
+ %(name)s(
+ const QString& busName,
+ const QString& objectPath,
+ QObject* parent = 0
+ );
+
+ /**
+ * Creates a %(name)s associated with the given object on the given bus.
+ *
+ * \\param connection The bus via which the object can be reached.
+ * \\param busName Name of the service the object is on.
+ * \\param objectPath Path to the object on the service.
+ * \\param parent Passed to the parent class constructor.
+ */
+ %(name)s(
+ const QDBusConnection& connection,
+ const QString& busName,
+ const QString& objectPath,
+ QObject* parent = 0
+ );
+""" % {'name' : name,
+ 'headercmd' : get_headerfile_cmd(self.realinclude, self.prettyinclude),
+ 'groupcmd' : self.group and (' * \\ingroup %s\n' % self.group),
+ 'dbusname' : dbusname,
+ 'visibility': self.visibility,
+ })
+
+ self.b("""
+%(name)s::%(name)s(const QString& busName, const QString& objectPath, QObject *parent)
+ : Tp::AbstractInterface(busName, objectPath, staticInterfaceName(), QDBusConnection::sessionBus(), parent)
+{
+}
+
+%(name)s::%(name)s(const QDBusConnection& connection, const QString& busName, const QString& objectPath, QObject *parent)
+ : Tp::AbstractInterface(busName, objectPath, staticInterfaceName(), connection, parent)
+{
+}
+""" % {'name' : name})
+
+ # Construct from DBusProxy subclass
+ self.h("""
+ /**
+ * Creates a %(name)s associated with the same object as the given proxy.
+ *
+ * \\param proxy The proxy to use. It will also be the QObject::parent()
+ * for this object.
+ */
+ %(name)s(%(dbus_proxy)s *proxy);
+""" % {'name' : name,
+ 'dbus_proxy' : self.dbus_proxy})
+
+ self.b("""
+%(name)s::%(name)s(%(dbus_proxy)s *proxy)
+ : Tp::AbstractInterface(proxy, staticInterfaceName())
+{
+}
+""" % {'name' : name,
+ 'dbus_proxy' : self.dbus_proxy})
+
+ # Main interface
+ mainiface = self.mainiface or 'Tp::AbstractInterface'
+
+ if mainiface != self.namespace + '::' + name:
+ self.h("""
+ /**
+ * Creates a %(name)s associated with the same object as the given proxy.
+ * Additionally, the created proxy will have the same parent as the given
+ * proxy.
+ *
+ * \\param mainInterface The proxy to use.
+ */
+ explicit %(name)s(const %(mainiface)s& mainInterface);
+
+ /**
+ * Creates a %(name)s associated with the same object as the given proxy.
+ * However, a different parent object can be specified.
+ *
+ * \\param mainInterface The proxy to use.
+ * \\param parent Passed to the parent class constructor.
+ */
+ %(name)s(const %(mainiface)s& mainInterface, QObject* parent);
+""" % {'name' : name,
+ 'mainiface' : mainiface})
+
+ self.b("""
+%(name)s::%(name)s(const %(mainiface)s& mainInterface)
+ : Tp::AbstractInterface(mainInterface.service(), mainInterface.path(), staticInterfaceName(), mainInterface.connection(), mainInterface.parent())
+{
+}
+
+%(name)s::%(name)s(const %(mainiface)s& mainInterface, QObject *parent)
+ : Tp::AbstractInterface(mainInterface.service(), mainInterface.path(), staticInterfaceName(), mainInterface.connection(), parent)
+{
+}
+""" % {'name' : name,
+ 'mainiface' : mainiface})
+
+ # Properties
+ has_props = False
+ for prop in get_by_path(iface, 'property'):
+ # Skip tp:properties
+ if not prop.namespaceURI:
+ self.do_prop(prop)
+ has_props = True
+
+ self.h("""
+ /**
+ * Request all of the DBus properties on the interface.
+ *
+ * \\return A pending variant map which will emit finished when the properties have
+ * been retrieved.
+ */
+ Tp::PendingVariantMap *requestAllProperties() const
+ {
+ return internalRequestAllProperties();
+ }
+""")
+
+ # Methods
+ methods = get_by_path(iface, 'method')
+
+ if methods:
+ self.h("""
+public Q_SLOTS:\
+""")
+
+ for method in methods:
+ self.do_method(method)
+
+ # Signals
+ signals = get_by_path(iface, 'signal')
+
+ if signals:
+ self.h("""
+Q_SIGNALS:\
+""")
+
+ for signal in signals:
+ self.do_signal(signal)
+
+ # invalidated handler (already a slot in the superclass)
+ # we can't just use disconnect(this, NULL, NULL, NULL) because
+ # (a) that would disconnect QObject::destroyed() and other non-D-Bus
+ # signals, and (b) QtDBus doesn't support that usage anyway (it needs
+ # specific signals in order to remove its signal match rules)
+ self.h("""
+protected:
+ virtual void invalidate(Tp::DBusProxy *, const QString &, const QString &);
+""")
+
+ self.b("""
+void %(name)s::invalidate(Tp::DBusProxy *proxy,
+ const QString &error, const QString &message)
+{
+""" % {'name' : name})
+
+ for signal in signals:
+ self.do_signal_disconnect(signal)
+
+ self.b("""
+ Tp::AbstractInterface::invalidate(proxy, error, message);
+}
+""")
+
+ # Close class
+ self.h("""\
+};
+""")
+
+ def do_prop(self, prop):
+ name = prop.getAttribute('name')
+ access = prop.getAttribute('access')
+ gettername = name
+ settername = None
+ docstring = format_docstring(prop, self.refs, ' * ').replace('*/', '&#42;&#47;')
+
+ sig = prop.getAttribute('type')
+ tptype = prop.getAttributeNS(NS_TP, 'type')
+ binding = binding_from_usage(sig, tptype, self.custom_lists, (sig, tptype) in self.externals, self.typesnamespace)
+
+ if 'write' in access:
+ settername = 'set' + name
+
+ if 'read' in access:
+ self.h("""
+ /**
+ * Asynchronous getter for the remote object property \\c %(name)s of type \\c %(val)s.
+ *
+%(docstring)s\
+ *
+ * \\return A pending variant which will emit finished when the property has been
+ * retrieved.
+ */
+ inline Tp::PendingVariant *%(gettername)s() const
+ {
+ return internalRequestProperty(QLatin1String("%(name)s"));
+ }
+""" % {'name' : name,
+ 'docstring' : docstring,
+ 'val' : binding.val,
+ 'gettername' : 'requestProperty' + name})
+
+ if 'write' in access:
+ self.h("""
+ /**
+ * Asynchronous setter for the remote object property \\c %(name)s of type \\c %(type)s.
+ *
+%(docstring)s\
+ *
+ * \\return A pending operation which will emit finished when the property has been
+ * set.
+ */
+ inline Tp::PendingOperation *%(settername)s(%(type)s newValue)
+ {
+ return internalSetProperty(QLatin1String("%(name)s"), QVariant::fromValue(newValue));
+ }
+""" % {'name' : name,
+ 'docstring' : docstring,
+ 'type' : binding.val,
+ 'name' : name,
+ 'settername' : 'setProperty' + name})
+
+ def do_method(self, method):
+ name = method.getAttribute('name')
+ args = get_by_path(method, 'arg')
+ argnames, argdocstrings, argbindings = extract_arg_or_member_info(args, self.custom_lists,
+ self.externals, self.typesnamespace, self.refs, ' * ')
+
+ inargs = []
+ outargs = []
+
+ for i in xrange(len(args)):
+ if args[i].getAttribute('direction') == 'out':
+ outargs.append(i)
+ else:
+ inargs.append(i)
+ assert argnames[i] != None, 'No argument name for input argument at index %d for method %s' % (i, name)
+
+ rettypes = ', '.join([argbindings[i].val for i in outargs])
+ params = ', '.join([argbindings[i].inarg + ' ' + argnames[i] for i in inargs])
+ if params:
+ params += ', int timeout = -1'
+ else:
+ params = 'int timeout = -1'
+
+ self.h("""
+ /**
+ * Begins a call to the D-Bus method \\c %s on the remote object.
+%s\
+ *
+ * Note that \\a timeout is ignored as of now. It will be used once
+ * http://bugreports.qt.nokia.com/browse/QTBUG-11775 is fixed.
+ *
+""" % (name, format_docstring(method, self.refs, ' * ')))
+
+ for i in inargs:
+ if argdocstrings[i]:
+ self.h("""\
+ *
+ * \\param %s
+%s\
+""" % (argnames[i], argdocstrings[i]))
+
+ self.h("""\
+ * \\param timeout The timeout in milliseconds.
+""")
+
+ for i in outargs:
+ if argdocstrings[i]:
+ self.h("""\
+ *
+ * \\return
+%s\
+""" % argdocstrings[i])
+
+ self.h("""\
+ */
+ inline QDBusPendingReply<%(rettypes)s> %(name)s(%(params)s)
+ {
+ if (!invalidationReason().isEmpty()) {
+ return QDBusPendingReply<%(rettypes)s>(QDBusMessage::createError(
+ invalidationReason(),
+ invalidationMessage()
+ ));
+ }
+""" % {'rettypes' : rettypes,
+ 'name' : name,
+ 'params' : params})
+
+ if inargs:
+ self.h("""
+ QDBusMessage callMessage = QDBusMessage::createMethodCall(this->service(), this->path(),
+ this->staticInterfaceName(), QLatin1String("%s"));
+ callMessage << %s;
+ return this->connection().asyncCall(callMessage, timeout);
+ }
+""" % (name, ' << '.join(['QVariant::fromValue(%s)' % argnames[i] for i in inargs])))
+ else:
+ self.h("""
+ QDBusMessage callMessage = QDBusMessage::createMethodCall(this->service(), this->path(),
+ this->staticInterfaceName(), QLatin1String("%s"));
+ return this->connection().asyncCall(callMessage, timeout);
+ }
+""" % name)
+
+ def do_signal(self, signal):
+ name = signal.getAttribute('name')
+ argnames, argdocstrings, argbindings = extract_arg_or_member_info(get_by_path(signal,
+ 'arg'), self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ')
+
+ self.h("""
+ /**
+ * Represents the signal \\c %s on the remote object.
+%s\
+""" % (name, format_docstring(signal, self.refs, ' * ')))
+
+ for i in xrange(len(argnames)):
+ assert argnames[i] != None, 'Name missing from argument at index %d for signal %s' % (i, name)
+ if argdocstrings[i]:
+ self.h("""\
+ *
+ * \\param %s
+%s\
+""" % (argnames[i], argdocstrings[i]))
+
+ self.h("""\
+ */
+ void %s(%s);
+""" % (name, ', '.join(['%s %s' % (binding.inarg, name) for binding, name in zip(argbindings, argnames)])))
+
+ def do_signal_disconnect(self, signal):
+ name = signal.getAttribute('name')
+ _, _, argbindings = extract_arg_or_member_info(get_by_path(signal, 'arg'),
+ self.custom_lists, self.externals, self.typesnamespace, self.refs, ' * ')
+
+ self.b("""\
+ disconnect(this, SIGNAL(%s(%s)), NULL, NULL);
+""" % (name, ', '.join([binding.inarg for binding in argbindings])))
+
+ def h(self, str):
+ self.hs.append(str)
+
+ def b(self, str):
+ self.bs.append(str)
+
+ def hb(self, str):
+ self.h(str)
+ self.b(str)
+
+
+if __name__ == '__main__':
+ options, argv = gnu_getopt(argv[1:], '',
+ ['group=',
+ 'namespace=',
+ 'typesnamespace=',
+ 'headerfile=',
+ 'implfile=',
+ 'ifacexml=',
+ 'specxml=',
+ 'realinclude=',
+ 'prettyinclude=',
+ 'extraincludes=',
+ 'mainiface=',
+ 'must-define=',
+ 'dbus-proxy=',
+ 'visibility='])
+
+ Generator(dict(options))()
diff --git a/qt4/tools/qt4-constants-gen.py b/qt4/tools/qt4-constants-gen.py
new file mode 100644
index 000000000..a28050a7a
--- /dev/null
+++ b/qt4/tools/qt4-constants-gen.py
@@ -0,0 +1,310 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Collabora Limited <http://www.collabora.co.uk>
+# Copyright (C) 2008 Nokia Corporation
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sys import argv, stdout, stderr
+import codecs
+import xml.dom.minidom
+from getopt import gnu_getopt
+
+from libtpcodegen import NS_TP, get_descendant_text, get_by_path
+from libqt4codegen import format_docstring, RefRegistry
+
+class Generator(object):
+ def __init__(self, opts):
+ try:
+ self.namespace = opts['--namespace']
+ self.must_define = opts.get('--must-define', None)
+ dom = xml.dom.minidom.parse(opts['--specxml'])
+ except KeyError, k:
+ assert False, 'Missing required parameter %s' % k.args[0]
+
+ self.define_prefix = None
+ if '--define-prefix' in opts:
+ self.define_prefix = opts['--define-prefix']
+
+ self.old_prefix = None
+ if '--str-constant-prefix' in opts:
+ self.old_prefix = opts['--str-constant-prefix']
+
+ self.spec = get_by_path(dom, "spec")[0]
+ self.out = codecs.getwriter('utf-8')(stdout)
+ self.refs = RefRegistry(self.spec)
+
+ def h(self, code):
+ self.out.write(code)
+
+ def __call__(self):
+ # Header
+ self.h('/* Generated from ')
+ self.h(get_descendant_text(get_by_path(self.spec, 'title')))
+ version = get_by_path(self.spec, "version")
+
+ if version:
+ self.h(', version ' + get_descendant_text(version))
+
+ self.h("""
+ */
+ """)
+
+ if self.must_define:
+ self.h("""
+#ifndef %s
+#error %s
+#endif
+""" % (self.must_define, self.must_define))
+
+ self.h("""
+#include <QFlags>
+
+/**
+ * \\addtogroup typesconstants Types and constants
+ *
+ * Enumerated, flag, structure, list and mapping types and utility constants.
+ */
+
+/**
+ * \\defgroup flagtypeconsts Flag type constants
+ * \\ingroup typesconstants
+ *
+ * Types generated from the specification representing bit flag constants and
+ * combinations of them (bitfields).
+ */
+
+/**
+ * \\defgroup enumtypeconsts Enumerated type constants
+ * \\ingroup typesconstants
+ *
+ * Types generated from the specification representing enumerated types ie.
+ * types the values of which are mutually exclusive integral constants.
+ */
+
+/**
+ * \\defgroup ifacestrconsts Interface string constants
+ * \\ingroup typesconstants
+ *
+ * D-Bus interface names of the interfaces in the specification.
+ */
+
+/**
+ * \\defgroup errorstrconsts Error string constants
+ * \\ingroup typesconstants
+ *
+ * Names of the D-Bus errors in the specification.
+ */
+""")
+
+ # Begin namespace
+ self.h("""
+namespace %s
+{
+""" % self.namespace)
+
+ # Flags
+ for flags in self.spec.getElementsByTagNameNS(NS_TP, 'flags'):
+ self.do_flags(flags)
+
+ # Enums
+ for enum in self.spec.getElementsByTagNameNS(NS_TP, 'enum'):
+ self.do_enum(enum)
+
+ # End namespace
+ self.h("""\
+}
+
+""")
+
+ # Interface names
+ for iface in self.spec.getElementsByTagName('interface'):
+ if self.old_prefix:
+ self.h("""\
+/**
+ * \\ingroup ifacestrconsts
+ *
+ * The interface name "%(name)s".
+ */
+#define %(DEFINE)s "%(name)s"
+
+""" % {'name' : iface.getAttribute('name'),
+ 'DEFINE' : self.old_prefix + 'INTERFACE_' + get_by_path(iface, '../@name').upper().replace('/', '')})
+
+ if self.define_prefix:
+ self.h("""\
+/**
+ * \\ingroup ifacestrconsts
+ *
+ * The interface name "%(name)s" as a QLatin1String, usable in QString requiring contexts even when
+ * building with Q_NO_CAST_FROM_ASCII defined.
+ */
+#define %(DEFINE)s (QLatin1String("%(name)s"))
+
+""" % {'name' : iface.getAttribute('name'),
+ 'DEFINE' : self.define_prefix + 'IFACE_' + get_by_path(iface, '../@name').upper().replace('/', '')})
+
+ # Error names
+ for error in get_by_path(self.spec, 'errors/error'):
+ name = error.getAttribute('name')
+ fullname = get_by_path(error, '../@namespace') + '.' + name.replace(' ', '')
+
+ if self.old_prefix:
+ define = self.old_prefix + 'ERROR_' + name.replace(' ', '_').replace('.', '_').upper()
+ self.h("""\
+/**
+ * \\ingroup errorstrconsts
+ *
+ * The error name "%(fullname)s".
+%(docstring)s\
+ */
+#define %(DEFINE)s "%(fullname)s"
+
+""" % {'fullname' : fullname,
+ 'docstring': format_docstring(error, self.refs),
+ 'DEFINE' : define})
+
+ if self.define_prefix:
+ define = self.define_prefix + 'ERROR_' + name.replace(' ', '_').replace('.', '_').upper()
+ self.h("""\
+/**
+ * \\ingroup errorstrconsts
+ *
+ * The error name "%(fullname)s" as a QLatin1String, usable in QString requiring contexts even when
+ * building with Q_NO_CAST_FROM_ASCII defined.
+%(docstring)s\
+ */
+#define %(DEFINE)s QLatin1String("%(fullname)s")
+
+""" % {'fullname' : fullname,
+ 'docstring': format_docstring(error, self.refs),
+ 'DEFINE' : define})
+
+ def do_flags(self, flags):
+ singular = flags.getAttribute('singular') or \
+ flags.getAttribute('value-prefix')
+
+ using_name = False
+ if not singular:
+ using_name = True
+ singular = flags.getAttribute('name')
+
+ if singular.endswith('lags'):
+ singular = singular[:-1]
+
+ if using_name and singular.endswith('s'):
+ singular = singular[:-1]
+
+ singular = singular.replace('_', '')
+ plural = (flags.getAttribute('plural') or flags.getAttribute('name') or singular + 's').replace('_', '')
+ self.h("""\
+/**
+ * \\ingroup flagtypeconsts
+ *
+ * Flag type generated from the specification.
+ */
+enum %(singular)s
+{
+""" % {'singular' : singular})
+
+ flagvalues = get_by_path(flags, 'flag')
+
+ for flag in flagvalues:
+ self.do_val(flag, singular, flag == flagvalues[-1])
+
+ self.h("""\
+ %s = 0xffffffffU
+""" % ("_" + singular + "Padding"))
+
+ self.h("""\
+};
+
+/**
+ * \\typedef QFlags<%(singular)s> %(plural)s
+ * \\ingroup flagtypeconsts
+ *
+ * Type representing combinations of #%(singular)s values.
+%(docstring)s\
+ */
+typedef QFlags<%(singular)s> %(plural)s;
+Q_DECLARE_OPERATORS_FOR_FLAGS(%(plural)s)
+
+""" % {'singular' : singular, 'plural' : plural, 'docstring' : format_docstring(flags, self.refs)})
+
+ def do_enum(self, enum):
+ singular = enum.getAttribute('singular') or \
+ enum.getAttribute('name')
+ value_prefix = enum.getAttribute('singular') or \
+ enum.getAttribute('value-prefix') or \
+ enum.getAttribute('name')
+
+ if singular.endswith('lags'):
+ singular = singular[:-1]
+
+ plural = enum.getAttribute('plural') or singular + 's'
+ singular = singular.replace('_', '')
+ value_prefix = value_prefix.replace('_', '')
+ vals = get_by_path(enum, 'enumvalue')
+
+ self.h("""\
+/**
+ * \\enum %(singular)s
+ * \\ingroup enumtypeconsts
+ *
+ * Enumerated type generated from the specification.
+%(docstring)s\
+ */
+enum %(singular)s
+{
+""" % {'singular' : singular, 'docstring' : format_docstring(enum, self.refs)})
+
+ for val in vals:
+ self.do_val(val, value_prefix, val == vals[-1])
+
+ self.h("""\
+ %s = 0xffffffffU
+};
+
+""" % ("_" + singular + "Padding"))
+
+ self.h("""\
+/**
+ * \\ingroup enumtypeconsts
+ *
+ * 1 higher than the highest valid value of %(singular)s.
+ */
+const int NUM_%(upper-plural)s = (%(last-val)s+1);
+
+""" % {'singular' : singular,
+ 'upper-plural' : plural.upper(),
+ 'last-val' : vals[-1].getAttribute('value')})
+
+ def do_val(self, val, prefix, last):
+ name = (val.getAttribute('suffix') or val.getAttribute('name')).replace('_', '')
+ self.h("""\
+%s\
+ %s = %s,
+
+""" % (format_docstring(val, self.refs, indent=' * ', brackets=(' /**', ' */')), prefix + name, val.getAttribute('value')))
+
+if __name__ == '__main__':
+ options, argv = gnu_getopt(argv[1:], '',
+ ['namespace=',
+ 'str-constant-prefix=',
+ 'define-prefix=',
+ 'must-define=',
+ 'specxml='])
+
+ Generator(dict(options))()
diff --git a/qt4/tools/qt4-types-gen.py b/qt4/tools/qt4-types-gen.py
new file mode 100644
index 000000000..0f5545d60
--- /dev/null
+++ b/qt4/tools/qt4-types-gen.py
@@ -0,0 +1,557 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Collabora Limited <http://www.collabora.co.uk>
+# Copyright (C) 2008 Nokia Corporation
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import xml.dom.minidom
+from getopt import gnu_getopt
+
+from libtpcodegen import NS_TP, get_descendant_text, get_by_path
+from libqt4codegen import binding_from_usage, binding_from_decl, extract_arg_or_member_info, format_docstring, gather_externals, gather_custom_lists, get_qt4_name, get_headerfile_cmd, RefRegistry
+
+class BrokenSpecException(Exception):
+ pass
+
+class MissingTypes(BrokenSpecException):
+ def __init__(self, types):
+ super(MissingTypes, self).__init__(self)
+ self.types = types
+
+ def __str__(self):
+ typelist = ''.join([' %s' % t for t in self.types])
+ return "The following types were used, but not provided by the spec " \
+ "or by <tp:external-type/> declarations in all.xml:\n%s" % typelist
+
+class UnresolvedDependency(BrokenSpecException):
+ def __init__(self, child, parent):
+ super(UnresolvedDependency, self).__init__(self)
+ self.child = child
+ self.parent = parent
+
+ def __str__(self):
+ return 'Type %s has unresolved dependency on %s' % (
+ self.child, self.parent)
+
+class EmptyStruct(BrokenSpecException):
+ def __init__(self, struct_name):
+ super(EmptyStruct, self).__init__(self)
+ self.struct_name = struct_name
+
+ def __str__(self):
+ return 'tp:struct %s should have some members' % self.struct_name
+
+class MalformedMapping(BrokenSpecException):
+ def __init__(self, mapping_name, members):
+ super(MalformedMapping, self).__init__(self)
+ self.mapping_name = mapping_name
+ self.members = members
+
+ def __str__(self):
+ return 'tp:mapping %s should have 2 members, not %u' % (
+ self.mapping_name, self.members)
+
+class WTF(BrokenSpecException):
+ def __init__(self, element_name):
+ super(BrokenSpecException, self).__init__(self)
+ self.element_name = element_name
+
+ def __str__(self):
+ return 'What the hell is a tp:%s?' % self.element_name
+
+
+class DepInfo:
+ def __init__(self, el, externals, custom_lists):
+ self.el = el
+ name = get_by_path(el, '@name')
+ array_name = get_by_path(el, '@array-name')
+ array_depth = get_by_path(el, '@array-depth')
+ if array_depth:
+ array_depth = int(array_depth)
+ else:
+ array_depth = None
+ self.binding = binding_from_decl(name, array_name, array_depth)
+ self.deps = []
+
+ for member in get_by_path(el, 'member'):
+ sig = member.getAttribute('type')
+ tptype = member.getAttributeNS(NS_TP, 'type')
+
+ if (sig, tptype) in externals:
+ continue
+
+ if tptype.endswith('[]'):
+ tptype = tptype[:-2]
+
+ binding = binding_from_usage(sig, tptype, custom_lists)
+
+ if binding.custom_type:
+ self.deps.append(binding.val)
+
+ self.revdeps = []
+
+class Generator(object):
+ def __init__(self, opts):
+ try:
+ self.namespace = opts['--namespace']
+ self.declfile = opts['--declfile']
+ self.implfile = opts['--implfile']
+ self.realinclude = opts['--realinclude']
+ self.prettyinclude = opts.get('--prettyinclude', self.realinclude)
+ self.extraincludes = opts.get('--extraincludes', None)
+ self.must_define = opts.get('--must-define', None)
+ self.visibility = opts.get('--visibility', '')
+ dom = xml.dom.minidom.parse(opts['--specxml'])
+ except KeyError, k:
+ assert False, 'Missing required parameter %s' % k.args[0]
+
+ self.decls = []
+ self.impls = []
+ self.spec = get_by_path(dom, "spec")[0]
+ self.externals = gather_externals(self.spec)
+ self.custom_lists = gather_custom_lists(self.spec, self.namespace)
+ self.required_custom = []
+ self.required_arrays = []
+ self.to_declare = []
+ self.depinfos = {}
+ self.refs = RefRegistry(self.spec)
+
+ def __call__(self):
+ # Emit comment header
+
+ self.both('/* Generated from ')
+ self.both(get_descendant_text(get_by_path(self.spec, 'title')))
+ version = get_by_path(self.spec, "version")
+
+ if version:
+ self.both(', version ' + get_descendant_text(version))
+
+ self.both(' */\n')
+
+ # Gather info on available and required types
+
+ self.gather_required()
+
+ if self.must_define:
+ self.decl('\n')
+ self.decl('#ifndef %s\n' % self.must_define)
+ self.decl('#error %s\n' % self.must_define)
+ self.decl('#endif')
+
+ self.decl('\n')
+
+ if self.extraincludes:
+ for include in self.extraincludes.split(','):
+ self.decl('#include %s\n' % include)
+
+ self.decl("""
+#include <QtGlobal>
+
+#include <QByteArray>
+#include <QString>
+#include <QStringList>
+#include <QVariantList>
+#include <QVariantMap>
+
+#include <QDBusArgument>
+#include <QDBusMetaType>
+#include <QDBusObjectPath>
+#include <QDBusSignature>
+#include <QDBusVariant>
+
+#include <TelepathyQt4/Global>
+
+/**
+ * \\addtogroup typesconstants Types and constants
+ *
+ * Enumerated, flag, structure, list and mapping types and utility constants.
+ */
+
+/**
+ * \\defgroup struct Structure types
+ * \\ingroup typesconstants
+ *
+ * Structure types generated from the specification.
+ */
+
+/**
+ * \\defgroup list List types
+ * \\ingroup typesconstants
+ *
+ * List types generated from the specification.
+ */
+
+/**
+ * \\defgroup mapping Mapping types
+ * \\ingroup typesconstants
+ *
+ * Mapping types generated from the specification.
+ */
+
+""")
+
+ if self.must_define:
+ self.impl("""
+#define %s""" % self.must_define)
+
+ self.impl("""
+#include "%s"
+""" % self.realinclude)
+
+ self.both("""
+namespace %s
+{
+""" % self.namespace)
+
+ # Emit type definitions for types provided in the spec
+
+ self.provide_all()
+
+ # Emit type registration function
+
+ self.decl("""
+} // namespace %s
+
+""" % self.namespace)
+
+ self.impl("""\
+TELEPATHY_QT4_NO_EXPORT void _registerTypes()
+{
+ static bool registered = false;
+ if (registered)
+ return;
+ registered = true;
+
+""")
+
+ # Emit Qt4 metatype declarations
+
+ self.to_declare.sort()
+
+ for metatype in self.to_declare:
+ self.decl('Q_DECLARE_METATYPE(%s)\n' % metatype)
+ self.impl(' qDBusRegisterMetaType<%s>();\n' % ((metatype.endswith('>') and metatype + ' ') or metatype))
+
+ self.impl("""\
+}
+
+} // namespace %s
+""" % self.namespace)
+
+ # Write output to files
+
+ open(self.declfile, 'w').write(''.join(self.decls).encode("utf-8"))
+ open(self.implfile, 'w').write(''.join(self.impls).encode("utf-8"))
+
+ def decl(self, str):
+ self.decls.append(str)
+
+ def impl(self, str):
+ self.impls.append(str)
+
+ def both(self, str):
+ self.decl(str)
+ self.impl(str)
+
+ def gather_required(self):
+ members = self.spec.getElementsByTagNameNS(NS_TP, 'member')
+ args = self.spec.getElementsByTagName('arg')
+ props = self.spec.getElementsByTagName('property')
+ tp_props = self.spec.getElementsByTagNameNS(NS_TP, 'property')
+
+ for requirer in members + args + props + tp_props:
+ sig = requirer.getAttribute('type')
+ tptype = requirer.getAttributeNS(NS_TP, 'type')
+ external = (sig, tptype) in self.externals
+ binding = binding_from_usage(sig, tptype, self.custom_lists, external)
+
+ if binding.custom_type and binding.val not in self.required_custom:
+ self.required_custom.append(binding.val)
+
+ if not binding.custom_type and binding.array_of and (binding.val, binding.array_of) not in self.required_arrays:
+ self.required_arrays.append((binding.val, binding.array_of))
+
+ def provide_all(self):
+ self.required_arrays.sort()
+ for (val, array_of) in self.required_arrays:
+ real = 'QList<%s>' % array_of
+ self.decl("""\
+/**
+ * \\struct %s
+ * \\ingroup list
+%s\
+ *
+ * Generic list type with %s elements. Convertible with
+ * %s, but needed to have a discrete type in the Qt4 type system.
+ */
+""" % (val, get_headerfile_cmd(self.realinclude, self.prettyinclude), array_of, real))
+ self.decl(self.faketype(val, real))
+ self.to_declare.append(self.namespace + '::' + val)
+
+ structs = self.spec.getElementsByTagNameNS(NS_TP, 'struct')
+ mappings = self.spec.getElementsByTagNameNS(NS_TP, 'mapping')
+ exts = self.spec.getElementsByTagNameNS(NS_TP, 'external-type')
+
+ for deptype in structs + mappings:
+ info = DepInfo(deptype, self.externals, self.custom_lists)
+ self.depinfos[info.binding.val] = info
+
+ leaves = []
+ next_leaves = []
+
+ for val, depinfo in self.depinfos.iteritems():
+ leaf = True
+
+ for dep in depinfo.deps:
+ if not self.depinfos.has_key(dep):
+ raise UnresolvedDependency(val, dep)
+
+ leaf = False
+ self.depinfos[dep].revdeps.append(val)
+
+ if leaf:
+ next_leaves.append(val)
+
+ while leaves or next_leaves:
+ if not leaves:
+ leaves = next_leaves
+ leaves.sort()
+ next_leaves = []
+
+ val = leaves.pop(0)
+ depinfo = self.depinfos[val]
+ self.output_by_depinfo(depinfo)
+
+ for revdep in depinfo.revdeps:
+ revdepinfo = self.depinfos[revdep]
+ revdepinfo.deps.remove(val)
+
+ if not revdepinfo.deps:
+ next_leaves.append(revdep)
+
+ del self.depinfos[val]
+
+ for provider in structs + mappings + exts:
+ name = get_by_path(provider, '@name')
+ array_name = get_by_path(provider, '@array-name')
+ array_depth = get_by_path(provider, '@array-depth')
+ if array_depth:
+ array_depth = int(array_depth)
+ else:
+ array_depth = None
+ sig = provider.getAttribute('type')
+ tptype = provider.getAttribute('name')
+ external = (sig, tptype) in self.externals
+ binding = binding_from_decl(name, array_name, array_depth, external)
+ self.provide(binding.val)
+
+ if binding.array_val:
+ self.provide(binding.array_val)
+
+ d = binding.array_depth
+ while d > 1:
+ d -= 1
+ self.provide(binding.array_val + ('List' * d))
+
+ if self.required_custom:
+ raise MissingTypes(self.required_custom)
+
+ def provide(self, type):
+ if type in self.required_custom:
+ self.required_custom.remove(type)
+
+ def output_by_depinfo(self, depinfo):
+ names, docstrings, bindings = extract_arg_or_member_info(get_by_path(depinfo.el, 'member'), self.custom_lists, self.externals, None, self.refs, ' * ', (' /**', ' */'))
+ members = len(names)
+
+ if depinfo.el.localName == 'struct':
+ if members == 0:
+ raise EmptyStruct(depinfo.binding.val)
+
+ self.decl("""\
+/**
+ * \\struct %(name)s
+ * \\ingroup struct
+%(headercmd)s\
+ *
+ * Structure type generated from the specification.
+%(docstring)s\
+ */
+struct %(visibility)s %(name)s
+{
+""" % {
+ 'name' : depinfo.binding.val,
+ 'headercmd': get_headerfile_cmd(self.realinclude, self.prettyinclude),
+ 'docstring' : format_docstring(depinfo.el, self.refs),
+ 'visibility': self.visibility,
+ })
+
+ for i in xrange(members):
+ self.decl("""\
+%s\
+ %s %s;
+""" % (docstrings[i], bindings[i].val, names[i]))
+
+ self.decl("""\
+};
+
+""")
+
+ self.both('%s bool operator==(%s v1, %s v2)' %
+ (self.visibility,
+ depinfo.binding.inarg,
+ depinfo.binding.inarg))
+ self.decl(';\n')
+ self.impl("""
+{""")
+ if (bindings[0].val != 'QDBusVariant'):
+ self.impl("""
+ return ((v1.%s == v2.%s)""" % (names[0], names[0]))
+ else:
+ self.impl("""
+ return ((v1.%s.variant() == v2.%s.variant())""" % (names[0], names[0]))
+ for i in xrange(1, members):
+ if (bindings[i].val != 'QDBusVariant'):
+ self.impl("""
+ && (v1.%s == v2.%s)""" % (names[i], names[i]))
+ else:
+ self.impl("""
+ && (v1.%s.variant() == v2.%s.variant())""" % (names[i], names[i]))
+ self.impl("""
+ );
+}
+
+""")
+
+ self.decl('inline bool operator!=(%s v1, %s v2)' %
+ (depinfo.binding.inarg, depinfo.binding.inarg))
+ self.decl("""
+{
+ return !operator==(v1, v2);
+}
+""")
+
+ self.both('%s QDBusArgument& operator<<(QDBusArgument& arg, %s val)' %
+ (self.visibility, depinfo.binding.inarg))
+ self.decl(';\n')
+ self.impl("""
+{
+ arg.beginStructure();
+ arg << %s;
+ arg.endStructure();
+ return arg;
+}
+
+""" % ' << '.join(['val.' + name for name in names]))
+
+ self.both('%s const QDBusArgument& operator>>(const QDBusArgument& arg, %s val)' %
+ (self.visibility, depinfo.binding.outarg))
+ self.decl(';\n\n')
+ self.impl("""
+{
+ arg.beginStructure();
+ arg >> %s;
+ arg.endStructure();
+ return arg;
+}
+
+""" % ' >> '.join(['val.' + name for name in names]))
+ elif depinfo.el.localName == 'mapping':
+ if members != 2:
+ raise MalformedMapping(depinfo.binding.val, members)
+
+ realtype = 'QMap<%s, %s>' % (bindings[0].val, (bindings[1].val.endswith('>') and bindings[1].val + ' ') or bindings[1].val)
+ self.decl("""\
+/**
+ * \\struct %s
+ * \\ingroup mapping
+%s\
+ *
+ * Mapping type generated from the specification. Convertible with
+ * %s, but needed to have a discrete type in the Qt4 type system.
+%s\
+ */
+""" % (depinfo.binding.val, get_headerfile_cmd(self.realinclude, self.prettyinclude), realtype, format_docstring(depinfo.el, self.refs)))
+ self.decl(self.faketype(depinfo.binding.val, realtype))
+ else:
+ raise WTF(depinfo.el.localName)
+
+ self.to_declare.append(self.namespace + '::' + depinfo.binding.val)
+
+ if depinfo.binding.array_val:
+ self.to_declare.append('%s::%s' % (self.namespace, depinfo.binding.array_val))
+ self.decl("""\
+/**
+ * \\ingroup list
+%s\
+ *
+ * Array of %s values.
+ */
+typedef %s %s;
+
+""" % (get_headerfile_cmd(self.realinclude, self.prettyinclude), depinfo.binding.val, 'QList<%s>' % depinfo.binding.val, depinfo.binding.array_val))
+
+ i = depinfo.binding.array_depth
+ while i > 1:
+ i -= 1
+ self.to_declare.append('%s::%s%s' % (self.namespace, depinfo.binding.array_val, ('List' * i)))
+ list_of = depinfo.binding.array_val + ('List' * (i-1))
+ self.decl("""\
+/**
+ * \\ingroup list
+%s\
+ *
+ * Array of %s values.
+ */
+typedef QList<%s> %sList;
+
+""" % (get_headerfile_cmd(self.realinclude, self.prettyinclude), list_of, list_of, list_of))
+
+ def faketype(self, fake, real):
+ return """\
+struct %(visibility)s %(fake)s : public %(real)s
+{
+ inline %(fake)s() : %(real)s() {}
+ inline %(fake)s(const %(real)s& a) : %(real)s(a) {}
+
+ inline %(fake)s& operator=(const %(real)s& a)
+ {
+ *(static_cast<%(real)s*>(this)) = a;
+ return *this;
+ }
+};
+
+""" % {'fake' : fake, 'real' : real, 'visibility': self.visibility}
+
+if __name__ == '__main__':
+ options, argv = gnu_getopt(sys.argv[1:], '',
+ ['declfile=',
+ 'implfile=',
+ 'realinclude=',
+ 'prettyinclude=',
+ 'extraincludes=',
+ 'must-define=',
+ 'namespace=',
+ 'specxml=',
+ 'visibility=',
+ ])
+
+ try:
+ Generator(dict(options))()
+ except BrokenSpecException as e:
+ print >> sys.stderr, 'Your spec is broken, dear developer! %s' % e
+ sys.exit(42)
diff --git a/qt4/tools/repeat-tests.sh b/qt4/tools/repeat-tests.sh
new file mode 100755
index 000000000..8da2f9ea8
--- /dev/null
+++ b/qt4/tools/repeat-tests.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+if [ $# -ne 2 ]
+then
+ echo "usage: $0 <command> <number of repetitions>"
+ echo "example: $0 \"make check-valgrind\" 100"
+ echo " or: $0 \"make -j4 check\" 100"
+ exit 1
+fi
+
+for i in `seq 1 $2`
+do
+ echo -n "Running test iteration ${i}... "
+ log="test-round-${i}.log"
+ $1 > ${log} 2>&1
+ if grep -q "FAIL" $log
+ then
+ echo "FAILED (log in $log)"
+ else
+ echo "PASSED"
+ rm ${log}
+ fi
+done
diff --git a/qt4/tools/telepathy-glib.supp b/qt4/tools/telepathy-glib.supp
new file mode 100644
index 000000000..28bd5a06a
--- /dev/null
+++ b/qt4/tools/telepathy-glib.supp
@@ -0,0 +1,390 @@
+# Valgrind error suppression file
+
+# ============================= libc ==================================
+
+{
+ ld.so initialization + selinux
+ Memcheck:Leak
+ ...
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code
+ Memcheck:Leak
+ ...
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+# default.supp has these for 2.10, but they're too specific
+{
+ Debian libc6 (2.10.x, 2.11.x) stripped dynamic linker
+ Memcheck:Cond
+ fun:index
+ fun:expand_dynamic_string_token
+ fun:_dl_map_object
+ fun:map_doit
+ fun:_dl_catch_error
+ fun:do_preload
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-*.so
+}
+{
+ Debian libc6 (2.9.x - 2.11.x) stripped dynamic linker
+ Memcheck:Cond
+ fun:_dl_relocate_object
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-*.so
+}
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:strlen
+ fun:_dl_init_paths
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}
+
+# ======================= libselinux on Debian amd64 =====================
+
+{
+ I have no idea what SELinux is doing but it's not my problem
+ Memcheck:Cond
+ ...
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+}
+
+{
+ I have no idea what SELinux is doing but it's not my problem
+ Memcheck:Value8
+ ...
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+}
+
+{
+ I have no idea what SELinux is doing but it's not my problem
+ Memcheck:Leak
+ ...
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+ obj:/lib/libselinux.so.1
+}
+
+# ============================= GLib ==================================
+
+{
+ g_set_prgname copies its argument
+ Memcheck:Leak
+ ...
+ fun:g_set_prgname
+}
+
+{
+ one g_get_charset per child^Wprocess
+ Memcheck:Leak
+ ...
+ fun:g_get_charset
+}
+
+{
+ one g_get_home_dir per process
+ Memcheck:Leak
+ ...
+ fun:g_get_home_dir
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_static_string
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_string
+}
+
+{
+ interned strings can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_intern_string
+}
+
+{
+ interned strings can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_intern_static_string
+}
+
+{
+ shared global default g_main_context
+ Memcheck:Leak
+ ...
+ fun:g_main_context_new
+ fun:g_main_context_default
+}
+
+{
+ GTest initialization
+ Memcheck:Leak
+ ...
+ fun:g_test_init
+ fun:main
+}
+
+{
+ GTest admin
+ Memcheck:Leak
+ ...
+ fun:g_test_add_vtable
+}
+
+{
+ GTest pseudorandomness
+ Memcheck:Leak
+ ...
+ fun:g_rand_new_with_seed_array
+ fun:test_run_seed
+ ...
+ fun:g_test_run
+}
+
+{
+ GSLice initialization
+ Memcheck:Leak
+ ...
+ fun:g_malloc0
+ fun:g_slice_init_nomessage
+ fun:g_slice_alloc
+}
+
+# ============================= GObject ===============================
+
+{
+ g_type_init
+ Memcheck:Leak
+ ...
+ fun:g_type_init
+}
+
+{
+ g_type_init_with_debug_flags
+ Memcheck:Leak
+ ...
+ fun:g_type_init_with_debug_flags
+}
+
+{
+ g_type_register_static
+ Memcheck:Leak
+ ...
+ fun:g_type_register_static
+}
+
+{
+ g_type_add_interface_static
+ Memcheck:Leak
+ ...
+ fun:g_type_add_interface_static
+}
+
+{
+ initialization of interfaces
+ Memcheck:Leak
+ ...
+ fun:type_iface_vtable_base_init_Wm
+ fun:g_type_class_ref
+}
+
+# ============================= GIO ===================================
+
+{
+ GIO init
+ Memcheck:Leak
+ ...
+ fun:g_inet_address_class_intern_init
+}
+
+{
+ g_simple_async_result class
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:g_simple_async_result_new
+}
+
+# ============================= dbus-glib =============================
+
+{
+ registering marshallers is permanent
+ Memcheck:Leak
+ ...
+ fun:dbus_g_object_register_marshaller_array
+ fun:dbus_g_object_register_marshaller
+}
+
+{
+ dbus-glib specialized GTypes are permanent
+ Memcheck:Leak
+ ...
+ fun:dbus_g_type_specialized_init
+}
+
+{
+ libdbus shared connection
+ Memcheck:Leak
+ ...
+ fun:dbus_g_bus_get
+}
+
+{
+ dbus-gobject registrations aren't freed unless we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:g_slist_append
+ fun:dbus_g_connection_register_g_object
+}
+
+{
+ DBusGProxy slots aren't freed unless we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_allocate_data_slot
+ ...
+ fun:dbus_g_proxy_constructor
+}
+
+{
+ error registrations are for life, not just for Christmas
+ Memcheck:Leak
+ ...
+ fun:dbus_g_error_domain_register
+}
+
+{
+ DBusGProxy class init
+ Memcheck:Leak
+ ...
+ fun:dbus_g_proxy_class_init
+}
+
+# ============================= telepathy-glib ========================
+
+{
+ tp_dbus_daemon_constructor @daemons once per DBusConnection
+ Memcheck:Leak
+ ...
+ fun:g_slice_alloc
+ fun:tp_dbus_daemon_constructor
+}
+
+{
+ tp_proxy_subclass_add_error_mapping refs the enum
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ fun:tp_proxy_subclass_add_error_mapping
+}
+
+{
+ tp_proxy_or_subclass_hook_on_interface_add never frees its list
+ Memcheck:Leak
+ ...
+ fun:tp_proxy_or_subclass_hook_on_interface_add
+}
+
+{
+ tp_dbus_daemon_constructor filter not freed til we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_add_filter
+ fun:tp_dbus_daemon_constructor
+}
+
+{
+ tp_g_socket_address_from_variant reffing GNIO types
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:tp_g_socket_address_from_variant
+}
+
+{
+ creating classes for DBusGProxy
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:g_object_new
+ ...
+ fun:tp_proxy_borrow_interface_by_id
+}
+
+{
+ creating classes for tp_dbus_daemon_new
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:g_object_new
+ ...
+ fun:tp_dbus_daemon_new
+}
+
+{
+ creating classes for TpCHannel
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:g_object_new
+ ...
+ fun:tp_channel_new
+}
+
+{
+ creating a boxed type to use in TpCapabilities
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:g_param_spec_boxed
+ fun:tp_capabilities_class_intern_init
+}
+
+# ============================= questionable ==========================
+
+{
+ creating classes for instances (this is a pretty big hammer)
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:g_type_create_instance
+ ...
+ fun:g_param_spec_string
+}
diff --git a/qt4/tools/tp-qt4-tests.supp b/qt4/tools/tp-qt4-tests.supp
new file mode 100644
index 000000000..2c0f12011
--- /dev/null
+++ b/qt4/tools/tp-qt4-tests.supp
@@ -0,0 +1,53 @@
+# QDBus doesn't call dbus_shutdown, so some D-Bus internal data structures are leaked.
+# We never call any low-level dbus message creation functions ourselves - if there are leaks,
+# they're either caused by not calling dbus_shutdown, QDBus bugs or libdbus bugs - neither of which
+# are our problem.
+
+{
+ Initial session bus registration message
+ Memcheck:Leak
+ fun:malloc
+ fun:dbus_message_new_empty_header
+}
+
+# The conference test CM channel object leaks some crazy GValue boxed data which I don't have the
+# energy to investigate how to properly free now - it's not production code anyway.
+
+{
+ Conference test CM channel boxed GValue data
+ Memcheck:Leak
+ ...
+ fun:g_boxed_copy
+ ...
+ fun:_ZN18TestConferenceChan12initTestCaseEv
+}
+
+# Reported as https://bugs.freedesktop.org/show_bug.cgi?id=32116
+
+{
+ TpBaseConnectionManager legacy protocol objects
+ Memcheck:Leak
+ ...
+ fun:g_object_new
+ ...
+ fun:tp_base_connection_manager_register
+}
+
+# O(number of error domains) leak from dbus_g_method_return_error
+{
+ dbus_g_method_return_error error domain enum class
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ ...
+ fun:dbus_g_method_return_error
+}
+
+# O(1) leak from tp_base_connection_manager installing the param spec for the dbus-daemon param
+{
+ tp_base_connection_manager dbus-daemon param spec
+ Memcheck:Leak
+ ...
+ fun:g_param_spec_object
+ fun:tp_base_connection_manager_class_intern_init
+}
diff --git a/qt4/tools/with-session-bus.sh b/qt4/tools/with-session-bus.sh
new file mode 100644
index 000000000..063bd7e17
--- /dev/null
+++ b/qt4/tools/with-session-bus.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+# with-session-bus.sh - run a program with a temporary D-Bus session daemon
+#
+# The canonical location of this program is the telepathy-glib tools/
+# directory, please synchronize any changes with that copy.
+#
+# Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+set -e
+
+me=with-session-bus
+
+dbus_daemon_args="--print-address=5 --print-pid=6 --fork"
+sleep=0
+
+usage ()
+{
+ echo "usage: $me [options] -- program [program_options]" >&2
+ echo "Requires write access to the current directory." >&2
+ echo "" >&2
+ echo "If \$WITH_SESSION_BUS_FORK_DBUS_MONITOR is set, fork dbus-monitor" >&2
+ echo "with the arguments in \$WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT." >&2
+ echo "The output of dbus-monitor is saved in $me-<pid>.dbus-monitor-logs" >&2
+ exit 2
+}
+
+while test "z$1" != "z--"; do
+ case "$1" in
+ --sleep=*)
+ sleep="$1"
+ sleep="${sleep#--sleep=}"
+ shift
+ ;;
+ --session)
+ dbus_daemon_args="$dbus_daemon_args --session"
+ shift
+ ;;
+ --config-file=*)
+ # FIXME: assumes config file doesn't contain any special characters
+ dbus_daemon_args="$dbus_daemon_args $1"
+ shift
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+shift
+if test "z$1" = "z"; then usage; fi
+
+exec 5> $me-$$.address
+exec 6> $me-$$.pid
+
+cleanup ()
+{
+ pid=`head -n1 $me-$$.pid`
+ if test -n "$pid" ; then
+ echo "Killing temporary bus daemon: $pid" >&2
+ kill -INT "$pid"
+ fi
+ rm -f $me-$$.address
+ rm -f $me-$$.pid
+}
+
+trap cleanup INT HUP TERM
+dbus-daemon $dbus_daemon_args
+
+{ echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2
+{ echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2
+
+e=0
+DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`"
+export DBUS_SESSION_BUS_ADDRESS
+
+if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then
+ echo -n "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2
+ dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT \
+ > $me-$$.dbus-monitor-logs 2>&1 &
+fi
+
+"$@" || e=$?
+
+if test $sleep != 0; then
+ sleep $sleep
+fi
+
+trap - INT HUP TERM
+cleanup
+
+exit $e
diff --git a/qt4/tools/xincludator.py b/qt4/tools/xincludator.py
new file mode 100644
index 000000000..63e106ace
--- /dev/null
+++ b/qt4/tools/xincludator.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+
+from sys import argv, stdout, stderr
+import codecs, locale
+import os
+import xml.dom.minidom
+
+stdout = codecs.getwriter('utf-8')(stdout)
+
+NS_XI = 'http://www.w3.org/2001/XInclude'
+
+def xincludate(dom, base, dropns = []):
+ remove_attrs = []
+ for i in xrange(dom.documentElement.attributes.length):
+ attr = dom.documentElement.attributes.item(i)
+ if attr.prefix == 'xmlns':
+ if attr.localName in dropns:
+ remove_attrs.append(attr)
+ else:
+ dropns.append(attr.localName)
+ for attr in remove_attrs:
+ dom.documentElement.removeAttributeNode(attr)
+ for include in dom.getElementsByTagNameNS(NS_XI, 'include'):
+ href = include.getAttribute('href')
+ # FIXME: assumes Unixy paths
+ filename = os.path.join(os.path.dirname(base), href)
+ subdom = xml.dom.minidom.parse(filename)
+ xincludate(subdom, filename, dropns)
+ if './' in href:
+ subdom.documentElement.setAttribute('xml:base', href)
+ include.parentNode.replaceChild(subdom.documentElement, include)
+
+if __name__ == '__main__':
+ argv = argv[1:]
+ dom = xml.dom.minidom.parse(argv[0])
+ xincludate(dom, argv[0])
+ xml = dom.toxml()
+ stdout.write(xml)
+ stdout.write('\n')