Move ArtLoader from mpris_common.h to its own file, add libxrme to 3rdparty, add a working XMPP remote.

This commit is contained in:
David Sansome 2011-02-19 18:24:11 +00:00
parent 304ce97b16
commit 72096bf1c8
42 changed files with 2005 additions and 580 deletions

51
3rdparty/libxrme/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,51 @@
include_directories(${CMAKE_BINARY_DIR})
include_directories(${GLOOX_INCLUDE_DIRS})
link_directories(${GLOOX_LIBRARY_DIRS})
set(SOURCES
common.cpp
connection.cpp
handler.cpp
mediaplayerhandler.cpp
mediaplayerinterface.cpp
remotecontrolhandler.cpp
remotecontrolinterface.cpp
)
set(HEADERS
connection.h
)
SET(PUBLIC_HEADERS
connection.h
common.h
mediaplayerinterface.h
remotecontrolinterface.h
)
qt4_wrap_cpp(MOC ${HEADERS})
add_library(xrme ${SOURCES} ${MOC})
target_link_libraries(xrme
${QT_LIBRARIES}
${GLOOX_LIBRARIES}
)
# Install library
install(TARGETS xrme
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
# Install public headers
install(FILES ${PUBLIC_HEADERS}
DESTINATION include/xrme/
)
# Also install public headers into the cmake binary dir so packages can use
# libxrme in their source tree by doing include_directories(${CMAKE_BINARY_DIR})
foreach(header ${PUBLIC_HEADERS})
configure_file(${header} ${CMAKE_BINARY_DIR}/xrme/${header} COPYONLY)
endforeach(header)

43
3rdparty/libxrme/common.cpp vendored Normal file
View File

@ -0,0 +1,43 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common.h"
namespace xrme {
const char* kXmlnsXrme = "http://purplehatstands.com/xmlns/xrme";
const char* kXmlnsXrmeMediaPlayer = "http://purplehatstands.com/xmlns/xrme/mediaplayer";
const char* kXmlnsXrmeRemoteControl = "http://purplehatstands.com/xmlns/xrme/remotecontrol";
Metadata::Metadata()
: track(0),
disc(0),
year(0),
length_millisec(0),
rating(0.0) {
}
State::State()
: playback_state(PlaybackState_Stopped),
position_millisec(0),
volume(0.0),
can_go_next(false),
can_go_previous(false),
can_seek(false) {
}
} // namespace xrme

65
3rdparty/libxrme/common.h vendored Normal file
View File

@ -0,0 +1,65 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef XRME_COMMON_H
#define XRME_COMMON_H
#include <QString>
namespace xrme {
extern const char* kXmlnsXrme;
extern const char* kXmlnsXrmeMediaPlayer;
extern const char* kXmlnsXrmeRemoteControl;
struct Metadata {
Metadata();
QString title;
QString artist;
QString album;
QString albumartist;
QString composer;
QString genre;
int track;
int disc;
int year;
int length_millisec;
double rating; // range 0.0 - 1.0
};
struct State {
State();
enum PlaybackState {
PlaybackState_Stopped = 0,
PlaybackState_Playing = 1,
PlaybackState_Paused = 2,
};
PlaybackState playback_state;
int position_millisec;
double volume; // range 0.0 - 1.0
bool can_go_next;
bool can_go_previous;
bool can_seek;
Metadata metadata;
};
} // namespace xrme
#endif // XRME_COMMON_H

498
3rdparty/libxrme/connection.cpp vendored Normal file
View File

@ -0,0 +1,498 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#include "connection.h"
#include "mediaplayerhandler.h"
#include "remotecontrolhandler.h"
#include <QSocketNotifier>
#include <QTimer>
#include <QtDebug>
#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/connectiontcpclient.h>
#include <gloox/disco.h>
#include <gloox/discohandler.h>
#include <gloox/loghandler.h>
#include <gloox/rosterlistener.h>
#include <gloox/rostermanager.h>
namespace xrme {
struct Connection::Private : public gloox::ConnectionListener,
public gloox::LogHandler,
public gloox::RosterListener,
public gloox::DiscoHandler {
Private(Connection* parent)
: parent_(parent),
server_(kDefaultServer),
jid_resource_(kDefaultJIDResource),
jid_host_(kDefaultJIDHost),
verbose_(false),
media_player_(NULL),
remote_control_(NULL),
spontaneous_disconnect_(true) {}
static const char* kDefaultServer;
static const char* kDefaultJIDResource;
static const char* kDefaultJIDHost;
Connection* parent_;
// Stuff the user sets before Connect()
QString username_;
QString password_;
QString agent_name_;
QString server_;
QString jid_resource_;
QString jid_host_;
bool verbose_;
// Interfaces
MediaPlayerInterface* media_player_;
RemoteControlInterface* remote_control_;
QList<Handler*> handlers_;
// Stuff that is valid when we're connected.
QScopedPointer<gloox::Client> client_;
QScopedPointer<QSocketNotifier> socket_notifier_;
// After discovering a peer we query it to find its capabilities. Only after
// it replies to the query do we put it in peers_ and emit PeerFound().
QList<Peer> peers_;
QList<Peer> querying_peers_;
bool has_peer(const QString& jid_resource) const;
// We can't destroy the client_ in the onDisconnect() handler, so we have to
// do it with a QTimer if we get a spontaneous disconnect.
bool spontaneous_disconnect_;
// gloox::MessageHandler
void handleMessage(gloox::Stanza* stanza, gloox::MessageSession* session = 0);
// gloox::ConnectionListener
void onConnect();
void onDisconnect(gloox::ConnectionError e);
bool onTLSConnect(const gloox::CertInfo& info);
// gloox::LogHandler
void handleLog(gloox::LogLevel level, gloox::LogArea area,
const std::string& message);
// gloox::IqHandler
bool handleIq(gloox::Stanza* stanza);
bool handleIqID(gloox::Stanza* stanza, int context);
// gloox::RosterListener
void handleItemAdded(const gloox::JID&) {}
void handleItemSubscribed(const gloox::JID&) {}
void handleItemRemoved(const gloox::JID&) {}
void handleItemUpdated(const gloox::JID&) {}
void handleItemUnsubscribed(const gloox::JID&) {}
void handleRoster(const gloox::Roster&) {}
void handleRosterPresence(const gloox::RosterItem& item, const std::string& resource, gloox::Presence presence, const std::string& msg);
void handleSelfPresence(const gloox::RosterItem&, const std::string&, gloox::Presence, const std::string&) {}
bool handleSubscriptionRequest(const gloox::JID&, const std::string&) { return false; }
bool handleUnsubscriptionRequest(const gloox::JID&, const std::string&) { return false; }
void handleNonrosterPresence(gloox::Stanza*) {}
void handleRosterError(gloox::Stanza*) {}
// gloox::DiscoHandler
void handleDiscoInfoResult(gloox::Stanza* stanza, int context);
void handleDiscoItemsResult(gloox::Stanza*, int) {}
void handleDiscoError(gloox::Stanza* stanza, int context);
};
const char* Connection::Private::kDefaultServer = "talk.google.com";
const char* Connection::Private::kDefaultJIDResource = "xrmeagent";
const char* Connection::Private::kDefaultJIDHost = "gmail.com";
Connection::Connection(QObject* parent)
: QObject(parent),
d(new Private(this)) {
}
Connection::~Connection() {
qDeleteAll(d->handlers_);
}
void Connection::set_username(const QString& username) { d->username_ = username; }
void Connection::set_password(const QString& password) { d->password_ = password; }
void Connection::set_agent_name(const QString& agent_name) { d->agent_name_ = agent_name; }
void Connection::set_server(const QString& server) { d->server_ = server; }
void Connection::set_jid_resource(const QString& resource) { d->jid_resource_ = resource; }
void Connection::set_jid_host(const QString& host) { d->jid_host_ = host; }
void Connection::set_verbose(bool verbose) { d->verbose_ = verbose; }
QString Connection::username() const { return d->username_; }
QString Connection::password() const { return d->password_; }
QString Connection::agent_name() const { return d->agent_name_; }
QString Connection::server() const { return d->server_; }
QString Connection::jid_resource() const { return d->jid_resource_; }
QString Connection::jid_host() const { return d->jid_host_; }
bool Connection::is_verbose() const { return d->verbose_; }
void Connection::SetMediaPlayer(MediaPlayerInterface* interface) {
if (d->media_player_) {
qWarning() << "Connection::SetMediaPlayer: this connection already has a"
" MediaPlayerInterface set";
return;
}
if (!interface) {
qWarning() << "Connection::SetMediaPlayer called with NULL interface";
return;
}
d->media_player_ = interface;
d->handlers_ << new MediaPlayerHandler(interface);
}
void Connection::SetRemoteControl(RemoteControlInterface* interface) {
if (d->media_player_) {
qWarning() << "Connection::RemoteControlInterface: this connection already"
" has a RemoteControlInterface set";
return;
}
if (!interface) {
qWarning() << "Connection::SetRemoteControl called with NULL interface";
return;
}
d->remote_control_ = interface;
d->handlers_ << new RemoteControlHandler(interface);
}
bool Connection::is_connected() const {
return (d->client_ && d->client_->state() == gloox::StateConnected);
}
QString Connection::jid() const {
if (is_connected()) {
return QString::fromUtf8(d->client_->jid().full().c_str());
}
return QString();
}
Connection::PeerList Connection::peers() const {
if (is_connected()) {
return d->peers_;
}
return PeerList();
}
Connection::PeerList Connection::peers(Peer::Capability cap) const {
PeerList ret;
foreach (const Peer& peer, peers()) {
if (peer.caps_ & cap) {
ret << peer;
}
}
return ret;
}
bool Connection::Connect() {
if (d->username_.isEmpty() || d->password_.isEmpty() || d->agent_name_.isEmpty()) {
qWarning() << __PRETTY_FUNCTION__
<< ": A required field (username/password/agent_name) was empty";
return false;
}
// Construct the JID - append the default host if the user didn't provide one
QString jid = d->username_;
if (!jid.contains('@')) {
jid.append("@" + d->jid_host_);
}
jid.append("/" + d->jid_resource_);
// Create a new connection
d->client_.reset(new gloox::Client(gloox::JID(jid.toUtf8().constData()),
d->password_.toUtf8().constData()));
gloox::ConnectionTCPClient* connection = new gloox::ConnectionTCPClient(
d->client_.data(), d->client_->logInstance(),
d->server_.toUtf8().constData());
d->client_->setConnectionImpl(connection);
// Add listeners
d->client_->registerConnectionListener(d.data());
d->client_->rosterManager()->registerRosterListener(d.data());
d->client_->logInstance().registerLogHandler(
gloox::LogLevelDebug, gloox::LogAreaAll, d.data());
// Setup disco
d->client_->disco()->setIdentity("client", "bot");
d->client_->disco()->setVersion(d->agent_name_.toUtf8().constData(), std::string());
d->client_->disco()->addFeature(kXmlnsXrme);
// Initialise the handlers
foreach (Handler* handler, d->handlers_) {
handler->Init(this, d->client_.data());
}
// Set presence
d->client_->setPresence(gloox::PresenceAvailable, -128);
// Connect
if (!d->client_->connect(false)) {
d->client_.reset();
foreach (Handler* handler, d->handlers_) {
handler->Reset();
}
return false;
}
// Listen on the connection's socket
d->socket_notifier_.reset(new QSocketNotifier(
connection->socket(), QSocketNotifier::Read));
connect(d->socket_notifier_.data(), SIGNAL(activated(int)),
SLOT(SocketReadyReceive()));
return true;
}
void Connection::Disconnect() {
if (is_connected()) {
d->spontaneous_disconnect_ = false;
d->client_->disconnect();
d->client_.reset();
d->spontaneous_disconnect_ = true;
}
}
void Connection::SocketReadyReceive() {
d->client_->recv();
}
void Connection::Private::onConnect() {
parent_->RefreshPeers();
emit parent_->Connected();
}
void Connection::Private::onDisconnect(gloox::ConnectionError e) {
QString error_text;
switch (e) {
case gloox::ConnNoError:
case gloox::ConnUserDisconnected:
break;
case gloox::ConnStreamError:
error_text = QString::fromUtf8(client_->streamErrorText().c_str());
break;
case gloox::ConnAuthenticationFailed:
error_text = QString("Authentication error (%1)").arg(client_->authError());
break;
default:
error_text = QString("Unknown error (%1)").arg(e);
break;
}
foreach (Handler* handler, handlers_) {
handler->Reset();
}
socket_notifier_->setEnabled(false);
socket_notifier_.reset();
peers_.clear();
querying_peers_.clear();
emit parent_->Disconnected(error_text);
if (spontaneous_disconnect_) {
QTimer::singleShot(0, parent_, SLOT(CleanupClient()));
}
}
void Connection::CleanupClient() {
d->client_.reset();
}
bool Connection::Private::onTLSConnect(const gloox::CertInfo& info) {
return true;
}
void Connection::Private::handleLog(gloox::LogLevel level, gloox::LogArea area,
const std::string& message) {
if (!verbose_) {
return;
}
QString prefix = "---";
if (area == gloox::LogAreaXmlIncoming) {
prefix = "<<<";
} else if (area == gloox::LogAreaXmlOutgoing) {
prefix = ">>>";
}
qDebug() << "XMPP" << prefix.toAscii().constData() << message.c_str();
}
void Connection::RefreshPeers() {
// Clear the lists
d->peers_.clear();
d->querying_peers_.clear();
// Query presence
qDebug() << "Sending presence query";
d->client_->send(gloox::Stanza::createPresenceStanza(d->client_->jid().bareJID()));
}
Connection::Peer::Peer()
: caps_(0) {
}
void Connection::Private::handleRosterPresence(
const gloox::RosterItem& item, const std::string& res,
gloox::Presence presence, const std::string&) {
// Ignore presence from anyone else
if (item.jid() != client_->jid().bare()) {
return;
}
QString resource = QString::fromUtf8(res.c_str());
switch (presence) {
case gloox::PresenceUnknown:
case gloox::PresenceUnavailable:
// The peer went offline - did we know about him?
qDebug() << "Peer unavailable" << resource;
for (int i=0 ; i<querying_peers_.count() ; ++i) {
if (querying_peers_[i].jid_resource_ == resource) {
querying_peers_.takeAt(i);
break;
}
}
for (int i=0 ; i<peers_.count() ; ++i) {
if (peers_[i].jid_resource_ == resource) {
emit parent_->PeerRemoved(peers_.takeAt(i));
break;
}
}
break;
default:
// The peer came online
if (!has_peer(resource)) {
qDebug() << "Got presence from" << resource;
// This is a peer on our own bare JID, and we haven't seen it before
gloox::JID full_jid(item.jid());
full_jid.setResource(res);
Peer peer;
peer.jid_resource_ = resource;
querying_peers_ << peer;
client_->disco()->getDiscoInfo(full_jid, std::string(), this, 0);
}
}
}
bool Connection::Private::has_peer(const QString& jid_resource) const {
foreach (const Peer& peer, peers_) {
if (peer.jid_resource_ == jid_resource) {
return true;
}
}
foreach (const Peer& peer, querying_peers_) {
if (peer.jid_resource_ == jid_resource) {
return true;
}
}
}
void Connection::Private::handleDiscoInfoResult(gloox::Stanza* stanza, int context) {
// Is this from our own bare JID?
if (stanza->from().bareJID() != client_->jid().bareJID()) {
return;
}
QString resource = QString::fromUtf8(stanza->from().resource().c_str());
qDebug() << "Got disco info from" << resource;
// Are we currently querying this peer?
int querying_peer_index = -1;
for (int i=0 ; i<querying_peers_.count() ; ++i) {
if (querying_peers_[i].jid_resource_ == resource) {
querying_peer_index = i;
break;
}
}
if (querying_peer_index == -1) {
return;
}
// Remove this peer from the querying list and try to fill in his info.
Peer peer = querying_peers_.takeAt(querying_peer_index);
// Check for requried tags in the stanza.
gloox::Tag* query = stanza->findChild("query");
if (!query) {
return;
}
gloox::Tag* identity = query->findChild("identity");
gloox::Tag::TagList features = query->findChildren("feature");
if (identity == NULL || features.size() == 0) {
return;
}
// Fill in the name.
peer.agent_name_ = QString::fromUtf8(identity->findAttribute("name").c_str());
// Fill in the list of capabilities.
foreach (gloox::Tag* feature, features) {
const std::string feature_name = feature->findAttribute("var");
if (feature_name == kXmlnsXrmeMediaPlayer) {
peer.caps_ |= Peer::MediaPlayer;
}
if (feature_name == kXmlnsXrmeRemoteControl) {
peer.caps_ |= Peer::RemoteControl;
}
}
// No recognised capabilities? Discard the peer.
if (peer.caps_ == Peer::None) {
return;
}
peers_ << peer;
emit parent_->PeerFound(peer);
qDebug() << "Peer found:" << peer.agent_name_ << peer.jid_resource_ << peer.caps_;
}
void Connection::Private::handleDiscoError(gloox::Stanza* stanza, int context) {
QString resource = QString::fromUtf8(stanza->from().resource().c_str());
// Remove this peer if we're currently querying it
for (int i=0 ; i<querying_peers_.count() ; ++i) {
if (querying_peers_[i].jid_resource_ == resource) {
querying_peers_.removeAt(i);
return;
}
}
}
} // namespace xrme

152
3rdparty/libxrme/connection.h vendored Normal file
View File

@ -0,0 +1,152 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBXRME_CONNECTION_H
#define LIBXRME_CONNECTION_H
#include <QObject>
#include <QPair>
#include <QScopedPointer>
#include <QString>
namespace xrme {
class MediaPlayerInterface;
class RemoteControlInterface;
class Connection : public QObject {
Q_OBJECT
public:
Connection(QObject* parent = NULL);
~Connection();
struct Peer {
Peer();
enum Capability {
None = 0x00,
MediaPlayer = 0x01,
RemoteControl = 0x02,
MediaStorage = 0x04,
};
Q_DECLARE_FLAGS(Capabilities, Capability);
QString agent_name_;
QString jid_resource_;
Capabilities caps_;
};
typedef QList<Peer> PeerList;
// The username and password MUST be set before calling Connect().
void set_username(const QString& username);
void set_password(const QString& password);
// The agent name MUST be set before calling Connect(). The agent name is a
// friendly user-visible name assigned to this agent to help the user pick it
// out amongst other media players or remotes. It might contain the name of
// the application or the computer's hostname.
void set_agent_name(const QString& agent_name);
// Sets the hostname of the XMPP server to connect to. Defaults to
// "talk.google.com".
void set_server(const QString& server);
// Sets the resource string to use in the JID. Defaults to "xrmeagent".
void set_jid_resource(const QString& resource);
// Sets the host part to append to the JID if the user didn't specify one in
// the username. The host part is the part of the JID after the @ and before
// the /, eg: "username@host/resource". Defaults to "gmail.com".
void set_jid_host(const QString& host);
// If this is set then detailed XMPP output will be printed. Defaults to off.
void set_verbose(bool verbose);
// The following getters just return the data set above.
QString username() const;
QString password() const;
QString agent_name() const;
QString server() const;
QString jid_resource() const;
QString jid_host() const;
bool is_verbose() const;
// Sets a media player on the connection. Calling this means the XRME agent
// will advertise itself as a media player, and will be able to controlled by
// remote control agents. Should be called before calling Connect(). The
// Connection will NOT take ownership of the MediaPlayerInterface, and the
// MediaPlayerInterface MUST stay alive for as long as the connection.
void SetMediaPlayer(MediaPlayerInterface* interface);
// Sets a remote control on the connection. Calling this means the XRME agent
// will advertise itself as a remote control, and will receive state changes
// from media player agents. Should be called before calling Connect(). The
// Connection will NOT take ownership of the RemoteControlInterface, and the
// RemoteControlInterface MUST stay alive for as long as the connection.
void SetRemoteControl(RemoteControlInterface* interface);
// Returns true after Connected() is emitted.
bool is_connected() const;
// Returns the user's actual JID. This is only valid if is_connected() is
// true. Before the connection is complete it will return a null QString.
QString jid() const;
// Returns the list of all known peers. You can refresh this list by calling
// RefreshPeers(). PeerFound() is emitted any time a peer is added to this
// list.
PeerList peers() const;
// Returns the list of known peers that support a certain capability.
PeerList peers(Peer::Capability cap) const;
public slots:
// Starts connecting and returns immediately. Will emit Connected() or
// Disconnected() later. The username and password must already be set.
// Returns true on success, false if there was some problem starting the
// connection (eg. invalid/unspecified username/password/agent_name).
bool Connect();
// Disconnects immediately and emits Disconnected().
void Disconnect();
// Clears the internal list of peers and sends XMPP queries to discover the
// current list. Emits PeerFound().
void RefreshPeers();
signals:
void Connected();
void Disconnected(const QString& error);
void PeerFound(const xrme::Connection::Peer& peer);
void PeerRemoved(const xrme::Connection::Peer& peer);
private slots:
void SocketReadyReceive();
void CleanupClient();
private:
struct Private;
friend struct Private;
QScopedPointer<Private> d;
};
} // namespace xrme
#endif

39
3rdparty/libxrme/handler.cpp vendored Normal file
View File

@ -0,0 +1,39 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "handler.h"
#include <QtGlobal>
namespace xrme {
Handler::Handler()
: client_(NULL),
connection_(NULL) {
}
void Handler::Init(Connection* connection, gloox::Client* client) {
connection_ = connection;
client_ = client;
}
void Handler::Reset() {
connection_ = NULL;
client_ = NULL;
}
} // namespace xrme

43
3rdparty/libxrme/handler.h vendored Normal file
View File

@ -0,0 +1,43 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBXRME_HANDLER_H
#define LIBXRME_HANDLER_H
namespace gloox {
class Client;
}
namespace xrme {
class Connection;
class Handler {
public:
Handler();
virtual ~Handler() {}
virtual void Init(Connection* connection, gloox::Client* client);
virtual void Reset();
protected:
Connection* connection_;
gloox::Client* client_;
};
} // namespace xrme
#endif // LIBXRME_HANDLER_H

149
3rdparty/libxrme/mediaplayerhandler.cpp vendored Normal file
View File

@ -0,0 +1,149 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "connection.h"
#include "mediaplayerhandler.h"
#include "mediaplayerinterface.h"
#include "remotecontrolhandler.h"
#include <QBuffer>
#include <QtDebug>
#include <gloox/client.h>
#include <gloox/disco.h>
namespace xrme {
MediaPlayerHandler::MediaPlayerHandler(MediaPlayerInterface* interface)
: interface_(interface) {
interface_->Attach(this);
}
void MediaPlayerHandler::StateChanged() {
if (!connection_) {
return;
}
State s = interface_->state();
foreach (const Connection::Peer& peer, connection_->peers(Connection::Peer::RemoteControl)) {
gloox::JID to(client_->jid().bareJID());
to.setResource(peer.jid_resource_.toUtf8().constData());
gloox::Tag* stanza = gloox::Stanza::createIqStanza(
to, std::string(), gloox::StanzaIqSet, kXmlnsXrmeRemoteControl);
gloox::Tag* state = new gloox::Tag(stanza, "state");
state->addAttribute("xmlns", kXmlnsXrmeRemoteControl);
state->addAttribute("playback_state", s.playback_state);
state->addAttribute("position_millisec", s.position_millisec);
state->addAttribute("volume", QString::number(s.volume, 'f').toUtf8().constData());
state->addAttribute("can_go_next", s.can_go_next ? 1 : 0);
state->addAttribute("can_go_previous", s.can_go_previous ? 1 : 0);
state->addAttribute("can_seek", s.can_seek ? 1 : 0);
gloox::Tag* metadata = new gloox::Tag(state, "metadata");
metadata->addAttribute("title", s.metadata.title.toUtf8().constData());
metadata->addAttribute("artist", s.metadata.artist.toUtf8().constData());
metadata->addAttribute("album", s.metadata.album.toUtf8().constData());
metadata->addAttribute("albumartist", s.metadata.albumartist.toUtf8().constData());
metadata->addAttribute("composer", s.metadata.composer.toUtf8().constData());
metadata->addAttribute("genre", s.metadata.genre.toUtf8().constData());
metadata->addAttribute("track", s.metadata.track);
metadata->addAttribute("disc", s.metadata.disc);
metadata->addAttribute("year", s.metadata.year);
metadata->addAttribute("length_millisec", s.metadata.length_millisec);
metadata->addAttribute("rating", QString::number(s.metadata.rating, 'f').toUtf8().constData());
client_->send(stanza);
}
}
void MediaPlayerHandler::AlbumArtChanged() {
if (!connection_) {
return;
}
QImage image = interface_->album_art();
// Scale the image down if it's too big
if (!image.isNull() && (image.width() > kMaxAlbumArtSize ||
image.height() > kMaxAlbumArtSize)) {
image = image.scaled(kMaxAlbumArtSize,kMaxAlbumArtSize,
Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
// Write the image data
QByteArray image_data;
if (!image.isNull()) {
QBuffer buffer(&image_data);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPEG");
}
// Convert to base64
QByteArray image_data_base64 = image_data.toBase64();
// Send the IQs
foreach (const Connection::Peer& peer, connection_->peers(Connection::Peer::RemoteControl)) {
gloox::JID to(client_->jid().bareJID());
to.setResource(peer.jid_resource_.toUtf8().constData());
gloox::Tag* stanza = gloox::Stanza::createIqStanza(
to, std::string(), gloox::StanzaIqSet, kXmlnsXrmeRemoteControl);
gloox::Tag* album_art = new gloox::Tag(stanza, "album_art");
album_art->addAttribute("xmlns", kXmlnsXrmeRemoteControl);
album_art->setCData(image_data_base64.constData());
client_->send(stanza);
}
}
void MediaPlayerHandler::Init(Connection* connection, gloox::Client* client) {
Handler::Init(connection, client);
client->registerIqHandler(this, kXmlnsXrmeMediaPlayer);
client->disco()->addFeature(kXmlnsXrmeMediaPlayer);
}
bool MediaPlayerHandler::handleIq(gloox::Stanza* stanza) {
// Ignore stanzas from anyone else
if (stanza->from().bareJID() != client_->jid().bareJID()) {
return false;
}
if (stanza->hasChild("playpause")) {
interface_->PlayPause();
} else if (stanza->hasChild("stop")) {
interface_->Stop();
} else if (stanza->hasChild("previous")) {
interface_->Previous();
} else if (stanza->hasChild("next")) {
interface_->Next();
} else if (stanza->hasChild("querystate")) {
StateChanged();
AlbumArtChanged();
} else {
qWarning() << "Unknown command received from"
<< stanza->from().resource().c_str()
<< stanza->xml().c_str();
return false;
}
return true;
}
} // namespace xrme

52
3rdparty/libxrme/mediaplayerhandler.h vendored Normal file
View File

@ -0,0 +1,52 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBXRME_MEDIAPLAYERHANDLER_H
#define LIBXRME_MEDIAPLAYERHANDLER_H
#include "handler.h"
#include <gloox/iqhandler.h>
namespace xrme {
class MediaPlayerInterface;
class MediaPlayerHandler : public Handler,
public gloox::IqHandler {
public:
MediaPlayerHandler(MediaPlayerInterface* interface);
static const int kMaxAlbumArtSize = 300;
void StateChanged();
void AlbumArtChanged();
// Handler
void Init(Connection* connection, gloox::Client* client);
// gloox::IqHandler
bool handleIq(gloox::Stanza* stanza);
bool handleIqID(gloox::Stanza*, int) {}
private:
MediaPlayerInterface* interface_;
};
} // namespace xrme
#endif // LIBXRME_MEDIAPLAYERHANDLER_H

View File

@ -0,0 +1,60 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mediaplayerhandler.h"
#include "mediaplayerinterface.h"
#include <QtDebug>
namespace xrme {
struct MediaPlayerInterface::Private {
Private()
: handler_(NULL) {}
MediaPlayerHandler* handler_;
};
MediaPlayerInterface::MediaPlayerInterface()
: d(new Private) {
}
MediaPlayerInterface::~MediaPlayerInterface() {
}
void MediaPlayerInterface::StateChanged() {
if (!d->handler_) {
return;
}
d->handler_->StateChanged();
}
void MediaPlayerInterface::AlbumArtChanged() {
if (!d->handler_) {
return;
}
d->handler_->AlbumArtChanged();
}
void MediaPlayerInterface::Attach(MediaPlayerHandler* handler) {
d->handler_ = handler;
}
} // namespace xrme

67
3rdparty/libxrme/mediaplayerinterface.h vendored Normal file
View File

@ -0,0 +1,67 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBXRME_MEDIAPLAYERINTERFACE_H
#define LIBXRME_MEDIAPLAYERINTERFACE_H
#include <xrme/common.h>
#include <QImage>
#include <QScopedPointer>
namespace xrme {
class MediaPlayerHandler;
class MediaPlayerInterface {
public:
MediaPlayerInterface();
virtual ~MediaPlayerInterface();
// Control playback
virtual void PlayPause() = 0;
virtual void Stop() = 0;
virtual void Next() = 0;
virtual void Previous() = 0;
// Query the current state of the player. StateChanged() should be called
// when any part of the state changes.
virtual State state() const = 0;
// Query the album art of the currently playing song. This is separate from
// state() because it is bigger and needs to be sent less often.
// AlbumArtChanged() should be called when this changes. Return a null
// QImage() if there is no song playing or the song has no album art.
virtual QImage album_art() const = 0;
// Call when the values returned from the above getters have changed.
virtual void StateChanged();
virtual void AlbumArtChanged();
private:
Q_DISABLE_COPY(MediaPlayerInterface);
friend class MediaPlayerHandler;
void Attach(MediaPlayerHandler* handler);
struct Private;
QScopedPointer<Private> d;
};
} // namespace xrme
#endif // LIBXRME_MEDIAPLAYERINTERFACE_H

View File

@ -0,0 +1,145 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mediaplayerhandler.h"
#include "remotecontrolinterface.h"
#include "remotecontrolhandler.h"
#include <QImage>
#include <QtDebug>
#include <gloox/client.h>
#include <gloox/disco.h>
namespace xrme {
RemoteControlHandler::RemoteControlHandler(RemoteControlInterface* interface)
: interface_(interface) {
interface_->Attach(this);
}
void RemoteControlHandler::Init(Connection* connection, gloox::Client* client) {
Handler::Init(connection, client);
client->registerIqHandler(this, kXmlnsXrmeRemoteControl);
client->disco()->addFeature(kXmlnsXrmeRemoteControl);
}
void RemoteControlHandler::SendIQ(const QString& jid_resource,
gloox::StanzaSubType type,
const QString& command) {
if (!client_) {
return;
}
gloox::JID to(client_->jid());
to.setResource(jid_resource.toUtf8().constData());
gloox::Tag* stanza = gloox::Stanza::createIqStanza(
to, client_->getID(), type, kXmlnsXrmeMediaPlayer);
gloox::Tag* c = new gloox::Tag(stanza, command.toUtf8().constData());
c->addAttribute("xmlns", kXmlnsXrmeMediaPlayer);
client_->send(stanza);
}
void RemoteControlHandler::PlayPause(const QString& jid_resource) {
SendIQ(jid_resource, gloox::StanzaIqSet, "playpause");
}
void RemoteControlHandler::Stop(const QString& jid_resource) {
SendIQ(jid_resource, gloox::StanzaIqSet, "stop");
}
void RemoteControlHandler::Next(const QString& jid_resource) {
SendIQ(jid_resource, gloox::StanzaIqSet, "next");
}
void RemoteControlHandler::Previous(const QString& jid_resource) {
SendIQ(jid_resource, gloox::StanzaIqSet, "previous");
}
void RemoteControlHandler::QueryState(const QString& jid_resource) {
SendIQ(jid_resource, gloox::StanzaIqGet, "querystate");
}
int RemoteControlHandler::ParseInt(gloox::Tag* tag, const char* attribute_name) {
return ParseString(tag, attribute_name).toInt();
}
double RemoteControlHandler::ParseDouble(gloox::Tag* tag, const char* attribute_name) {
return ParseString(tag, attribute_name).toFloat();
}
QString RemoteControlHandler::ParseString(gloox::Tag* tag, const char* attribute_name) {
return QString::fromUtf8(tag->findAttribute(attribute_name).c_str());
}
bool RemoteControlHandler::handleIq(gloox::Stanza* stanza) {
// Ignore stanzas from anyone else
if (stanza->from().bareJID() != client_->jid().bareJID()) {
return false;
}
QString resource = QString::fromUtf8(stanza->from().resource().c_str());
qDebug() << resource << stanza->xml().c_str();
gloox::Tag* state = stanza->findChild("state");
if (state) {
gloox::Tag* metadata = state->findChild("metadata");
if (metadata) {
State s;
s.playback_state = State::PlaybackState(ParseInt(state, "playback_state"));
s.position_millisec = ParseInt(state, "position_millisec");
s.volume = ParseDouble(state, "volume");
s.can_go_next = ParseInt(state, "can_go_next");
s.can_go_previous = ParseInt(state, "can_go_previous");
s.can_seek = ParseInt(state, "can_seek");
s.metadata.title = ParseString(metadata, "title");
s.metadata.artist = ParseString(metadata, "artist");
s.metadata.album = ParseString(metadata, "album");
s.metadata.albumartist = ParseString(metadata, "albumartist");
s.metadata.composer = ParseString(metadata, "composer");
s.metadata.genre = ParseString(metadata, "genre");
s.metadata.track = ParseInt(metadata, "track");
s.metadata.disc = ParseInt(metadata, "disc");
s.metadata.year = ParseInt(metadata, "year");
s.metadata.length_millisec = ParseInt(metadata, "length_millisec");
s.metadata.rating = ParseDouble(metadata, "rating");
interface_->StateChanged(resource, s);
}
}
gloox::Tag* album_art = stanza->findChild("album_art");
if (album_art) {
QByteArray data(album_art->cdata().c_str(), album_art->cdata().size());
QImage image;
if (!data.isEmpty()) {
image.loadFromData(QByteArray::fromBase64(data), "JPEG");
}
interface_->AlbumArtChanged(resource, image);
}
return state || album_art;
}
} // namespace xrme

63
3rdparty/libxrme/remotecontrolhandler.h vendored Normal file
View File

@ -0,0 +1,63 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTECONTROLHANDLER_H
#define REMOTECONTROLHANDLER_H
#include "handler.h"
#include <QString>
#include <gloox/iqhandler.h>
namespace xrme {
class RemoteControlInterface;
class RemoteControlHandler : public Handler,
public gloox::IqHandler {
public:
RemoteControlHandler(RemoteControlInterface* interface);
void PlayPause(const QString& jid_resource);
void Stop(const QString& jid_resource);
void Next(const QString& jid_resource);
void Previous(const QString& jid_resource);
void QueryState(const QString& jid_resource);
// Handler
void Init(Connection* connection, gloox::Client* client);
// gloox::IqHandler
bool handleIq(gloox::Stanza* stanza);
bool handleIqID(gloox::Stanza*, int) {}
private:
void SendIQ(const QString& jid_resource, gloox::StanzaSubType type,
const QString& command);
static int ParseInt(gloox::Tag* tag, const char* attribute_name);
static double ParseDouble(gloox::Tag* tag, const char* attribute_name);
static QString ParseString(gloox::Tag* tag, const char* attribute_name);
private:
RemoteControlInterface* interface_;
};
} // namespace xrme
#endif // REMOTECONTROLHANDLER_H

View File

@ -0,0 +1,72 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "remotecontrolhandler.h"
#include "remotecontrolinterface.h"
namespace xrme {
struct RemoteControlInterface::Private {
Private()
: handler_(NULL) {}
RemoteControlHandler* handler_;
};
RemoteControlInterface::RemoteControlInterface()
: d(new Private) {
}
RemoteControlInterface::~RemoteControlInterface() {
}
void RemoteControlInterface::PlayPause(const QString& peer_jid_resource) {
if (d->handler_) {
d->handler_->PlayPause(peer_jid_resource);
}
}
void RemoteControlInterface::Stop(const QString& peer_jid_resource) {
if (d->handler_) {
d->handler_->Stop(peer_jid_resource);
}
}
void RemoteControlInterface::Next(const QString& peer_jid_resource) {
if (d->handler_) {
d->handler_->Next(peer_jid_resource);
}
}
void RemoteControlInterface::Previous(const QString& peer_jid_resource) {
if (d->handler_) {
d->handler_->Previous(peer_jid_resource);
}
}
void RemoteControlInterface::QueryState(const QString& peer_jid_resource) {
if (d->handler_) {
d->handler_->QueryState(peer_jid_resource);
}
}
void RemoteControlInterface::Attach(RemoteControlHandler* handler) {
d->handler_ = handler;
}
} // namespace xrme

View File

@ -0,0 +1,66 @@
/* This file is part of the XMPP Remote Media Extension.
Copyright 2010, David Sansome <me@davidsansome.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef XRME_REMOTECONTROLINTERFACE_H
#define XRME_REMOTECONTROLINTERFACE_H
#include "common.h"
#include "connection.h"
class QImage;
namespace xrme {
class RemoteControlHandler;
class RemoteControlInterface {
public:
RemoteControlInterface();
virtual ~RemoteControlInterface();
// All functions here will work asynchronously and return immediately.
// Call these to control the playback of a MediaPlayer.
void PlayPause(const QString& peer_jid_resource);
void Stop(const QString& peer_jid_resource);
void Next(const QString& peer_jid_resource);
void Previous(const QString& peer_jid_resource);
// Call this to query the MediaPlayer. StateChanged() will be called later.
void QueryState(const QString& peer_jid_resource);
// Called whenever the MediaPlayer's state changes.
virtual void StateChanged(const QString& peer_jid_resource,
const State& state) = 0;
// Called whenever the MediaPlayer's album art changes.
virtual void AlbumArtChanged(const QString& peer_jid_resource,
const QImage& art) = 0;
private:
Q_DISABLE_COPY(RemoteControlInterface);
friend class RemoteControlHandler;
void Attach(RemoteControlHandler* handler);
struct Private;
QScopedPointer<Private> d;
};
} // namespace xrme
#endif // XRME_REMOTECONTROLINTERFACE_H

View File

@ -237,7 +237,7 @@ option(STATIC_SQLITE "Compile and use a static sqlite3 library" ON)
if(ENABLE_REMOTE AND GLOOX_LIBRARIES)
set(HAVE_REMOTE ON)
add_subdirectory(3rdparty/keychain)
add_subdirectory(3rdparty/libxrme)
endif(ENABLE_REMOTE AND GLOOX_LIBRARIES)
set(HAVE_STATIC_SQLITE ${STATIC_SQLITE})

View File

@ -12,6 +12,7 @@ if(WIN32)
include_directories(../3rdparty/qtwin)
endif(WIN32)
include_directories(${CMAKE_BINARY_DIR})
include_directories(${GLIB_INCLUDE_DIRS})
include_directories(${LIBXML_INCLUDE_DIRS})
include_directories(${GOBJECT_INCLUDE_DIRS})
@ -52,6 +53,7 @@ set(SOURCES
analyzers/turbine.cpp
core/albumcoverloader.cpp
core/artloader.cpp
core/backgroundstreams.cpp
core/backgroundthread.cpp
core/commandlineoptions.cpp
@ -66,7 +68,6 @@ set(SOURCES
core/gnomeglobalshortcutbackend.cpp
core/kittenloader.cpp
core/mergedproxymodel.cpp
core/mpris_common.cpp
core/musicstorage.cpp
core/network.cpp
core/networkproxyfactory.cpp
@ -258,6 +259,7 @@ set(HEADERS
analyzers/turbine.h
core/albumcoverloader.h
core/artloader.h
core/backgroundstreams.h
core/backgroundthread.h
core/database.h
@ -268,7 +270,6 @@ set(HEADERS
core/kittenloader.h
core/mergedproxymodel.h
core/mimedata.h
core/mpris_common.h
core/network.h
core/organise.h
core/player.h
@ -663,36 +664,6 @@ if(HAVE_DBUS)
# Gnome Screensaver DBus interface
list(APPEND SOURCES ui/dbusscreensaver.cpp)
if(HAVE_REMOTE)
# TODO: Use CMake macro when it supports -i
find_program(QDBUSXML2CPP qdbusxml2cpp)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h
COMMAND ${QDBUSXML2CPP}
dbus/org.freedesktop.Avahi.EntryGroup.xml
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup
-i dbus/metatypes.h
DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h
COMMAND ${QDBUSXML2CPP}
dbus/org.freedesktop.Avahi.Server.xml
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver
-i dbus/metatypes.h
DEPENDS dbus/org.freedesktop.Avahi.Server.xml
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp)
endif(HAVE_REMOTE)
endif(HAVE_DBUS)
# Libgpod device backend
@ -788,24 +759,15 @@ if(HAVE_SCRIPTING_PYTHON)
endif(HAVE_SCRIPTING_PYTHON)
if(HAVE_REMOTE)
include_directories(${GLOOX_INCLUDE_DIRS})
link_directories(${GLOOX_LIBRARY_DIRS})
include_directories(../3rdparty/keychain)
list(APPEND UI remote/remoteconfig.ui)
list(APPEND HEADERS remote/remoteconfig.h)
list(APPEND SOURCES remote/remoteconfig.cpp)
list(APPEND HEADERS remote/xmpp.h)
list(APPEND SOURCES remote/xmpp.cpp)
list(APPEND SOURCES remote/zeroconf.cpp)
if(APPLE)
list(APPEND SOURCES remote/bonjour.mm)
endif(APPLE)
if(HAVE_DBUS)
list(APPEND SOURCES remote/avahi.cpp)
list(APPEND HEADERS remote/avahi.h)
endif(HAVE_DBUS)
list(APPEND HEADERS
remote/remote.h
remote/remoteconfig.h
)
list(APPEND SOURCES
remote/remote.cpp
remote/remoteconfig.cpp
)
endif(HAVE_REMOTE)
# OS-specific sources that should be searched for translatable strings even
@ -846,6 +808,11 @@ list(APPEND OTHER_SOURCES
devices/wmdmlister.h
devices/wmdmloader.h
devices/wmdmloader.cpp
remote/remote.cpp
remote/remote.h
remote/remoteconfig.cpp
remote/remoteconfig.h
${CMAKE_CURRENT_BINARY_DIR}/ui_remoteconfig.h
ui/macsystemtrayicon.h
ui/macsystemtrayicon.mm
ui/wiimotedevshortcutsconfig.cpp
@ -968,7 +935,7 @@ endif(HAVE_SCRIPTING_PYTHON)
if(HAVE_REMOTE)
target_link_libraries(clementine_lib ${GLOOX_LIBRARIES})
target_link_libraries(clementine_lib keychain)
target_link_libraries(clementine_lib xrme)
endif(HAVE_REMOTE)
if (APPLE)

View File

@ -16,13 +16,11 @@
*/
#include "albumcoverloader.h"
#include "mpris_common.h"
#include "artloader.h"
#include <QDir>
#include <QTemporaryFile>
namespace mpris {
ArtLoader::ArtLoader(QObject* parent)
: QObject(parent),
temp_file_pattern_(QDir::tempPath() + "/clementine-art-XXXXXX.jpg"),
@ -56,6 +54,7 @@ void ArtLoader::TempArtLoaded(quint64 id, const QImage& image) {
QString uri;
QString thumbnail_uri;
QImage thumbnail;
if (!image.isNull()) {
temp_art_.reset(new QTemporaryFile(temp_file_pattern_));
@ -66,16 +65,13 @@ void ArtLoader::TempArtLoaded(quint64 id, const QImage& image) {
// since it's the GUI thread, but the alternative is hard.
temp_art_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
temp_art_thumbnail_->open();
image.scaledToHeight(120, Qt::SmoothTransformation)
.save(temp_art_thumbnail_->fileName(), "JPEG");
thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation);
thumbnail.save(temp_art_thumbnail_->fileName(), "JPEG");
uri = "file://" + temp_art_->fileName();
thumbnail_uri = "file://" + temp_art_thumbnail_->fileName();
}
emit ArtLoaded(last_song_, uri);
emit ThumbnailLoaded(last_song_, thumbnail_uri);
emit ArtLoaded(last_song_, uri, image);
emit ThumbnailLoaded(last_song_, thumbnail_uri, thumbnail);
}
} // namespace mpris

62
src/core/artloader.h Normal file
View File

@ -0,0 +1,62 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARTLOADER_H
#define ARTLOADER_H
#include "backgroundthread.h"
#include "song.h"
#include <QObject>
#include <boost/scoped_ptr.hpp>
class AlbumCoverLoader;
class QImage;
class QTemporaryFile;
class ArtLoader : public QObject {
Q_OBJECT
public:
ArtLoader(QObject* parent = 0);
~ArtLoader();
public slots:
void LoadArt(const Song& song);
signals:
void ArtLoaded(const Song& song, const QString& uri, const QImage& image);
void ThumbnailLoaded(const Song& song, const QString& uri, const QImage& image);
private slots:
void Initialised();
void TempArtLoaded(quint64 id, const QImage& image);
private:
QString temp_file_pattern_;
boost::scoped_ptr<QTemporaryFile> temp_art_;
boost::scoped_ptr<QTemporaryFile> temp_art_thumbnail_;
BackgroundThread<AlbumCoverLoader>* cover_loader_;
quint64 id_;
Song last_song_;
};
#endif // ARTLOADER_H

View File

@ -18,6 +18,7 @@
#include "mpris.h"
#include "mpris1.h"
#include "mpris2.h"
#include "core/artloader.h"
namespace mpris {

View File

@ -20,11 +20,11 @@
#include <QObject>
class ArtLoader;
class Player;
namespace mpris {
class ArtLoader;
class Mpris1;
class Mpris2;

View File

@ -15,6 +15,7 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "artloader.h"
#include "mpris1.h"
#include "mpris_common.h"

View File

@ -24,6 +24,7 @@
#include <QDBusArgument>
#include <QObject>
class ArtLoader;
class PlayerInterface;
class Playlist;
@ -54,7 +55,6 @@ Q_DECLARE_METATYPE(Version);
QDBusArgument& operator <<(QDBusArgument& arg, const Version& version);
const QDBusArgument& operator >>(const QDBusArgument& arg, Version& version);
namespace mpris {
enum DBusCaps {
@ -68,8 +68,6 @@ enum DBusCaps {
CAN_HAS_TRACKLIST = 1 << 6,
};
class ArtLoader;
class Mpris1Root;
class Mpris1Player;
class Mpris1TrackList;

View File

@ -19,6 +19,7 @@
#include "mpris_common.h"
#include "mpris1.h"
#include "mpris2.h"
#include "core/artloader.h"
#include "core/mpris2_player.h"
#include "core/mpris2_root.h"
#include "core/mpris2_tracklist.h"
@ -56,7 +57,7 @@ Mpris2::Mpris2(PlayerInterface* player, ArtLoader* art_loader,
QDBusConnection::sessionBus().registerService(kServiceName);
QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this);
connect(art_loader, SIGNAL(ArtLoaded(Song,QString)), SLOT(ArtLoaded(Song,QString)));
connect(art_loader, SIGNAL(ArtLoaded(Song,QString,QImage)), SLOT(ArtLoaded(Song,QString)));
connect(player->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
connect(player, SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));

View File

@ -26,6 +26,7 @@
#include <boost/scoped_ptr.hpp>
class ArtLoader;
class MainWindow;
class PlayerInterface;
@ -35,7 +36,6 @@ Q_DECLARE_METATYPE(TrackMetadata)
namespace mpris {
class ArtLoader;
class Mpris1;
class Mpris2 : public QObject {

View File

@ -18,53 +18,13 @@
#ifndef MPRIS_COMMON_H
#define MPRIS_COMMON_H
#include "backgroundthread.h"
#include "song.h"
#include <QDateTime>
#include <QObject>
#include <QStringList>
#include <QVariantMap>
#include <boost/scoped_ptr.hpp>
class AlbumCoverLoader;
class QImage;
class QTemporaryFile;
namespace mpris {
class ArtLoader : public QObject {
Q_OBJECT
public:
ArtLoader(QObject* parent = 0);
~ArtLoader();
public slots:
void LoadArt(const Song& song);
signals:
void ArtLoaded(const Song& song, const QString& uri);
void ThumbnailLoaded(const Song& song, const QString& uri);
private slots:
void Initialised();
void TempArtLoaded(quint64 id, const QImage& image);
private:
QString temp_file_pattern_;
boost::scoped_ptr<QTemporaryFile> temp_art_;
boost::scoped_ptr<QTemporaryFile> temp_art_thumbnail_;
BackgroundThread<AlbumCoverLoader>* cover_loader_;
quint64 id_;
Song last_song_;
};
inline void AddMetadata(const QString& key, const QString& metadata, QVariantMap* map) {
if (!metadata.isEmpty()) (*map)[key] = metadata;
}

View File

@ -24,6 +24,7 @@
#endif // Q_OS_WIN32
#include "config.h"
#include "core/artloader.h"
#include "core/commandlineoptions.h"
#include "core/database.h"
#include "core/encoding.h"
@ -80,7 +81,6 @@ using boost::scoped_ptr;
#endif
#ifdef HAVE_DBUS
#include "core/mpris_common.h"
#include "core/mpris.h"
#include "core/mpris2.h"
#include "dbus/metatypes.h"
@ -92,11 +92,6 @@ using boost::scoped_ptr;
const QDBusArgument& operator>> (const QDBusArgument& arg, QImage& image);
#endif
#ifdef HAVE_REMOTE
#include "remote/xmpp.h"
#include "remote/zeroconf.h"
#endif
class GstEnginePipeline;
// Load sqlite plugin on windows and mac.
@ -316,25 +311,21 @@ int main(int argc, char *argv[]) {
scoped_ptr<SystemTrayIcon> tray_icon(SystemTrayIcon::CreateSystemTrayIcon());
OSD osd(tray_icon.get());
ArtLoader art_loader;
#ifdef HAVE_DBUS
qDBusRegisterMetaType<QImage>();
qDBusRegisterMetaType<TrackMetadata>();
qDBusRegisterMetaType<TrackIds>();
qDBusRegisterMetaType<QList<QByteArray> >();
mpris::ArtLoader art_loader;
mpris::Mpris mpris(&player, &art_loader);
QObject::connect(&playlists, SIGNAL(CurrentSongChanged(Song)), &art_loader, SLOT(LoadArt(Song)));
QObject::connect(&art_loader, SIGNAL(ThumbnailLoaded(Song, QString)),
QObject::connect(&art_loader, SIGNAL(ThumbnailLoaded(Song, QString, QImage)),
&osd, SLOT(CoverArtPathReady(Song, QString)));
#endif
#ifdef HAVE_REMOTE
XMPP xmpp;
xmpp.Connect();
#endif
// Window
MainWindow w(
database.get(),
@ -343,7 +334,8 @@ int main(int argc, char *argv[]) {
&radio_model,
&player,
tray_icon.get(),
&osd);
&osd,
&art_loader);
#ifdef HAVE_DBUS
QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise()));
#endif

View File

@ -1,95 +0,0 @@
#include "avahi.h"
#include <QDBusConnection>
#include <QHostInfo>
#include <QtDebug>
#include "dbus/avahientrygroup.h"
#include "dbus/avahiserver.h"
Avahi::Avahi()
: server_(NULL),
entry_group_(NULL)
{
}
void Avahi::Publish(const QString& domain,
const QString& type,
const QString& name,
quint16 port) {
if (server_) {
// Already published
return;
}
domain_ = domain;
type_ = type;
name_ = name;
port_ = port;
server_ = new OrgFreedesktopAvahiServerInterface(
"org.freedesktop.Avahi",
"/",
QDBusConnection::systemBus(),
this);
QDBusPendingReply<QDBusObjectPath> reply = server_->EntryGroupNew();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
SLOT(EntryGroupNewFinished(QDBusPendingCallWatcher*)));
}
void Avahi::EntryGroupNewFinished(QDBusPendingCallWatcher* call) {
call->deleteLater();
QDBusPendingReply<QDBusObjectPath> reply = *call;
if (reply.isError()) {
qWarning() << "Failed to create new Avahi entry group:" << call->error().message();
return;
}
entry_group_ = new OrgFreedesktopAvahiEntryGroupInterface(
"org.freedesktop.Avahi",
reply.value().path(),
QDBusConnection::systemBus(),
this);
QDBusPendingReply<> add_reply = entry_group_->AddService(
-1, // Interface (Unspecified, ie. all interfaces)
-1, // Protocol (Unspecified, ie. IPv4 & IPv6)
0, // Flags
name_, // Service name
type_, // Service type
domain_, // Domain, ie. local
QString::null, // Hostname (Avahi fills it if it's null)
port_, // Port
QList<QByteArray>()); // TXT record
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(add_reply);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
SLOT(AddServiceFinished(QDBusPendingCallWatcher*)));
}
void Avahi::AddServiceFinished(QDBusPendingCallWatcher* call) {
call->deleteLater();
if (call->isError()) {
qWarning() << "Failed to add Avahi service:" << call->error().message();
return;
}
QDBusPendingReply<> commit_reply = entry_group_->Commit();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(commit_reply);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
SLOT(CommitFinished(QDBusPendingCallWatcher*)));
}
void Avahi::CommitFinished(QDBusPendingCallWatcher* call) {
call->deleteLater();
if (call->isError()) {
qWarning() << "Failed to commit Avahi changes:" << call->error().message();
} else {
qDebug() << "Remote interface published on Avahi";
}
}

View File

@ -1,39 +0,0 @@
#ifndef AVAHI_H
#define AVAHI_H
#include "zeroconf.h"
#include <QObject>
class OrgFreedesktopAvahiEntryGroupInterface;
class OrgFreedesktopAvahiServerInterface;
class QDBusPendingCallWatcher;
class Avahi : public QObject, public Zeroconf {
Q_OBJECT
public:
Avahi();
virtual void Publish(const QString& domain,
const QString& type,
const QString& name,
quint16 port);
private slots:
void EntryGroupNewFinished(QDBusPendingCallWatcher* call);
void AddServiceFinished(QDBusPendingCallWatcher* call);
void CommitFinished(QDBusPendingCallWatcher* call);
private:
OrgFreedesktopAvahiServerInterface* server_;
OrgFreedesktopAvahiEntryGroupInterface* entry_group_;
QString domain_;
QString type_;
QString name_;
quint16 port_;
};
#endif

View File

@ -1,18 +0,0 @@
#ifndef BONJOUR_H
#define BONJOUR_H
#include "zeroconf.h"
class BonjourWrapper;
class Bonjour : public Zeroconf {
public:
Bonjour();
virtual ~Bonjour();
void Publish(const QString& domain, const QString& type, const QString& name, quint16 port);
private:
BonjourWrapper* wrapper_;
};
#endif

View File

@ -1,84 +0,0 @@
#include "bonjour.h"
#include <QtDebug>
#import <Foundation/Foundation.h>
@interface NetServicePublicationDelegate : NSObject <NSNetServiceDelegate> {
NSMutableArray* services_;
}
- (void)netServiceWillPublish:(NSNetService*)netService;
- (void)netService:(NSNetService*)netService
didNotPublish:(NSDictionary*)errorDict;
- (void)netServiceDidStop:(NSNetService*)netService;
@end
@implementation NetServicePublicationDelegate
- (id)init {
self = [super init];
if (self) {
services_ = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[services_ release];
[super dealloc];
}
- (void)netServiceWillPublish: (NSNetService*)netService {
[services_ addObject: netService];
qDebug() << Q_FUNC_INFO
<< [[netService name] UTF8String];
}
- (void)netService: (NSNetService*)netService didNotPublish: (NSDictionary*)errorDict {
[services_ removeObject: netService];
qDebug() << Q_FUNC_INFO;
NSLog(@"%@", errorDict);
}
- (void)netServiceDidStop: (NSNetService*)netService {
[services_ removeObject: netService];
qDebug() << Q_FUNC_INFO;
}
@end
class BonjourWrapper {
public:
BonjourWrapper() {
delegate_ = [[NetServicePublicationDelegate alloc] init];
}
NetServicePublicationDelegate* delegate() const { return delegate_; }
private:
NetServicePublicationDelegate* delegate_;
};
#define QSTRING_TO_NSSTRING(x) \
[[NSString alloc] initWithUTF8String:x.toUtf8().constData()]
Bonjour::Bonjour()
: wrapper_(new BonjourWrapper) {
}
Bonjour::~Bonjour() {
delete wrapper_;
}
void Bonjour::Publish(const QString& domain, const QString& type, const QString& name, quint16 port) {
NSNetService* service = [[NSNetService alloc] initWithDomain: QSTRING_TO_NSSTRING(domain)
type: QSTRING_TO_NSSTRING(type)
name: QSTRING_TO_NSSTRING(name)
port: port];
if (service) {
[service setDelegate: wrapper_->delegate()];
[service publish];
}
}

165
src/remote/remote.cpp Normal file
View File

@ -0,0 +1,165 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "remote.h"
#include "remoteconfig.h"
#include "core/player.h"
#include "engines/enginebase.h"
#include "playlist/playlist.h"
#include "playlist/playlistmanager.h"
#include <QSettings>
#include <QTimer>
Remote::Remote(Player* player, QObject* parent)
: QObject(parent),
player_(player),
connection_(new xrme::Connection(this)),
retry_count_(0)
{
connection_->SetMediaPlayer(this);
connection_->set_verbose(true);
connect(connection_, SIGNAL(Connected()), SLOT(Connected()));
connect(connection_, SIGNAL(Disconnected(QString)), SLOT(Disconnected(QString)));
connect(player_, SIGNAL(Playing()), SLOT(SetStateChanged()));
connect(player_, SIGNAL(Paused()), SLOT(SetStateChanged()));
connect(player_, SIGNAL(Stopped()), SLOT(SetStateChanged()));
connect(player_, SIGNAL(PlaylistFinished()), SLOT(SetStateChanged()));
connect(player_, SIGNAL(VolumeChanged(int)), SLOT(SetStateChanged()));
connect(player_, SIGNAL(Seeked(qlonglong)), SLOT(SetStateChanged()));
connect(player_->playlists(), SIGNAL(CurrentSongChanged(Song)), SLOT(SetStateChanged()));
ReloadSettings();
}
void Remote::ReloadSettings() {
QSettings s;
s.beginGroup(RemoteConfig::kSettingsGroup);
QString username = s.value("username").toString();
QString password = s.value("password").toString();
QString agent_name = s.value("agent_name", RemoteConfig::DefaultAgentName()).toString();
// Have the settings changed?
if (username != connection_->username() ||
password != connection_->password() ||
agent_name != connection_->agent_name()) {
connection_->set_username(username);
connection_->set_agent_name(agent_name);
connection_->set_password(password);
if (connection_->is_connected()) {
// We'll reconnect later
connection_->Disconnect();
} else if (is_configured()) {
connection_->Connect();
}
}
}
bool Remote::is_configured() const {
return !connection_->username().isEmpty() &&
!connection_->password().isEmpty() &&
!connection_->agent_name().isEmpty();
}
void Remote::Connected() {
retry_count_ = 0;
}
void Remote::Disconnected(const QString& error) {
if (retry_count_++ >= kMaxRetries) {
// Show an error and give up if we're above the retry count
if (!error.isEmpty()) {
emit Error("XMPP remote control disconnected: " + error);
}
} else if (is_configured()) {
// Try again
QTimer::singleShot(0, connection_, SLOT(Connect()));
}
}
void Remote::PlayPause() {
player_->PlayPause();
}
void Remote::Stop() {
player_->Stop();
}
void Remote::Next() {
player_->Next();
}
void Remote::Previous() {
player_->Previous();
}
xrme::State Remote::state() const {
const Playlist* active = player_->playlists()->active();
const Engine::State state = player_->GetState();
const PlaylistItemPtr current_item = player_->GetCurrentItem();
xrme::State ret;
ret.can_go_next = active->next_row() != -1 ||
active->current_item_options() & PlaylistItem::ContainsMultipleTracks;
ret.can_go_previous = active->previous_row() != -1;
ret.can_seek = current_item &&
current_item->Metadata().filetype() != Song::Type_Stream;
switch (state) {
case Engine::Playing: ret.playback_state = xrme::State::PlaybackState_Playing; break;
case Engine::Paused: ret.playback_state = xrme::State::PlaybackState_Paused; break;
case Engine::Idle:
case Engine::Empty: ret.playback_state = xrme::State::PlaybackState_Stopped; break;
}
ret.position_millisec = player_->engine()->position_nanosec() / kNsecPerMsec;
ret.volume = double(player_->GetVolume()) / 100;
if (current_item) {
const Song m = current_item->Metadata();
ret.metadata.title = m.title();
ret.metadata.artist = m.artist();
ret.metadata.album = m.album();
ret.metadata.albumartist = m.albumartist();
ret.metadata.composer = m.composer();
ret.metadata.genre = m.genre();
ret.metadata.track = m.track();
ret.metadata.disc = m.disc();
ret.metadata.year = m.year();
ret.metadata.length_millisec = m.length_nanosec() / kNsecPerMsec;
ret.metadata.rating = m.rating();
}
return ret;
}
QImage Remote::album_art() const {
return last_image_;
}
void Remote::SetStateChanged() {
StateChanged();
}
void Remote::ArtLoaded(const Song&, const QString&, const QImage& image) {
last_image_ = image;
AlbumArtChanged();
}

73
src/remote/remote.h Normal file
View File

@ -0,0 +1,73 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef REMOTE_H
#define REMOTE_H
#include "core/song.h"
#include <QObject>
#include <xrme/connection.h>
#include <xrme/mediaplayerinterface.h>
class ArtLoader;
class Player;
class Remote : public QObject,
protected xrme::MediaPlayerInterface {
Q_OBJECT
public:
Remote(Player* player, QObject* parent = 0);
static const int kMaxRetries = 3;
public slots:
void ReloadSettings();
void ArtLoaded(const Song&, const QString&, const QImage& image);
signals:
void Error(const QString& message);
protected:
// xrme::MediaPlayerInterface
void PlayPause();
void Stop();
void Next();
void Previous();
xrme::State state() const;
QImage album_art() const;
private slots:
void Connected();
void Disconnected(const QString& error);
void SetStateChanged();
private:
bool is_configured() const;
private:
Player* player_;
xrme::Connection* connection_;
QImage last_image_;
int retry_count_;
};
#endif // REMOTE_H

View File

@ -19,8 +19,8 @@
#include "ui_remoteconfig.h"
#include "ui/iconloader.h"
#include "keychain.h"
#include <QCoreApplication>
#include <QHostInfo>
#include <QMessageBox>
#include <QNetworkReply>
#include <QSettings>
@ -46,6 +46,11 @@ RemoteConfig::RemoteConfig(QWidget *parent)
resize(sizeHint());
}
QString RemoteConfig::DefaultAgentName() {
return QString("%1 on %2").arg(QCoreApplication::applicationName(),
QHostInfo::localHostName());
}
RemoteConfig::~RemoteConfig() {
delete ui_;
}
@ -95,11 +100,13 @@ void RemoteConfig::AuthenticationComplete(bool success) {
ui_->busy->hide();
waiting_for_auth_ = false;
if (success) {
validated_password_ = ui_->password->text();
ui_->password->clear();
} else {
if (!success) {
QMessageBox::warning(this, tr("Authentication failed"), tr("Your Google credentials were incorrect"));
} else {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("password", ui_->password->text());
ui_->password->clear();
}
emit ValidationComplete(success);
@ -108,24 +115,25 @@ void RemoteConfig::AuthenticationComplete(bool success) {
void RemoteConfig::Load() {
QSettings s;
s.beginGroup(kSettingsGroup);
QVariant username = s.value("username");
if (username.isValid()) {
ui_->username->setText(username.toString());
}
ui_->username->setText(s.value("username").toString());
ui_->agent_name->setText(s.value("agent_name", DefaultAgentName()).toString());
}
void RemoteConfig::Save() {
QSettings s;
s.beginGroup(kSettingsGroup);
const QString& username = ui_->username->text();
s.setValue("username", username);
Keychain* keychain = Keychain::getDefault();
keychain->setPassword(username, validated_password_);
validated_password_.clear();
s.setValue("username", ui_->username->text());
s.setValue("agent_name", ui_->agent_name->text());
}
void RemoteConfig::SignOut() {
ui_->username->clear();
ui_->password->clear();
ui_->sign_out->setEnabled(false);
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("password", QString());
}

View File

@ -31,6 +31,8 @@ class RemoteConfig : public QWidget {
RemoteConfig(QWidget* parent = 0);
~RemoteConfig();
static QString DefaultAgentName();
bool NeedsValidation() const;
static const char* kSettingsGroup;
@ -54,8 +56,6 @@ class RemoteConfig : public QWidget {
Ui_RemoteConfig* ui_;
bool waiting_for_auth_;
NetworkAccessManager* network_;
QString validated_password_;
};
#endif // REMOTECONFIG_H

View File

@ -6,20 +6,27 @@
<rect>
<x>0</x>
<y>0</y>
<width>773</width>
<height>555</height>
<width>507</width>
<height>437</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Clementine can be controlled remotely by an Android phone. To enable this feature log in with the same Google account that is configured on your phone.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Account details</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@ -27,20 +34,6 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Google password</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
@ -65,6 +58,49 @@
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Google password</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Player name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="agent_name"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_7">
<property name="text">
<string>If you use the remote on more than one computer, this name will help you choose which one to connect to on your phone.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -1,93 +0,0 @@
#include "xmpp.h"
#include <gloox/connectiontcpclient.h>
#include <QSettings>
#include <QtDebug>
#include "keychain.h"
#include "remoteconfig.h"
using gloox::Client;
using gloox::ConnectionTCPClient;
using gloox::JID;
using gloox::MessageSession;
XMPP::XMPP() {
}
XMPP::~XMPP() {
}
void XMPP::Connect() {
QSettings s;
s.beginGroup(RemoteConfig::kSettingsGroup);
QVariant username = s.value("username");
if (username.isValid()) {
Keychain* keychain = Keychain::getDefault();
QString password = keychain->getPassword(username.toString());
Connect(username.toString() + "/clementine", password);
} else {
qWarning() << "No username or password set.";
}
}
void XMPP::Connect(const QString& jid, const QString& password) {
// TODO: Generate <256 char resource.
JID j(jid.toUtf8().constData());
client_.reset(new Client(j, password.toUtf8().constData()));
ConnectionTCPClient* connection = new ConnectionTCPClient(
client_.get(), client_->logInstance(), "talk.google.com");
client_->setConnectionImpl(connection);
client_->registerConnectionListener(this);
client_->registerMessageHandler(this);
client_->logInstance().registerLogHandler(gloox::LogLevelDebug, gloox::LogAreaAll, this);
client_->setPresence(gloox::PresenceAvailable, -128);
client_->connect(false);
notifier_.reset(new QSocketNotifier(connection->socket(), QSocketNotifier::Read));
connect(notifier_.get(), SIGNAL(activated(int)), SLOT(Receive()));
}
void XMPP::handleMessage(gloox::Stanza* stanza, MessageSession* session) {
qDebug() << Q_FUNC_INFO;
qDebug() << stanza->xml().c_str();
gloox::Stanza* reply = gloox::Stanza::createMessageStanza(
stanza->from(), "Hello world!");
client_->send(reply);
}
void XMPP::onConnect() {
qDebug() << "Connected with resource:" << client_->resource().c_str()
<< client_->jid().full().c_str();
client_->login();
}
void XMPP::onDisconnect(gloox::ConnectionError e) {
qDebug() << "Disconnected:" << e;
notifier_->setEnabled(false);
}
bool XMPP::onTLSConnect(const gloox::CertInfo& info) {
return true;
}
void XMPP::Receive() {
client_->recv();
}
void XMPP::handleLog(gloox::LogLevel level, gloox::LogArea area,
const std::string& message) {
QString prefix = "---";
if (area == gloox::LogAreaXmlIncoming) {
prefix = "<<<";
} else if (area == gloox::LogAreaXmlOutgoing) {
prefix = ">>>";
}
qDebug() << "XMPP" << prefix.toAscii().constData() << message.c_str();
}

View File

@ -1,47 +0,0 @@
#ifndef XMPP_H
#define XMPP_H
#include <gloox/client.h>
#include <gloox/connectionlistener.h>
#include <gloox/loghandler.h>
#include <gloox/messagehandler.h>
#include <boost/scoped_ptr.hpp>
#include <QSocketNotifier>
#include <QString>
class XMPP : public QObject,
public gloox::ConnectionListener,
public gloox::MessageHandler,
public gloox::LogHandler {
Q_OBJECT
public:
XMPP();
virtual ~XMPP();
void Connect();
void Connect(const QString& jid, const QString& password);
private slots:
void Receive();
private:
// gloox::MessageHandler
virtual void handleMessage(gloox::Stanza* stanza,
gloox::MessageSession* session = 0);
// gloox::ConnectionListener
virtual void onConnect();
virtual void onDisconnect(gloox::ConnectionError e);
virtual bool onTLSConnect(const gloox::CertInfo& info);
// gloox::LogHandler
virtual void handleLog(gloox::LogLevel level, gloox::LogArea area,
const std::string& message);
boost::scoped_ptr<gloox::Client> client_;
boost::scoped_ptr<QSocketNotifier> notifier_;
};
#endif // XMPP_H

View File

@ -1,25 +0,0 @@
#include "zeroconf.h"
#include "config.h"
#ifdef Q_OS_DARWIN
#include "bonjour.h"
#endif
#ifdef HAVE_DBUS
#include "avahi.h"
#endif
Zeroconf* Zeroconf::instance_ = NULL;
Zeroconf* Zeroconf::GetZeroconf() {
if (!instance_) {
#if defined(Q_OS_DARWIN)
instance_ = new Bonjour();
#elif defined(HAVE_DBUS)
instance_ = new Avahi();
#endif
}
return instance_;
}

View File

@ -1,21 +0,0 @@
#ifndef ZEROCONF_H
#define ZEROCONF_H
#include <QString>
class Zeroconf {
public:
virtual ~Zeroconf() {}
virtual void Publish(const QString& domain,
const QString& type,
const QString& name,
quint16 port) = 0;
static Zeroconf* GetZeroconf();
private:
static Zeroconf* instance_;
};
#endif

View File

@ -17,6 +17,7 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "core/artloader.h"
#include "core/backgroundstreams.h"
#include "core/commandlineoptions.h"
#include "core/database.h"
@ -103,6 +104,10 @@
# include "visualisations/visualisationcontainer.h"
#endif
#ifdef HAVE_REMOTE
# include "remote/remote.h"
#endif
#include <QCloseEvent>
#include <QDir>
#include <QFileDialog>
@ -148,6 +153,7 @@ MainWindow::MainWindow(
Player* player,
SystemTrayIcon* tray_icon,
OSD* osd,
ArtLoader* art_loader,
QWidget* parent)
: QMainWindow(parent),
ui_(new Ui_MainWindow),
@ -162,6 +168,7 @@ MainWindow::MainWindow(
player_(player),
library_(NULL),
global_shortcuts_(new GlobalShortcuts(this)),
remote_(NULL),
devices_(NULL),
library_view_(new LibraryViewContainer(this)),
file_view_(new FileView(this)),
@ -574,6 +581,14 @@ MainWindow::MainWindow(
connect(global_shortcuts_, SIGNAL(RateCurrentSong(int)), playlists_, SLOT(RateCurrentSong(int)));
// XMPP Remote control
#ifdef HAVE_REMOTE
remote_ = new Remote(player_, this);
connect(remote_, SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString)));
connect(art_loader, SIGNAL(ArtLoaded(Song,QString,QImage)),
remote_, SLOT(ArtLoaded(Song,QString,QImage)));
#endif
// Fancy tabs
connect(ui_->tabs, SIGNAL(ModeChanged(FancyTabWidget::Mode)), SLOT(SaveGeometry()));
connect(ui_->tabs, SIGNAL(CurrentChanged(int)), SLOT(SaveGeometry()));
@ -733,6 +748,9 @@ void MainWindow::ReloadAllSettings() {
#ifdef ENABLE_WIIMOTEDEV
wiimotedev_shortcuts_->ReloadSettings();
#endif
#ifdef HAVE_REMOTE
remote_->ReloadSettings();
#endif
}
void MainWindow::MediaStopped() {

View File

@ -34,6 +34,7 @@
class About;
class AddStreamDialog;
class ArtistInfoView;
class ArtLoader;
class BackgroundStreams;
class CommandlineOptions;
class Database;
@ -58,6 +59,7 @@ class QueueManager;
class RadioItem;
class RadioModel;
class RadioViewContainer;
class Remote;
class ScriptDialog;
class ScriptManager;
class Song;
@ -88,6 +90,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
Player* player,
SystemTrayIcon* tray_icon,
OSD* osd,
ArtLoader* art_loader,
QWidget *parent = 0);
~MainWindow();
@ -255,6 +258,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
Player* player_;
Library* library_;
GlobalShortcuts* global_shortcuts_;
Remote* remote_;
DeviceManager* devices_;