#include "moonlight.hpp" #include #include #include #include #include #include "ppapi/cpp/input_event.h" #include #include #include // Requests the NaCl module to connection to the server specified after the : #define MSG_START_REQUEST "startRequest" // Requests the NaCl module stop streaming #define MSG_STOP_REQUEST "stopRequest" // Sent by the NaCl module when the stream has stopped whether user-requested or not #define MSG_STREAM_TERMINATED "streamTerminated" #define MSG_OPENURL "openUrl" MoonlightInstance* g_Instance; class MoonlightModule : public pp::Module { public: MoonlightModule() : pp::Module() {} virtual ~MoonlightModule() {} virtual pp::Instance* CreateInstance(PP_Instance instance) { return new MoonlightInstance(instance); } }; void MoonlightInstance::OnConnectionStarted(uint32_t unused) { // Tell the front end pp::Var response("Connection Established"); PostMessage(response); // Start receiving input events RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); // Filtering is suboptimal but it ensures that we can pass keyboard events // to the browser when mouse lock is disabled. This is neccessary for Esc // to kick the app out of full-screen. RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); } void MoonlightInstance::OnConnectionStopped(uint32_t error) { // Not running anymore m_Running = false; // Stop receiving input events ClearInputEventRequest(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL | PP_INPUTEVENT_CLASS_KEYBOARD); // Unlock the mouse UnlockMouse(); // Notify the JS code that the stream has ended pp::Var response(MSG_STREAM_TERMINATED); PostMessage(response); } void MoonlightInstance::StopConnection() { pthread_t t; // Stopping needs to happen in a separate thread to avoid a potential deadlock // caused by us getting a callback to the main thread while inside LiStopConnection. pthread_create(&t, NULL, MoonlightInstance::StopThreadFunc, NULL); // We'll need to call the listener ourselves since our connection terminated callback // won't be invoked for a manually requested termination. OnConnectionStopped(0); } void* MoonlightInstance::StopThreadFunc(void* context) { // We must join the connection thread first, because LiStopConnection must // not be invoked during LiStartConnection. pthread_join(g_Instance->m_ConnectionThread, NULL); // Force raise all modifier keys to avoid leaving them down after disconnecting LiSendKeyboardEvent(0xA0, KEY_ACTION_UP, 0); LiSendKeyboardEvent(0xA1, KEY_ACTION_UP, 0); LiSendKeyboardEvent(0xA2, KEY_ACTION_UP, 0); LiSendKeyboardEvent(0xA3, KEY_ACTION_UP, 0); LiSendKeyboardEvent(0xA4, KEY_ACTION_UP, 0); LiSendKeyboardEvent(0xA5, KEY_ACTION_UP, 0); // Not running anymore g_Instance->m_Running = false; // We also need to stop this thread after the connection thread, because it depends // on being initialized there. pthread_join(g_Instance->m_InputThread, NULL); // Stop the connection LiStopConnection(); return NULL; } void* MoonlightInstance::InputThreadFunc(void* context) { MoonlightInstance* me = (MoonlightInstance*)context; while (me->m_Running) { me->PollGamepads(); me->ReportMouseMovement(); // Poll every 5 ms usleep(5 * 1000); } return NULL; } void* MoonlightInstance::ConnectionThreadFunc(void* context) { MoonlightInstance* me = (MoonlightInstance*)context; int err; SERVER_INFORMATION serverInfo; // Post a status update before we begin pp::Var response("Starting connection to " + me->m_Host); me->PostMessage(response); LiInitializeServerInformation(&serverInfo); serverInfo.address = me->m_Host.c_str(); serverInfo.serverInfoAppVersion = me->m_AppVersion.c_str(); err = LiStartConnection(&serverInfo, &me->m_StreamConfig, &MoonlightInstance::s_ClCallbacks, &MoonlightInstance::s_DrCallbacks, &MoonlightInstance::s_ArCallbacks, NULL, 0, NULL, 0); if (err != 0) { // Notify the JS code that the stream has ended pp::Var response(MSG_STREAM_TERMINATED); me->PostMessage(response); return NULL; } // Set running state before starting connection-specific threads me->m_Running = true; pthread_create(&me->m_InputThread, NULL, MoonlightInstance::InputThreadFunc, me); return NULL; } // hook from javascript into the CPP code. void MoonlightInstance::HandleMessage(const pp::Var& var_message) { // Ignore the message if it is not a string. if (!var_message.is_dictionary()) return; pp::VarDictionary msg(var_message); int32_t callbackId = msg.Get("callbackId").AsInt(); std::string method = msg.Get("method").AsString(); pp::VarArray params(msg.Get("params")); if (strcmp(method.c_str(), MSG_START_REQUEST) == 0) { HandleStartStream(callbackId, params); } else if (strcmp(method.c_str(), MSG_STOP_REQUEST) == 0) { HandleStopStream(callbackId, params); } else if (strcmp(method.c_str(), MSG_OPENURL) == 0) { HandleOpenURL(callbackId, params); } else if (strcmp(method.c_str(), "httpInit") == 0) { NvHTTPInit(callbackId, params); } else if (strcmp(method.c_str(), "makeCert") == 0) { MakeCert(callbackId, params); } else if (strcmp(method.c_str(), "pair") == 0) { HandlePair(callbackId, params); } else if (strcmp(method.c_str(), "STUN") == 0) { HandleSTUN(callbackId, params); } else { pp::Var response("Unhandled message received: " + method); PostMessage(response); } } static void hexStringToBytes(const char* str, char* output) { for (int i = 0; i < strlen(str); i += 2) { sscanf(&str[i], "%2hhx", &output[i / 2]); } } void MoonlightInstance::HandleStartStream(int32_t callbackId, pp::VarArray args) { std::string host = args.Get(0).AsString(); std::string width = args.Get(1).AsString(); std::string height = args.Get(2).AsString(); std::string fps = args.Get(3).AsString(); std::string bitrate = args.Get(4).AsString(); std::string rikey = args.Get(5).AsString(); std::string rikeyid = args.Get(6).AsString(); std::string appversion = args.Get(7).AsString(); pp::Var response("Setting stream width to: " + width); PostMessage(response); response = ("Setting stream height to: " + height); PostMessage(response); response = ("Setting stream fps to: " + fps); PostMessage(response); response = ("Setting stream host to: " + host); PostMessage(response); response = ("Setting stream bitrate to: " + bitrate); PostMessage(response); response = ("Setting rikey to: " + rikey); PostMessage(response); response = ("Setting rikeyid to: " + rikeyid); PostMessage(response); response = ("Setting appversion to: " + appversion); PostMessage(response); // Populate the stream configuration LiInitializeStreamConfiguration(&m_StreamConfig); m_StreamConfig.width = stoi(width); m_StreamConfig.height = stoi(height); m_StreamConfig.fps = stoi(fps); m_StreamConfig.bitrate = stoi(bitrate); // kilobits per second m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO; m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO; m_StreamConfig.packetSize = 1392; // Load the rikey and rikeyid into the stream configuration hexStringToBytes(rikey.c_str(), m_StreamConfig.remoteInputAesKey); int rikeyiv = htonl(stoi(rikeyid)); memcpy(m_StreamConfig.remoteInputAesIv, &rikeyiv, sizeof(rikeyiv)); // Store the parameters from the start message m_Host = host; m_AppVersion = appversion; // Initialize the rendering surface before starting the connection if (InitializeRenderingSurface(m_StreamConfig.width, m_StreamConfig.height)) { // Start the worker thread to establish the connection pthread_create(&m_ConnectionThread, NULL, MoonlightInstance::ConnectionThreadFunc, this); } else { // Failed to initialize renderer OnConnectionStopped(0); } pp::VarDictionary ret; ret.Set("callbackId", pp::Var(callbackId)); ret.Set("type", pp::Var("resolve")); ret.Set("ret", pp::VarDictionary()); PostMessage(ret); } void MoonlightInstance::HandleStopStream(int32_t callbackId, pp::VarArray args) { // Begin connection teardown StopConnection(); pp::VarDictionary ret; ret.Set("callbackId", pp::Var(callbackId)); ret.Set("type", pp::Var("resolve")); ret.Set("ret", pp::VarDictionary()); PostMessage(ret); } void MoonlightInstance::HandleOpenURL(int32_t callbackId, pp::VarArray args) { m_HttpThreadPool[m_HttpThreadPoolSequence++ % HTTP_HANDLER_THREADS]->message_loop().PostWork( m_CallbackFactory.NewCallback(&MoonlightInstance::NvHTTPRequest, callbackId, args)); } void MoonlightInstance::HandlePair(int32_t callbackId, pp::VarArray args) { m_HttpThreadPool[m_HttpThreadPoolSequence++ % HTTP_HANDLER_THREADS]->message_loop().PostWork( m_CallbackFactory.NewCallback(&MoonlightInstance::PairCallback, callbackId, args)); } void MoonlightInstance::PairCallback(int32_t /*result*/, int32_t callbackId, pp::VarArray args) { char* ppkstr; int err = gs_pair(atoi(args.Get(0).AsString().c_str()), args.Get(1).AsString().c_str(), args.Get(2).AsString().c_str(), &ppkstr); pp::VarDictionary ret; ret.Set("callbackId", pp::Var(callbackId)); if (err == 0) { ret.Set("type", pp::Var("resolve")); ret.Set("ret", pp::Var(ppkstr)); free(ppkstr); } else { ret.Set("type", pp::Var("reject")); ret.Set("ret", pp::Var(err)); } PostMessage(ret); } void MoonlightInstance::HandleSTUN(int32_t callbackId, pp::VarArray args) { m_HttpThreadPool[m_HttpThreadPoolSequence++ % HTTP_HANDLER_THREADS]->message_loop().PostWork( m_CallbackFactory.NewCallback(&MoonlightInstance::STUNCallback, callbackId, args)); } void MoonlightInstance::STUNCallback(int32_t /*result*/, int32_t callbackId, pp::VarArray args) { unsigned int wanAddr; char addrStr[128] = {}; pp::VarDictionary ret; ret.Set("callbackId", pp::Var(callbackId)); ret.Set("type", pp::Var("resolve")); if (LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &wanAddr) == 0) { inet_ntop(AF_INET, &wanAddr, addrStr, sizeof(addrStr)); ret.Set("ret", pp::Var(addrStr)); } else { ret.Set("ret", pp::Var()); } PostMessage(ret); } bool MoonlightInstance::Init(uint32_t argc, const char* argn[], const char* argv[]) { g_Instance = this; return true; } namespace pp { Module* CreateModule() { return new MoonlightModule(); } } // namespace pp