diff --git a/Makefile b/Makefile index 206a1f0..b268e42 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ SOURCES = \ main.cpp \ input.cpp \ gamepad.cpp \ + connectionlistener.cpp \ # Build rules generated by macros from common.mk: diff --git a/connectionlistener.cpp b/connectionlistener.cpp new file mode 100644 index 0000000..4a7c797 --- /dev/null +++ b/connectionlistener.cpp @@ -0,0 +1,49 @@ +#include "moonlight.hpp" + +#include "ppapi/c/ppb_input_event.h" + +#include "ppapi/cpp/input_event.h" +#include "ppapi/cpp/mouse_lock.h" + +void MoonlightInstance::ClStageStarting(int stage) { + pp::Var response(std::string("Starting ") + std::string(LiGetStageName(stage)) + std::string("...")); + g_Instance->PostMessage(response); +} + +void MoonlightInstance::ClStageFailed(int stage, long errorCode) { + pp::Var response(std::string("Starting ") + std::string(LiGetStageName(stage)) + std::string("failed")); + g_Instance->PostMessage(response); +} + +void MoonlightInstance::ClConnectionStarted(void) { + pp::Module::Get()->core()->CallOnMainThread(0, + g_Instance->m_CallbackFactory.NewCallback(&MoonlightInstance::OnConnectionStarted)); +} + +void MoonlightInstance::ClConnectionTerminated(long errorCode) { + pp::Module::Get()->core()->CallOnMainThread(0, + g_Instance->m_CallbackFactory.NewCallback(&MoonlightInstance::OnConnectionStopped), (uint32_t)errorCode); + + pp::Var response("Connection terminated"); + g_Instance->PostMessage(response); +} + +void MoonlightInstance::ClDisplayMessage(char* message) { + pp::Var response(message); + g_Instance->PostMessage(response); +} + +void MoonlightInstance::ClDisplayTransientMessage(char* message) { + pp::Var response(message); + g_Instance->PostMessage(response); +} + +CONNECTION_LISTENER_CALLBACKS MoonlightInstance::s_ClCallbacks = { + MoonlightInstance::ClStageStarting, + NULL, + MoonlightInstance::ClStageFailed, + MoonlightInstance::ClConnectionStarted, + MoonlightInstance::ClConnectionTerminated, + MoonlightInstance::ClDisplayMessage, + MoonlightInstance::ClDisplayTransientMessage +}; \ No newline at end of file diff --git a/input.cpp b/input.cpp index 89d953a..da9409d 100644 --- a/input.cpp +++ b/input.cpp @@ -19,12 +19,28 @@ static int ConvertPPButtonToLiButton(PP_InputEvent_MouseButton ppButton) { } } +void MoonlightInstance::DidLockMouse(int32_t result) { + m_MouseLocked = (result == PP_OK); +} + +void MoonlightInstance::MouseLockLost() { + m_MouseLocked = false; +} + bool MoonlightInstance::HandleInputEvent(const pp::InputEvent& event) { switch (event.GetType()) { case PP_INPUTEVENT_TYPE_MOUSEDOWN: { + // Lock the mouse cursor when the user clicks on the stream + if (!m_MouseLocked) { + g_Instance->LockMouse(g_Instance->m_CallbackFactory.NewCallback(&MoonlightInstance::DidLockMouse)); + + // Assume it worked until we get a callback telling us otherwise + m_MouseLocked = true; + } + pp::MouseInputEvent mouseEvent(event); - LiSendMouseButtonEvent(ConvertPPButtonToLiButton(mouseEvent.GetButton()), BUTTON_ACTION_PRESS); + LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, ConvertPPButtonToLiButton(mouseEvent.GetButton())); return true; } @@ -39,7 +55,7 @@ bool MoonlightInstance::HandleInputEvent(const pp::InputEvent& event) { case PP_INPUTEVENT_TYPE_MOUSEUP: { pp::MouseInputEvent mouseEvent(event); - LiSendMouseButtonEvent(ConvertPPButtonToLiButton(mouseEvent.GetButton()), BUTTON_ACTION_RELEASE); + LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, ConvertPPButtonToLiButton(mouseEvent.GetButton())); return true; } diff --git a/main.cpp b/main.cpp index b044d15..1d45e8a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,15 @@ #include "moonlight.hpp" +#include +#include + +#include "ppapi/cpp/input_event.h" + +static char s_Host[256]; +static STREAM_CONFIGURATION s_StreamConfig; + +MoonlightInstance* g_Instance; + MoonlightInstance::~MoonlightInstance() {} class MoonlightModule : public pp::Module { @@ -12,6 +22,81 @@ class MoonlightModule : public pp::Module { } }; +void MoonlightInstance::OnConnectionStarted(uint32_t unused) { + printf("Connection started\n"); + + // Start receiving input events + g_Instance->RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); + g_Instance->RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_WHEEL | PP_INPUTEVENT_CLASS_KEYBOARD); +} + +void MoonlightInstance::OnConnectionStopped(uint32_t error) { + // Stop receiving input events + g_Instance->ClearInputEventRequest(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL | PP_INPUTEVENT_CLASS_KEYBOARD); + + // Unlock the mouse + g_Instance->UnlockMouse(); +} + +void* MoonlightInstance::ConnectionThreadFunc(void* context) { + MoonlightInstance* me = (MoonlightInstance*)context; + int err; + + err = LiStartConnection(s_Host, + &s_StreamConfig, + &MoonlightInstance::s_ClCallbacks, + NULL, NULL, + NULL, 0, 4); + if (err != 0) { + pp::Var response("Starting connection failed"); + g_Instance->PostMessage(response); + return NULL; + } + + for (;;) { + me->PollGamepads(); + } + + return NULL; +} + +void MoonlightInstance::HandleMessage(const pp::Var& var_message) { + // Ignore the message if it is not a string. + if (!var_message.is_string()) + return; + + std::string host = var_message.AsString(); + + // Populate the stream configuration + LiInitializeStreamConfiguration(&s_StreamConfig); + s_StreamConfig.width = 1280; + s_StreamConfig.height = 720; + s_StreamConfig.fps = 30; + s_StreamConfig.bitrate = 10; // megabits per second + s_StreamConfig.packetSize = 1024; + s_StreamConfig.streamingRemotely = 0; + s_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO; + + // Store the host + host = host.substr(host.find(":") + 1); + strcpy(s_Host, host.c_str()); + + // Post a status update before we begin + pp::Var response("Starting connection..."); + PostMessage(response); + + // Start the worker thread to establish the connection + pthread_t t; + pthread_create(&t, NULL, MoonlightInstance::ConnectionThreadFunc, this); +} + +bool MoonlightInstance::Init(uint32_t argc, + const char* argn[], + const char* argv[]) { + g_Instance = this; + return true; +} + namespace pp { Module* CreateModule() { return new MoonlightModule(); diff --git a/moonlight.hpp b/moonlight.hpp index 86d0fe5..6f1b86b 100644 --- a/moonlight.hpp +++ b/moonlight.hpp @@ -1,14 +1,23 @@ #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/var.h" +#include "ppapi/cpp/mouse_lock.h" #include "ppapi/c/ppb_gamepad.h" +#include "ppapi/utility/completion_callback_factory.h" + #include "nacl_io/nacl_io.h" -class MoonlightInstance : public pp::Instance { +#include + +class MoonlightInstance : public pp::Instance, public pp::MouseLock { public: - explicit MoonlightInstance(PP_Instance instance) : pp::Instance(instance) { + explicit MoonlightInstance(PP_Instance instance) : + pp::Instance(instance), + pp::MouseLock(this), + m_CallbackFactory(this), + m_MouseLocked(false) { // This function MUST be used otherwise sockets don't work (nacl_io_init() doesn't work!) nacl_io_init_ppapi(pp_instance(), pp::Module::Get()->get_browser_interface()); @@ -17,11 +26,42 @@ class MoonlightInstance : public pp::Instance { virtual ~MoonlightInstance(); + bool Init(uint32_t argc, const char* argn[], const char* argv[]); + + void HandleMessage(const pp::Var& var_message); + bool HandleInputEvent(const pp::InputEvent& event); void PollGamepads(); + void DidLockMouse(int32_t result); + + void MouseLockLost(); + + void OnConnectionStopped(uint32_t unused); + + void OnConnectionStarted(uint32_t error); + + static void* ConnectionThreadFunc(void* context); + + static void ClStageStarting(int stage); + + static void ClStageFailed(int stage, long errorCode); + + static void ClConnectionStarted(void); + + static void ClConnectionTerminated(long errorCode); + + static void ClDisplayMessage(char* message); + + static void ClDisplayTransientMessage(char* message); + private: double m_LastPadTimestamps[4]; const PPB_Gamepad* m_GamepadApi; -}; \ No newline at end of file + static CONNECTION_LISTENER_CALLBACKS s_ClCallbacks; + pp::CompletionCallbackFactory m_CallbackFactory; + bool m_MouseLocked; +}; + +extern MoonlightInstance* g_Instance; \ No newline at end of file