mirror of
https://github.com/clementine-player/Clementine
synced 2025-02-01 20:06:53 +01:00
Merge branch 'master' of https://code.google.com/r/asfa194-clementineremote
This commit is contained in:
commit
53608665e4
@ -5,13 +5,14 @@ enum MsgType {
|
||||
UNKNOWN = 0;
|
||||
// Messages generally send from client to server
|
||||
CONNECT = 1;
|
||||
DISCONNECT = 2;
|
||||
REQUEST_PLAYLISTS = 3;
|
||||
REQUEST_PLAYLIST_SONGS = 4;
|
||||
CHANGE_SONG = 5;
|
||||
SET_VOLUME = 6;
|
||||
SET_TRACK_POSITION = 7;
|
||||
|
||||
// Messages send by both
|
||||
DISCONNECT = 2;
|
||||
PLAY = 20;
|
||||
PLAYPAUSE = 21;
|
||||
PAUSE = 22;
|
||||
@ -146,20 +147,45 @@ message ResponseUpdateTrackPosition {
|
||||
optional int32 position = 1;
|
||||
}
|
||||
|
||||
// The connect message containing the authentication code
|
||||
message RequestConnect {
|
||||
optional int32 auth_code = 1;
|
||||
}
|
||||
|
||||
// Respone, why the connection was closed
|
||||
enum ReasonDisconnect {
|
||||
Server_Shutdown = 1;
|
||||
Wrong_Auth_Code = 2;
|
||||
}
|
||||
message ResponseDisconnect {
|
||||
optional ReasonDisconnect reason_disconnect = 1;
|
||||
}
|
||||
|
||||
// A client requests a new track position
|
||||
// position in seconds!
|
||||
message RequestSetTrackPosition {
|
||||
optional int32 position = 1;
|
||||
}
|
||||
|
||||
// The message itself
|
||||
message Message {
|
||||
optional int32 version = 1 [default=2];
|
||||
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
|
||||
|
||||
optional RequestConnect request_connect = 21;
|
||||
optional RequestPlaylistSongs request_playlist_songs = 10;
|
||||
optional RequestChangeSong request_change_song = 11;
|
||||
optional RequestSetVolume request_set_volume = 12;
|
||||
optional RequestSetTrackPosition request_set_track_position = 23;
|
||||
|
||||
optional Repeat repeat = 13;
|
||||
optional Shuffle shuffle = 14;
|
||||
|
||||
optional ResponseClementineInfo response_clementine_info = 15;
|
||||
optional ResponseCurrentMetadata response_current_metadata = 16;
|
||||
optional ResponsePlaylists response_playlists = 17;
|
||||
optional ResponsePlaylistSongs response_playlist_songs = 18;
|
||||
optional ResponseEngineStateChanged response_engine_state_changed = 19;
|
||||
optional ResponseUpdateTrackPosition response_update_track_position = 20;
|
||||
optional ResponseDisconnect response_disconnect = 22;
|
||||
}
|
||||
|
10
src/main.cpp
10
src/main.cpp
@ -104,6 +104,10 @@ using boost::scoped_ptr;
|
||||
# include "devices/wmdmthread.h"
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_WIN32
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
// Load sqlite plugin on windows and mac.
|
||||
#ifdef HAVE_STATIC_SQLITE
|
||||
# include <QtPlugin>
|
||||
@ -406,6 +410,12 @@ int main(int argc, char *argv[]) {
|
||||
qtsparkle::LoadTranslations(language);
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_WIN32
|
||||
// This is needed to prevent SIGPIPE Errors, which occur under some
|
||||
// circumstances in RemoteClient. They cause a program termination.
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
// Icons
|
||||
IconLoader::Init();
|
||||
|
||||
|
@ -42,6 +42,8 @@ IncomingDataParser::IncomingDataParser(Application* app)
|
||||
app_->player(), SLOT(SetVolume(int)));
|
||||
connect(this, SIGNAL(PlayAt(int,Engine::TrackChangeFlags,bool)),
|
||||
app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool)));
|
||||
connect(this, SIGNAL(SeekTo(int)),
|
||||
app_->player(), SLOT(SeekTo(int)));
|
||||
|
||||
// For some connects we have to wait for the playlistmanager
|
||||
// to be initialized
|
||||
@ -69,16 +71,9 @@ bool IncomingDataParser::close_connection() {
|
||||
return close_connection_;
|
||||
}
|
||||
|
||||
void IncomingDataParser::Parse(const QByteArray& data) {
|
||||
void IncomingDataParser::Parse(const pb::remote::Message& msg) {
|
||||
close_connection_ = false;
|
||||
|
||||
// Parse the incoming data
|
||||
pb::remote::Message msg;
|
||||
if (!msg.ParseFromArray(data.constData(), data.size())) {
|
||||
qLog(Info) << "Couldn't parse data";
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check what's to do
|
||||
switch (msg.type()) {
|
||||
case pb::remote::CONNECT: emit SendClementineInfo();
|
||||
@ -112,6 +107,9 @@ void IncomingDataParser::Parse(const QByteArray& data) {
|
||||
break;
|
||||
case pb::remote::SHUFFLE: SetShuffleMode(msg.shuffle());
|
||||
break;
|
||||
case pb::remote::SET_TRACK_POSITION:
|
||||
emit SeekTo(msg.request_set_track_position().position());
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public:
|
||||
bool close_connection();
|
||||
|
||||
public slots:
|
||||
void Parse(const QByteArray& pb_data);
|
||||
void Parse(const pb::remote::Message& msg);
|
||||
|
||||
signals:
|
||||
void SendClementineInfo();
|
||||
@ -34,6 +34,7 @@ signals:
|
||||
void ShuffleCurrent();
|
||||
void SetRepeatMode(PlaylistSequence::RepeatMode mode);
|
||||
void SetShuffleMode(PlaylistSequence::ShuffleMode mode);
|
||||
void SeekTo(int seconds);
|
||||
|
||||
private slots:
|
||||
void PlaylistManagerInitialized();
|
||||
|
@ -65,6 +65,10 @@ void NetworkRemote::SetupServer() {
|
||||
SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&)));
|
||||
|
||||
// Only connect the signals once
|
||||
connect(server_.get(), SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
|
||||
connect(server_ipv6_.get(), SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
|
||||
}
|
||||
|
||||
void NetworkRemote::StartServer() {
|
||||
@ -81,9 +85,6 @@ void NetworkRemote::StartServer() {
|
||||
|
||||
qLog(Info) << "Starting network remote";
|
||||
|
||||
connect(server_.get(), SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
|
||||
connect(server_ipv6_.get(), SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
|
||||
|
||||
server_->listen(QHostAddress::Any, port_);
|
||||
server_ipv6_->listen(QHostAddress::AnyIPv6, port_);
|
||||
|
||||
@ -97,6 +98,7 @@ void NetworkRemote::StartServer() {
|
||||
|
||||
void NetworkRemote::StopServer() {
|
||||
if (server_->isListening()) {
|
||||
outgoing_data_creator_.get()->DisconnectAllClients();
|
||||
server_->close();
|
||||
server_ipv6_->close();
|
||||
clients_.clear();
|
||||
@ -176,7 +178,7 @@ void NetworkRemote::CreateRemoteClient(QTcpSocket *client_socket) {
|
||||
clients_.push_back(client);
|
||||
|
||||
// Connect the signal to parse data
|
||||
connect(client, SIGNAL(Parse(QByteArray)),
|
||||
incoming_data_parser_.get(), SLOT(Parse(QByteArray)));
|
||||
connect(client, SIGNAL(Parse(pb::remote::Message)),
|
||||
incoming_data_parser_.get(), SLOT(Parse(pb::remote::Message)));
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,9 @@ void OutgoingDataCreator::SendFirstData() {
|
||||
|
||||
// then the current volume
|
||||
VolumeChanged(app_->player()->GetVolume());
|
||||
|
||||
// And the current track position
|
||||
UpdateTrackPosition();
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::CurrentSongChanged(const Song& song, const QString& uri, const QImage& img) {
|
||||
@ -340,3 +343,10 @@ void OutgoingDataCreator::UpdateTrackPosition() {
|
||||
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::DisconnectAllClients() {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::DISCONNECT);
|
||||
msg.mutable_response_disconnect()->set_reason_disconnect(pb::remote::Server_Shutdown);
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public slots:
|
||||
void SendRepeatMode(PlaylistSequence::RepeatMode mode);
|
||||
void SendShuffleMode(PlaylistSequence::ShuffleMode mode);
|
||||
void UpdateTrackPosition();
|
||||
void DisconnectAllClients();
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
|
@ -18,8 +18,10 @@
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "remoteclient.h"
|
||||
#include "networkremote.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QSettings>
|
||||
|
||||
RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
: app_(app),
|
||||
@ -32,6 +34,21 @@ RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
|
||||
// Connect to the slot IncomingData when receiving data
|
||||
connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData()));
|
||||
|
||||
// Connect the signals to see if an error occured or the client
|
||||
// was disconnected.
|
||||
connect(client, SIGNAL(disconnected()), this, SLOT(Disconnected()));
|
||||
connect(client, SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
this, SLOT(Error(QAbstractSocket::SocketError)));
|
||||
|
||||
// Check if we use auth code
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(NetworkRemote::kSettingsGroup);
|
||||
use_auth_code_ = s.value("use_auth_code", false).toBool();
|
||||
auth_code_ = s.value("auth_code", 0).toInt();
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +71,7 @@ void RemoteClient::IncomingData() {
|
||||
// Did we get everything?
|
||||
if (buffer_.size() == expected_length_) {
|
||||
// Parse the message
|
||||
emit Parse(buffer_.data());
|
||||
ParseMessage(buffer_.data());
|
||||
|
||||
// Clear the buffer
|
||||
buffer_.close();
|
||||
@ -65,19 +82,61 @@ void RemoteClient::IncomingData() {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::ParseMessage(const QByteArray &data) {
|
||||
pb::remote::Message msg;
|
||||
if (!msg.ParseFromArray(data.constData(), data.size())) {
|
||||
qLog(Info) << "Couldn't parse data";
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type() == pb::remote::CONNECT && use_auth_code_) {
|
||||
if (msg.request_connect().auth_code() != auth_code_) {
|
||||
DisconnectClient();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now parse the other data
|
||||
emit Parse(msg);
|
||||
}
|
||||
|
||||
void RemoteClient::DisconnectClient() {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::DISCONNECT);
|
||||
msg.mutable_response_disconnect()->set_reason_disconnect(pb::remote::Wrong_Auth_Code);
|
||||
SendData(&msg);
|
||||
|
||||
// Just close the connection. The next time the outgoing data creator
|
||||
// sends a keep alive, the client will be deleted
|
||||
client_->close();
|
||||
}
|
||||
|
||||
void RemoteClient::SendData(pb::remote::Message *msg) {
|
||||
// Serialize the message
|
||||
std::string data = msg->SerializeAsString();
|
||||
|
||||
// write the length of the data first
|
||||
if (client_->isWritable()) {
|
||||
QDataStream s(client_);
|
||||
s << qint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
|
||||
// Flush data
|
||||
client_->flush();
|
||||
} else {
|
||||
client_->close();
|
||||
}
|
||||
}
|
||||
|
||||
QAbstractSocket::SocketState RemoteClient::State() {
|
||||
return client_->state();
|
||||
}
|
||||
|
||||
void RemoteClient::Disconnected() {
|
||||
qLog(Info) << "Client Disconnected";
|
||||
}
|
||||
|
||||
void RemoteClient::Error(QAbstractSocket::SocketError socket_error) {
|
||||
qLog(Info) << "Client Error:" << socket_error;
|
||||
client_->close();
|
||||
}
|
||||
|
@ -21,11 +21,19 @@ private slots:
|
||||
void IncomingData();
|
||||
|
||||
signals:
|
||||
void Parse(const QByteArray& pb_data);
|
||||
void Parse(const pb::remote::Message& msg);
|
||||
|
||||
private:
|
||||
void ParseMessage(const QByteArray& data);
|
||||
void DisconnectClient();
|
||||
void Disconnected();
|
||||
void Error(QAbstractSocket::SocketError);
|
||||
|
||||
Application* app_;
|
||||
|
||||
bool use_auth_code_;
|
||||
int auth_code_;
|
||||
|
||||
QTcpSocket* client_;
|
||||
bool reading_protobuf_;
|
||||
quint32 expected_length_;
|
||||
|
@ -59,6 +59,10 @@ void NetworkRemoteSettingsPage::Load() {
|
||||
s.setValue("only_non_public_ip", true);
|
||||
}
|
||||
|
||||
// Auth Code, 5 digits
|
||||
ui_->use_auth_code->setChecked(s.value("use_auth_code", false).toBool());
|
||||
ui_->auth_code->setValue(s.value("auth_code", qrand() % 100000).toInt());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
QPixmap android_qr_code(":clementine_remote_qr.png");
|
||||
@ -72,6 +76,8 @@ void NetworkRemoteSettingsPage::Save() {
|
||||
s.setValue("port", ui_->remote_port->value());
|
||||
s.setValue("use_remote", ui_->use_remote->isChecked());
|
||||
s.setValue("only_non_public_ip", ui_->only_non_public_ip->isChecked());
|
||||
s.setValue("use_auth_code", ui_->use_auth_code->isChecked());
|
||||
s.setValue("auth_code", ui_->auth_code->value());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
|
@ -83,6 +83,33 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_auth_code">
|
||||
<property name="toolTip">
|
||||
<string>A client can connect only, if the correct code was entered.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use authentication code</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="auth_code">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -146,5 +173,21 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>use_auth_code</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>auth_code</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>137</x>
|
||||
<y>124</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>351</x>
|
||||
<y>125</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
Loading…
x
Reference in New Issue
Block a user