2011-02-19 19:24:11 +01:00
|
|
|
/* 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"
|
|
|
|
|
2011-03-08 22:16:09 +01:00
|
|
|
#include <QDataStream>
|
|
|
|
#include <QHostAddress>
|
2011-02-19 19:24:11 +01:00
|
|
|
#include <QSettings>
|
|
|
|
#include <QTimer>
|
|
|
|
|
2011-03-08 22:16:09 +01:00
|
|
|
#include <qjson/parser.h>
|
|
|
|
#include <qjson/serializer.h>
|
|
|
|
|
2011-02-19 19:24:11 +01:00
|
|
|
Remote::Remote(Player* player, QObject* parent)
|
|
|
|
: QObject(parent),
|
|
|
|
player_(player),
|
|
|
|
connection_(new xrme::Connection(this)),
|
|
|
|
retry_count_(0)
|
|
|
|
{
|
|
|
|
connection_->SetMediaPlayer(this);
|
2011-03-13 19:20:37 +01:00
|
|
|
connection_->SetMediaStorage(this);
|
2011-02-19 19:24:11 +01:00
|
|
|
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()));
|
|
|
|
|
2011-03-08 22:16:09 +01:00
|
|
|
connect(connection_,
|
|
|
|
SIGNAL(TomahawkSIPReceived(const QVariant&)),
|
|
|
|
SLOT(TomahawkSIPReceived(const QVariant&)));
|
|
|
|
|
2011-02-19 19:24:11 +01:00
|
|
|
ReloadSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::ReloadSettings() {
|
|
|
|
QSettings s;
|
|
|
|
s.beginGroup(RemoteConfig::kSettingsGroup);
|
|
|
|
|
|
|
|
QString username = s.value("username").toString();
|
2011-04-07 15:38:27 +02:00
|
|
|
QString password = s.value("password").toString();
|
2011-02-19 19:24:11 +01:00
|
|
|
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;
|
2011-03-20 13:43:10 +01:00
|
|
|
ret.can_seek = current_item && !current_item->Metadata().is_stream();
|
2011-02-19 19:24:11 +01:00
|
|
|
|
|
|
|
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_;
|
|
|
|
}
|
|
|
|
|
2011-03-13 19:20:37 +01:00
|
|
|
QStringList Remote::GetArtists() const {
|
|
|
|
return QStringList();
|
|
|
|
}
|
|
|
|
|
2011-02-19 19:24:11 +01:00
|
|
|
void Remote::SetStateChanged() {
|
|
|
|
StateChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::ArtLoaded(const Song&, const QString&, const QImage& image) {
|
|
|
|
last_image_ = image;
|
|
|
|
AlbumArtChanged();
|
|
|
|
}
|
2011-03-08 22:16:09 +01:00
|
|
|
|
|
|
|
void Remote::TomahawkSIPReceived(const QVariant& json) {
|
|
|
|
QVariantMap message = json.toMap();
|
|
|
|
const QString& ip = message["ip"].toString();
|
|
|
|
const QString& key = message["key"].toString();
|
|
|
|
const quint16 port = message["port"].toUInt();
|
|
|
|
const QString& unique_name = message["uniqname"].toString();
|
|
|
|
const bool visible = message["visible"].toBool();
|
|
|
|
|
|
|
|
QTcpSocket* socket = new QTcpSocket(this);
|
|
|
|
socket->connectToHost(ip, port);
|
|
|
|
connect(socket, SIGNAL(connected()), SLOT(TomahawkConnected()));
|
|
|
|
connect(socket, SIGNAL(disconnected()), SLOT(TomahawkDisconnected()));
|
|
|
|
connect(socket, SIGNAL(readyRead()), SLOT(TomahawkReadyRead()));
|
|
|
|
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
|
|
|
|
SLOT(TomahawkError(QAbstractSocket::SocketError)));
|
|
|
|
|
|
|
|
qDebug() << Q_FUNC_INFO << "connecting to" << ip << port;
|
|
|
|
|
|
|
|
TomahawkConnection* connection = new TomahawkConnection;
|
|
|
|
connection->key = key;
|
|
|
|
connection->unique_name = unique_name;
|
|
|
|
connection->visible = visible;
|
|
|
|
connection->num_bytes = -1;
|
|
|
|
tomahawk_connections_[socket] = connection;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkConnected() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
|
|
|
Q_ASSERT(socket);
|
|
|
|
|
|
|
|
TomahawkConnection* connection = tomahawk_connections_[socket];
|
|
|
|
|
|
|
|
QVariantMap map;
|
|
|
|
map["conntype"] = "accept-offer";
|
|
|
|
map["key"] = connection->key;
|
|
|
|
map["port"] = 12345;
|
|
|
|
map["nodeid"] = "foobar";
|
|
|
|
map["controlid"] = "foobarbaz";
|
|
|
|
|
|
|
|
QJson::Serializer serialiser;
|
|
|
|
const QByteArray& serialised = serialiser.serialize(map);
|
|
|
|
quint32 length = serialised.length();
|
|
|
|
|
|
|
|
// Send offer acceptance.
|
|
|
|
{
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)length;
|
|
|
|
stream << (quint8)(128 | 2);
|
|
|
|
stream.writeRawData(serialised.constData(), serialised.length());
|
|
|
|
qDebug() << "now:" << socket->bytesToWrite();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)2;
|
|
|
|
stream << (quint8)(128 | 1);
|
|
|
|
stream.writeRawData("ok", 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send ping.
|
|
|
|
{
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)0;
|
|
|
|
stream << (quint8)(32);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
{
|
|
|
|
QVariantMap json;
|
|
|
|
json["method"] = "dbsync-offer";
|
|
|
|
json["key"] = "abcdefg";
|
|
|
|
const QByteArray& request = serialiser.serialize(json);
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)request.length();
|
|
|
|
stream << (quint8)2; // JSON
|
|
|
|
stream.writeRawData(request.constData(), request.length());
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkReadyRead() {
|
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
|
|
|
Q_ASSERT(socket);
|
|
|
|
|
|
|
|
TomahawkConnection* connection = tomahawk_connections_[socket];
|
|
|
|
if (connection->num_bytes == -1) {
|
|
|
|
// Expecting header.
|
|
|
|
if (socket->bytesAvailable() >= 5) {
|
|
|
|
QDataStream stream(socket);
|
|
|
|
quint32 size;
|
|
|
|
stream >> size;
|
|
|
|
quint8 flags;
|
|
|
|
stream >> flags;
|
|
|
|
connection->num_bytes = size > 0 ? size : -1;
|
|
|
|
connection->flags = flags;
|
|
|
|
if (flags & 32) {
|
|
|
|
qDebug() << "PING!";
|
|
|
|
}
|
|
|
|
if (connection->num_bytes == -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (socket->bytesAvailable() < connection->num_bytes) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray data = socket->readAll();
|
|
|
|
connection->num_bytes = -1;
|
|
|
|
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
qDebug() << data;
|
|
|
|
qDebug() << Q_FUNC_INFO << "done";
|
|
|
|
|
|
|
|
if (connection->flags & 2) {
|
|
|
|
QJson::Parser parser;
|
|
|
|
bool ok = false;
|
|
|
|
QVariant json = parser.parse(data, &ok);
|
|
|
|
if (ok) {
|
|
|
|
QVariantMap map = json.toMap();
|
|
|
|
QString method = map["method"].toString();
|
2011-03-13 19:20:37 +01:00
|
|
|
QString conntype = map["conntype"].toString();
|
2011-03-08 22:16:09 +01:00
|
|
|
if (method == "dbsync-offer") {
|
|
|
|
qDebug() << "DBSYNC!";
|
|
|
|
TomahawkConnection* db_connection = new TomahawkConnection;
|
|
|
|
db_connection->key = map["key"].toString();
|
|
|
|
db_connection->num_bytes = -1;
|
|
|
|
QTcpSocket* sync_socket = new QTcpSocket(this);
|
|
|
|
sync_socket->connectToHost(socket->peerAddress(), socket->peerPort());
|
|
|
|
connect(sync_socket, SIGNAL(connected()), SLOT(TomahawkDBConnected()));
|
|
|
|
connect(sync_socket, SIGNAL(disconnected()), SLOT(TomahawkDBDisconnected()));
|
|
|
|
connect(sync_socket, SIGNAL(readyRead()), SLOT(TomahawkDBReadyRead()));
|
|
|
|
tomahawk_connections_[sync_socket] = db_connection;
|
2011-03-13 19:20:37 +01:00
|
|
|
} else if (conntype == "request-offer") {
|
|
|
|
qDebug() << "File Transfer!";
|
|
|
|
TomahawkConnection* file_transfer = new TomahawkConnection;
|
|
|
|
file_transfer->key = map["key"].toString();
|
|
|
|
file_transfer->num_bytes = -1;
|
|
|
|
file_transfer->offer = map["offer"].toString();
|
|
|
|
file_transfer->controlid = map["controlid"].toString();
|
|
|
|
QTcpSocket* file_transfer_socket = new QTcpSocket(this);
|
|
|
|
file_transfer_socket->connectToHost(socket->peerAddress(), map["port"].toUInt());
|
|
|
|
connect(file_transfer_socket, SIGNAL(connected()), SLOT(TomahawkTransferConnected()));
|
|
|
|
connect(file_transfer_socket, SIGNAL(disconnected()), SLOT(TomahawkTransferDisconnected()));
|
|
|
|
connect(file_transfer_socket, SIGNAL(readyRead()), SLOT(TomahawkTransferReadyRead()));
|
|
|
|
tomahawk_connections_[file_transfer_socket] = file_transfer;
|
2011-03-08 22:16:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkDBConnected() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
|
|
|
Q_ASSERT(socket);
|
|
|
|
|
|
|
|
TomahawkConnection* connection = tomahawk_connections_[socket];
|
|
|
|
|
|
|
|
{
|
|
|
|
QVariantMap request;
|
|
|
|
request["method"] = "accept-offer";
|
|
|
|
request["key"] = connection->key;
|
|
|
|
|
|
|
|
QJson::Serializer serialiser;
|
|
|
|
const QByteArray& json = serialiser.serialize(request);
|
|
|
|
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)json.length();
|
|
|
|
stream << (quint8)(2);
|
|
|
|
stream.writeRawData(json.constData(), json.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)2;
|
|
|
|
stream << (quint8)(128 | 1); // SETUP | RAW
|
|
|
|
stream.writeRawData("ok", 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
QVariantMap request;
|
|
|
|
request["command"] = "addfiles";
|
|
|
|
QVariantMap file;
|
|
|
|
file["url"] = "http://data.clementine-player.org/rainymood";
|
|
|
|
file["mtime"] = 123456;
|
|
|
|
file["size"] = 1e7;
|
|
|
|
file["hash"] = "abcdefg";
|
|
|
|
file["mimetype"] = "audio/mpeg";
|
|
|
|
file["duration"] = 1000;
|
|
|
|
file["bitrate"] = 128;
|
|
|
|
file["artist"] = "foo";
|
|
|
|
file["album"] = "bar";
|
|
|
|
file["track"] = "Rain!";
|
|
|
|
file["albumpos"] = 1;
|
|
|
|
file["year"] = 2011;
|
|
|
|
QVariantList files;
|
|
|
|
files << file;
|
|
|
|
request["files"] = files;
|
|
|
|
|
|
|
|
QJson::Serializer serialiser;
|
|
|
|
const QByteArray& json = serialiser.serialize(request);
|
|
|
|
|
|
|
|
qDebug() << json;
|
|
|
|
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)json.length();
|
|
|
|
stream << (quint8)(16 | 2); // DBOP | JSON
|
|
|
|
stream.writeRawData(json.constData(), json.length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkDBReadyRead() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
|
|
|
Q_ASSERT(socket);
|
|
|
|
|
|
|
|
TomahawkConnection* connection = tomahawk_connections_[socket];
|
|
|
|
if (connection->num_bytes == -1) {
|
|
|
|
// Expecting header.
|
|
|
|
if (socket->bytesAvailable() >= 5) {
|
|
|
|
QDataStream stream(socket);
|
|
|
|
quint32 size;
|
|
|
|
stream >> size;
|
|
|
|
quint8 flags;
|
|
|
|
stream >> flags;
|
|
|
|
connection->num_bytes = size > 0 ? size : -1;
|
|
|
|
connection->flags = flags;
|
|
|
|
if (flags & 32) {
|
|
|
|
qDebug() << "PING!";
|
|
|
|
}
|
|
|
|
if (connection->num_bytes == -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (socket->bytesAvailable() < connection->num_bytes) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray data = socket->readAll();
|
|
|
|
connection->num_bytes = -1;
|
|
|
|
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
qDebug() << data;
|
|
|
|
qDebug() << Q_FUNC_INFO << "done";
|
|
|
|
|
|
|
|
if (connection->flags & 2) {
|
|
|
|
QJson::Parser parser;
|
|
|
|
bool ok = false;
|
|
|
|
QVariant json = parser.parse(data, &ok);
|
|
|
|
if (ok) {
|
|
|
|
QVariantMap map = json.toMap();
|
|
|
|
qDebug() << map;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkDBDisconnected() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkDisconnected() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkError(QAbstractSocket::SocketError error) {
|
|
|
|
qDebug() << Q_FUNC_INFO << error;
|
|
|
|
}
|
2011-03-13 19:20:37 +01:00
|
|
|
|
|
|
|
void Remote::TomahawkTransferConnected() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
|
|
|
Q_ASSERT(socket);
|
|
|
|
|
|
|
|
TomahawkConnection* connection = tomahawk_connections_[socket];
|
|
|
|
|
|
|
|
{
|
|
|
|
QVariantMap request;
|
|
|
|
request["method"] = "accept-offer";
|
|
|
|
request["key"] = connection->key;
|
|
|
|
|
|
|
|
QJson::Serializer serialiser;
|
|
|
|
const QByteArray& json = serialiser.serialize(request);
|
|
|
|
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)json.length();
|
|
|
|
stream << (quint8)(2);
|
|
|
|
stream.writeRawData(json.constData(), json.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
QDataStream stream(socket);
|
|
|
|
stream << (quint32)2;
|
|
|
|
stream << (quint8)(128 | 1); // SETUP | RAW
|
|
|
|
stream.writeRawData("ok", 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkTransferDisconnected() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Remote::TomahawkTransferReadyRead() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
}
|