From ce65c95580af1bbf7ff0c2746a1ef4b4c895869d Mon Sep 17 00:00:00 2001 From: John Maguire Date: Thu, 30 Dec 2010 13:03:36 +0000 Subject: [PATCH] Beginnings of remote control support. --- src/CMakeLists.txt | 23 ++++++---- src/main.cpp | 36 ++++++++++----- src/remote/bonjour.h | 18 ++++++++ src/remote/bonjour.mm | 84 +++++++++++++++++++++++++++++++++++ src/remote/httpconnection.cpp | 72 ++++++++++++++++++++++++++++++ src/remote/httpconnection.h | 35 +++++++++++++++ src/remote/httpserver.cpp | 28 ++++++++++++ src/remote/httpserver.h | 20 +++++++++ src/remote/zeroconf.cpp | 17 +++++++ src/remote/zeroconf.h | 17 +++++++ 10 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 src/remote/bonjour.h create mode 100644 src/remote/bonjour.mm create mode 100644 src/remote/httpconnection.cpp create mode 100644 src/remote/httpconnection.h create mode 100644 src/remote/httpserver.cpp create mode 100644 src/remote/httpserver.h create mode 100644 src/remote/zeroconf.cpp create mode 100644 src/remote/zeroconf.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 868ed84af..4783a1950 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -147,6 +147,10 @@ set(SOURCES radio/savedradio.cpp radio/somafmservice.cpp + remote/httpserver.cpp + remote/httpconnection.cpp + remote/zeroconf.cpp + smartplaylists/generator.cpp smartplaylists/generatorinserter.cpp smartplaylists/querygenerator.cpp @@ -314,6 +318,9 @@ set(HEADERS radio/savedradio.h radio/somafmservice.h + remote/httpserver.h + remote/httpconnection.h + smartplaylists/generator.h smartplaylists/generatorinserter.h smartplaylists/generatormimedata.h @@ -513,19 +520,19 @@ if(HAVE_LIBLASTFM) ) endif(HAVE_LIBLASTFM) -# OSDs if(APPLE) - list(APPEND SOURCES widgets/osd_mac.mm) - list(APPEND SOURCES core/macglobalshortcutbackend.mm) - list(APPEND SOURCES devices/macdevicelister.mm) - list(APPEND HEADERS devices/macdevicelister.h) - list(APPEND SOURCES ui/macsystemtrayicon.mm) list(APPEND HEADERS core/macglobalshortcutbackend.h) + list(APPEND HEADERS devices/macdevicelister.h) + list(APPEND HEADERS ui/macscreensaver.h) list(APPEND HEADERS ui/macsystemtrayicon.h) list(APPEND HEADERS widgets/maclineedit.h) - list(APPEND SOURCES widgets/maclineedit.mm) + list(APPEND SOURCES core/macglobalshortcutbackend.mm) + list(APPEND SOURCES devices/macdevicelister.mm) + list(APPEND SOURCES remote/bonjour.mm) list(APPEND SOURCES ui/macscreensaver.cpp) - list(APPEND HEADERS ui/macscreensaver.h) + list(APPEND SOURCES ui/macsystemtrayicon.mm) + list(APPEND SOURCES widgets/maclineedit.mm) + list(APPEND SOURCES widgets/osd_mac.mm) include_directories(${GROWL}/Headers) else(APPLE) if(WIN32) diff --git a/src/main.cpp b/src/main.cpp index 04231115d..a65bcfcdc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,8 @@ #include "engines/enginebase.h" #include "library/directory.h" #include "playlist/playlist.h" +#include "remote/httpserver.h" +#include "remote/zeroconf.h" #include "smartplaylists/generator.h" #include "ui/equalizer.h" #include "ui/iconloader.h" @@ -111,21 +113,23 @@ int main(int argc, char *argv[]) { // This must go before QApplication initialisation. mac::MacMain(); - // Bump the soft limit for the number of file descriptors from the default of 256 to - // the maximum (usually 10240). - struct rlimit limit; - getrlimit(RLIMIT_NOFILE, &limit); + { + // Bump the soft limit for the number of file descriptors from the default of 256 to + // the maximum (usually 10240). + struct rlimit limit; + getrlimit(RLIMIT_NOFILE, &limit); - // getrlimit() lies about the hard limit so we have to check sysctl. - int max_fd = 0; - size_t len = sizeof(max_fd); - sysctlbyname("kern.maxfilesperproc", &max_fd, &len, NULL, 0); + // getrlimit() lies about the hard limit so we have to check sysctl. + int max_fd = 0; + size_t len = sizeof(max_fd); + sysctlbyname("kern.maxfilesperproc", &max_fd, &len, NULL, 0); - limit.rlim_cur = max_fd; - int ret = setrlimit(RLIMIT_NOFILE, &limit); + limit.rlim_cur = max_fd; + int ret = setrlimit(RLIMIT_NOFILE, &limit); - if (ret == 0) { - qDebug() << "Max fd:" << max_fd; + if (ret == 0) { + qDebug() << "Max fd:" << max_fd; + } } #endif @@ -269,6 +273,14 @@ int main(int argc, char *argv[]) { // Seed the random number generator srand(time(NULL)); + Zeroconf* zeroconf = Zeroconf::GetZeroconf(); + if (zeroconf) { + HttpServer* server = new HttpServer; + server->Listen(QHostAddress::Any, 12345); + + zeroconf->Publish("local", "_clementine._tcp", "Clementine", 12345); + } + // Window MainWindow w; diff --git a/src/remote/bonjour.h b/src/remote/bonjour.h new file mode 100644 index 000000000..41fccac4c --- /dev/null +++ b/src/remote/bonjour.h @@ -0,0 +1,18 @@ +#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 diff --git a/src/remote/bonjour.mm b/src/remote/bonjour.mm new file mode 100644 index 000000000..448f1ce1e --- /dev/null +++ b/src/remote/bonjour.mm @@ -0,0 +1,84 @@ +#include "bonjour.h" + +#include + +#import + +@interface NetServicePublicationDelegate : NSObject { + 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]; + } +} diff --git a/src/remote/httpconnection.cpp b/src/remote/httpconnection.cpp new file mode 100644 index 000000000..4c8569f6b --- /dev/null +++ b/src/remote/httpconnection.cpp @@ -0,0 +1,72 @@ +#include "httpconnection.h" + +#include +#include + +HttpConnection::HttpConnection(QTcpSocket* socket) + : QObject(socket), + socket_(socket), + state_(WaitingForRequest) { + connect(socket_, SIGNAL(readyRead()), SLOT(ReadyRead())); +} + +void HttpConnection::ReadyRead() { + switch (state_) { + case WaitingForRequest: + if (socket_->canReadLine()) { + QString line = socket_->readLine(); + if (ParseRequest(line)) { + state_ = WaitingForHeaders; + } else { + state_ = Error; + } + } + break; + case WaitingForHeaders: + while (socket_->canReadLine()) { + QByteArray line = socket_->readLine(); + if (line == "\r\n") { + DoRequest(); + state_ = Finished; + } else { + // TODO: Parse headers. + } + } + break; + + default: + break; + } + + if (state_ == Error) { + socket_->close(); + return; + } + + if (socket_->canReadLine()) { + ReadyRead(); + } +} + +bool HttpConnection::ParseRequest(const QString& line) { + QStringList tokens = line.split(' ', QString::SkipEmptyParts); + if (tokens.length() != 3) { + return false; + } + const QString& method = tokens[0]; + if (method != "GET") { + return false; + } + + method_ = QNetworkAccessManager::GetOperation; + path_ = tokens[1]; + return true; +} + +void HttpConnection::DoRequest() { + socket_->write("HTTP/1.0 200 OK\r\n"); + socket_->write("Content-type: application/json\r\n"); + socket_->write("\r\n"); + socket_->write("{content:'Hello World!'}"); + socket_->close(); +} diff --git a/src/remote/httpconnection.h b/src/remote/httpconnection.h new file mode 100644 index 000000000..e82981071 --- /dev/null +++ b/src/remote/httpconnection.h @@ -0,0 +1,35 @@ +#ifndef HTTPCONNECTION_H +#define HTTPCONNECTION_H + +#include +#include + +class QTcpSocket; + +class HttpConnection : public QObject { + Q_OBJECT + public: + HttpConnection(QTcpSocket* sock); + + private slots: + void ReadyRead(); + + private: + void DoRequest(); + bool ParseRequest(const QString& line); + + QTcpSocket* socket_; + + enum State { + WaitingForRequest, + WaitingForHeaders, + WaitingForRequestFinish, + Finished, + Error, + } state_; + + QNetworkAccessManager::Operation method_; + QString path_; +}; + +#endif diff --git a/src/remote/httpserver.cpp b/src/remote/httpserver.cpp new file mode 100644 index 000000000..ce3a05a0e --- /dev/null +++ b/src/remote/httpserver.cpp @@ -0,0 +1,28 @@ +#include "httpserver.h" + +#include + +#include "httpconnection.h" + +HttpServer::HttpServer(QObject* parent) + : QObject(parent) { + connect(&server_, SIGNAL(newConnection()), SLOT(NewConnection())); +} + +bool HttpServer::Listen(const QHostAddress& addr, quint16 port) { + return server_.listen(addr, port); +} + +void HttpServer::NewConnection() { + qDebug() << Q_FUNC_INFO; + QTcpSocket* socket = server_.nextPendingConnection(); + HttpConnection* conn = new HttpConnection(socket); +} + +void HttpServer::ReadyRead() { + QTcpSocket* socket = qobject_cast(sender()); + Q_ASSERT(socket); + while (socket->canReadLine()) { + QByteArray line = socket->readLine(); + } +} diff --git a/src/remote/httpserver.h b/src/remote/httpserver.h new file mode 100644 index 000000000..88affffde --- /dev/null +++ b/src/remote/httpserver.h @@ -0,0 +1,20 @@ +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +#include + +class HttpServer : public QObject { + Q_OBJECT + public: + HttpServer(QObject* parent = 0); + bool Listen(const QHostAddress& addr, quint16 port); + + private slots: + void NewConnection(); + void ReadyRead(); + + private: + QTcpServer server_; +}; + +#endif diff --git a/src/remote/zeroconf.cpp b/src/remote/zeroconf.cpp new file mode 100644 index 000000000..2a95c895a --- /dev/null +++ b/src/remote/zeroconf.cpp @@ -0,0 +1,17 @@ +#include "zeroconf.h" + +#ifdef Q_OS_DARWIN +#include "bonjour.h" +#endif + +Zeroconf* Zeroconf::instance_ = NULL; + +Zeroconf* Zeroconf::GetZeroconf() { + if (!instance_) { + #ifdef Q_OS_DARWIN + return new Bonjour(); + #endif + } + + return instance_; +} diff --git a/src/remote/zeroconf.h b/src/remote/zeroconf.h new file mode 100644 index 000000000..c60c16109 --- /dev/null +++ b/src/remote/zeroconf.h @@ -0,0 +1,17 @@ +#ifndef ZEROCONF_H +#define ZEROCONF_H + +#include + +class Zeroconf { + public: + virtual void Publish( + const QString& domain, const QString& type, const QString& name, quint16 port) = 0; + + static Zeroconf* GetZeroconf(); + + private: + static Zeroconf* instance_; +}; + +#endif