From c320fc0922c6dea7df06b682f067661540b52466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rgen=20Persson?= Date: Thu, 19 Jan 2023 18:15:57 +0100 Subject: [PATCH] - Support for newest firmware, - NVIDIA Gamestream, Steam Link, Sunshine and RDP - Fixed a bug where the fullscreen detection for user idle mode was triggered by the NVIDIA GFE overlay sometimes - Improved logic for the monitor topology feature --- .../LGTV Companion Service.vcxproj | 12 +- LGTV Companion Service/Service.cpp | 65 ++- LGTV Companion Service/Service.h | 18 +- LGTV Companion Service/Session.cpp | 466 ++++++++++++++---- LGTV Companion Setup/Product.wxs | 2 +- LGTV Companion UI/LGTV Companion UI.cpp | 284 ++++++----- LGTV Companion UI/LGTV Companion UI.h | 9 +- LGTV Companion UI/LGTV Companion UI.rc | Bin 39346 -> 40778 bytes LGTV Companion UI/resource.h | 7 +- LGTV Companion User/Daemon.cpp | 418 +++++++++++----- LGTV Companion User/Daemon.h | 78 ++- LGTV Companion User/LGTV Companion User.rc | 2 +- 12 files changed, 942 insertions(+), 419 deletions(-) diff --git a/LGTV Companion Service/LGTV Companion Service.vcxproj b/LGTV Companion Service/LGTV Companion Service.vcxproj index 7eedf4a7..b8bf1e03 100644 --- a/LGTV Companion Service/LGTV Companion Service.vcxproj +++ b/LGTV Companion Service/LGTV Companion Service.vcxproj @@ -118,14 +118,14 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - C:\Users\jorge\Programs\boost_1_75_0;%(AdditionalIncludeDirectories) + C:\Users\jorge\Programs\boost_1_75_0;C:\Users\jorge\Programs\OpenSSL-Win64\include;%(AdditionalIncludeDirectories) MultiThreadedDebug Console true - %(AdditionalDependencies) - C:\Users\jorge\Programs\boost_1_75_0\lib64-msvc-14.2;%(AdditionalLibraryDirectories) + C:\Users\jorge\Programs\OpenSSL-Win64\lib\VC\static\libcrypto64MTd.lib;C:\Users\jorge\Programs\OpenSSL-Win64\lib\VC\static\libssl64MTd.lib;crypt32.lib;%(AdditionalDependencies) + C:\Users\jorge\Programs\OpenSSL-Win64\lib;C:\Users\jorge\Programs\boost_1_75_0\lib64-msvc-14.2;%(AdditionalLibraryDirectories) @@ -136,7 +136,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - C:\Users\jorge\Programs\boost_1_75_0;%(AdditionalIncludeDirectories) + C:\Users\jorge\Programs\boost_1_75_0;C:\Users\jorge\Programs\OpenSSL-Win64\include;%(AdditionalIncludeDirectories) MultiThreaded @@ -144,8 +144,8 @@ true true true - %(AdditionalDependencies) - C:\Users\jorge\Programs\boost_1_75_0\lib64-msvc-14.2;%(AdditionalLibraryDirectories) + C:\Users\jorge\Programs\OpenSSL-Win64\lib\VC\static\libcrypto64MT.lib;C:\Users\jorge\Programs\OpenSSL-Win64\lib\VC\static\libssl64MT.lib;crypt32.lib;%(AdditionalDependencies) + C:\Users\jorge\Programs\OpenSSL-Win64\lib;C:\Users\jorge\Programs\boost_1_75_0\lib64-msvc-14.2;%(AdditionalLibraryDirectories) diff --git a/LGTV Companion Service/Service.cpp b/LGTV Companion Service/Service.cpp index a086016d..40babeff 100644 --- a/LGTV Companion Service/Service.cpp +++ b/LGTV Companion Service/Service.cpp @@ -1,6 +1,7 @@ // See LGTV Companion UI.cpp for additional details #include "Service.h" +#include using namespace std; using json = nlohmann::json; @@ -697,9 +698,9 @@ bool ReadConfigFile() if (!j.empty() && j.is_number()) Prefs.BlankScreenWhenIdleDelay = j.get(); - j = jsonPrefs[JSON_PREFS_NODE][JSON_RDP_POWEROFF]; + j = jsonPrefs[JSON_PREFS_NODE][JSON_TOPOLOGYMODE]; if (!j.empty() && j.is_boolean()) - Prefs.PowerOffDuringRDP = j.get(); + Prefs.TopologyPreferPowerEfficiency = j.get(); Log(st); Log("Configuration file successfully read"); @@ -744,6 +745,10 @@ void InitDeviceSessions() if (item.value()["Enabled"].is_boolean()) params.Enabled = item.value()["Enabled"].get(); s << "Enabled:" << (params.Enabled ? "yes" : "no") << ", "; + //NewConnectionMethod + if (item.value()["NewSockConnect"].is_boolean()) + params.SSL = item.value()["NewSockConnect"].get(); + s << "NewConn:" << (params.SSL ? "yes" : "no") << ", "; //SUBNET AND WOL TYPE if (item.value()["Subnet"].is_string()) params.Subnet = item.value()["Subnet"].get(); @@ -1086,50 +1091,30 @@ void IPCThread(void) } DispatchSystemPowerEvent(SYSTEM_EVENT_USERIDLE); } - else if (param == "remoteconnect_busy") + else if (param == "remote_connect") { - if (Prefs.PowerOffDuringRDP) - { - Log("IPC, Remote session connected. User idle management disabled, Powering off managed displays."); - DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYOFF); - } - else - Log("IPC, Remote session connected. User idle management disabled."); - } - else if (param == "remoteconnect_idle") - { - if (Prefs.PowerOffDuringRDP) - { - Log("IPC, Remote session connected. User idle management disabled. Powering off managed displays."); + Log("IPC, Remote streaming client connected. Managed devices will power off."); DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYOFF); - } - else - { - Log("IPC, Remote session connected. User idle management disabled"); - DispatchSystemPowerEvent(SYSTEM_EVENT_UNBLANK); - } + + for (auto& d : DeviceCtrlSessions) + { + d.RemoteHostConnected(); + } } - else if (param == "remoteconnect") + else if (param == "remote_disconnect") { - if (Prefs.PowerOffDuringRDP) + for (auto& d : DeviceCtrlSessions) { - Log("IPC, Remote session connected. Powering off managed displays."); - DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYOFF); + d.RemoteHostDisconnected(); } - else - Log("IPC, Remote session connected."); - } - else if (param == "remotedisconnect") - { if (Prefs.DisplayIsCurrentlyRequestedPoweredOnByWindows) { - Log("IPC, Remote session disconnected. Powering on managed displays."); + Log("IPC, Remote streaming client disconnected. Powering on managed displays"); DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYON); } else - { - Log("IPC, Remote session disconnected."); - } + Log("IPC, Remote streaming client disconnected. Managed displays will remain powered off,"); + } else if (param == "topology") { @@ -1140,15 +1125,23 @@ void IPCThread(void) d.SetTopology(false); } } + else if (param == "gfe") + { + Log("IPC, NVIDIA GFE overlay fullscreen compatibility set"); + } } else if (param1 == APP_IPC_DAEMON_TOPOLOGY) { if (param == "invalid") { - Log("IPC, A recent change to the system seem to have invalidated the monitor topology configuration. " + Log("IPC, A recent change to the system have invalidated the monitor topology configuration. " "Please run the configuration guide in the global options again to ensure correct operation."); } + if (param == "undetermined") + { + Log("IPC, No active devices detected when verifying Windows Monitor Topology. Topology feature has been disabled"); + } if (param == "*") { string s; diff --git a/LGTV Companion Service/Service.h b/LGTV Companion Service/Service.h index cbd5d001..5f061006 100644 --- a/LGTV Companion Service/Service.h +++ b/LGTV Companion Service/Service.h @@ -4,6 +4,7 @@ #include #include #include +//#include #include #include #include @@ -13,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -26,6 +26,8 @@ #include #include #include +//#include + #include "nlohmann/json.hpp" #include "Handshake.h" @@ -37,10 +39,11 @@ #pragma comment(lib, "Iphlpapi.lib") #define APPNAME L"LGTV Companion" -#define APPVERSION L"1.9.0" +#define APPVERSION L"2.0.0" #define SVCNAME L"LGTVsvc" #define SVCDISPLAYNAME L"LGTV Companion Service" #define SERVICE_PORT "3000" +#define SERVICE_PORT_SSL "3001" #define JSON_PREFS_NODE "LGTV Companion" #define JSON_EVENT_RESTART_STRINGS "LocalEventLogRestartString" @@ -54,6 +57,7 @@ #define JSON_IDLEBLANKDELAY "BlankWhenIdleDelay" #define JSON_RDP_POWEROFF "PowerOffDuringRDP" #define JSON_ADHERETOPOLOGY "AdhereDisplayTopology" +#define JSON_TOPOLOGYMODE "TopologyPreferPowerEfficient" #define SERVICE_DEPENDENCIES L"Dhcp\0Dnscache\0LanmanServer\0\0" #define SERVICE_ACCOUNT NULL //L"NT AUTHORITY\\LocalService" @@ -77,8 +81,7 @@ #define SYSTEM_EVENT_USERIDLE 14 #define SYSTEM_EVENT_FORCESETHDMI 15 #define SYSTEM_EVENT_BOOT 16 -#define SYSTEM_EVENT_UNBLANK 17 -#define SYSTEM_EVENT_TOPOLOGY 18 +#define SYSTEM_EVENT_TOPOLOGY 17 #define APP_CMDLINE_ON 1 #define APP_CMDLINE_OFF 2 @@ -120,8 +123,8 @@ struct PREFS { int PowerOnTimeout = 40; bool BlankWhenIdle = false; int BlankScreenWhenIdleDelay = 10; - bool PowerOffDuringRDP = false; bool DisplayIsCurrentlyRequestedPoweredOnByWindows = false; + bool TopologyPreferPowerEfficiency = true; }; struct SESSIONPARAMETERS { @@ -140,6 +143,7 @@ struct SESSIONPARAMETERS { int BlankScreenWhenIdleDelay = 10; bool SetHDMIInputOnResume = false; int SetHDMIInputOnResumeToNumber = 1; + bool SSL = true; }; class CSession { @@ -147,6 +151,8 @@ class CSession { CSession(SESSIONPARAMETERS*); ~CSession(); void Run(); + void RemoteHostConnected(); + void RemoteHostDisconnected(); void Stop(); void SystemEvent(DWORD, int param = 0); SESSIONPARAMETERS GetParams(); @@ -166,7 +172,7 @@ class CSession { void TurnOnDisplay(bool SendWOL); void TurnOffDisplay(bool forced, bool dimmed, bool blankscreen); void SetDisplayHdmiInput(int HdmiInput); - + bool bRemoteHostConnected = false; SESSIONPARAMETERS Parameters; }; diff --git a/LGTV Companion Service/Session.cpp b/LGTV Companion Service/Session.cpp index a2afd0ba..c0a9c0b8 100644 --- a/LGTV Companion Service/Session.cpp +++ b/LGTV Companion Service/Session.cpp @@ -1,5 +1,15 @@ // See LGTV Companion UI.cpp for additional details #include "Service.h" +//#include +//#include + +#include +#include +//#include +#include +#include +#include +#include using namespace std; namespace beast = boost::beast; // from @@ -7,8 +17,11 @@ namespace http = beast::http; // from namespace websocket = beast::websocket; // from namespace net = boost::asio; // from using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from using json = nlohmann::json; + + mutex mMutex; namespace { @@ -92,6 +105,22 @@ void CSession::Run() Parameters.Enabled = true; mMutex.unlock(); } +void CSession::RemoteHostConnected() +{ + //thread safe section + while (!mMutex.try_lock()) + Sleep(MUTEX_WAIT); + bRemoteHostConnected = true; + mMutex.unlock(); +} +void CSession::RemoteHostDisconnected() +{ + //thread safe section + while (!mMutex.try_lock()) + Sleep(MUTEX_WAIT); + bRemoteHostConnected = false; + mMutex.unlock(); +} void CSession::Stop() { //thread safe section @@ -264,6 +293,7 @@ void CSession::SystemEvent(DWORD dwMsg, int param) { case SYSTEM_EVENT_REBOOT: { + bRemoteHostConnected = false; ActivePowerState = false; }break; case SYSTEM_EVENT_UNSURE: @@ -271,12 +301,15 @@ void CSession::SystemEvent(DWORD dwMsg, int param) { TurnOffDisplay(false, false, false); ActivePowerState = false; + bRemoteHostConnected = false; }break; case SYSTEM_EVENT_SUSPEND: { + bRemoteHostConnected = false; }break; case SYSTEM_EVENT_RESUME: { + bRemoteHostConnected = false; }break; case SYSTEM_EVENT_RESUMEAUTO: { @@ -290,10 +323,13 @@ void CSession::SystemEvent(DWORD dwMsg, int param) else SetDisplayHdmiInput(Parameters.SetHDMIInputOnResumeToNumber); } + bRemoteHostConnected = false; }break; case SYSTEM_EVENT_DISPLAYON: { + if (bRemoteHostConnected) + break; if (AdhereTopology) { if (TopologyEnabled) @@ -305,16 +341,22 @@ void CSession::SystemEvent(DWORD dwMsg, int param) }break; case SYSTEM_EVENT_DISPLAYOFF: { + if (bRemoteHostConnected) + break; TurnOffDisplay(false, false, false); ActivePowerState = false; }break; case SYSTEM_EVENT_DISPLAYDIMMED: { + if (bRemoteHostConnected) + break; TurnOffDisplay(false, true, false); ActivePowerState = false; }break; case SYSTEM_EVENT_USERIDLE: { + if (bRemoteHostConnected) + break; if (Parameters.BlankWhenIdle) { if (AdhereTopology) @@ -329,6 +371,8 @@ void CSession::SystemEvent(DWORD dwMsg, int param) }break; case SYSTEM_EVENT_USERBUSY: { + if (bRemoteHostConnected) + break; if (Parameters.BlankWhenIdle) { if (AdhereTopology) @@ -354,13 +398,10 @@ void CSession::SystemEvent(DWORD dwMsg, int param) SetDisplayHdmiInput(Parameters.SetHDMIInputOnResumeToNumber); } }break; - case SYSTEM_EVENT_UNBLANK: - { - TurnOnDisplay(false); - ActivePowerState = false; - }break; case SYSTEM_EVENT_TOPOLOGY: { + if (bRemoteHostConnected) + break; if (!ActivePowerState) break; if (AdhereTopology) @@ -386,6 +427,7 @@ void DisplayPowerOnThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Cal string hostip = CallingSessionParameters->IP; string key = CallingSessionParameters->SessionKey; string device = CallingSessionParameters->DeviceId; + bool SSL = CallingSessionParameters->SSL; string logmsg; string ck = "CLIENTKEYx0x0x0"; string handshake; @@ -414,35 +456,81 @@ void DisplayPowerOnThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Cal try { //BOOST::BEAST + beast::flat_buffer buffer; net::io_context ioc; - tcp::resolver resolver{ ioc }; + tcp::resolver resolver{ ioc }; + + //context holds certificates + ssl::context ctx{ ssl::context::tlsv12_client }; + //load_root_certificates(ctx); + + + //SSL + websocket::stream> wss{ ioc, ctx }; websocket::stream ws{ ioc }; - beast::flat_buffer buffer; + + string host = hostip; // try communicating with the display - auto const results = resolver.resolve(host, SERVICE_PORT); - auto ep = net::connect(ws.next_layer(), results); + auto const results = resolver.resolve(host, SSL?SERVICE_PORT_SSL:SERVICE_PORT); + auto ep = net::connect(SSL? get_lowest_layer(wss):ws.next_layer(), results); + + //SSL set SNI Hostname + if(SSL) + SSL_set_tlsext_host_name(wss.next_layer().native_handle(), host.c_str()); + + //build the host string for the decorator host += ':' + std::to_string(ep.port()); + + //SSL handshake + if(SSL) + wss.next_layer().handshake(ssl::stream_base::client); - ws.set_option(websocket::stream_base::decorator( - [](websocket::request_type& req) - { - req.set(http::field::user_agent, - std::string(BOOST_BEAST_VERSION_STRING) + - " websocket-client-LGTVsvc"); - })); - ws.handshake(host, "/"); - ws.write(net::buffer(std::string(handshake))); - ws.read(buffer); // read the first response + if (SSL) + { + wss.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-LGTVsvc"); + })); + wss.handshake(host, "/"); + wss.write(net::buffer(std::string(handshake))); + wss.read(buffer); // read the first response + } + else + { + ws.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-LGTVsvc"); + })); + ws.handshake(host, "/"); + ws.write(net::buffer(std::string(handshake))); + ws.read(buffer); // read the first response + } + + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) ON response 1: " : "[DEBUG] ON Response 1: "; + st += tt; + Log(st); + } json j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); boost::string_view check = j["type"]; + buffer.consume(buffer.size()); + // parse for pairing key if needed if (std::string(check) != "registered") { - buffer.consume(buffer.size()); + if (key != "") { logmsg = device; @@ -450,12 +538,33 @@ void DisplayPowerOnThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Cal Log(logmsg); handshake = narrow(HANDSHAKE_NOTPAIRED); - ws.write(net::buffer(std::string(handshake))); - ws.read(buffer); // read the first response + if (SSL) + { + wss.write(net::buffer(std::string(handshake))); + wss.read(buffer); // read the first response + } + else + { + ws.write(net::buffer(std::string(handshake))); + ws.read(buffer); // read the first response + } } - ws.read(buffer); //read the second response which should now contain the session key + buffer.consume(buffer.size()); + + if(SSL) + wss.read(buffer); + else + ws.read(buffer); //read the second response which should now contain the session key string t = beast::buffers_to_string(buffer.data()); + + //debuggy + string ss = SSL?"[DEBUG] (SSL) ON response key: ": "[DEBUG] ON response key: "; + ss += t; + Log(ss); + + buffer.consume(buffer.size()); + size_t u = t.find("client-key\":\""); if (u != string::npos) // so did we get a session key? { @@ -481,13 +590,44 @@ void DisplayPowerOnThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Cal } } - ws.write(net::buffer(std::string(screenonmess))); - ws.read(buffer); + if (SSL) + { + wss.write(net::buffer(std::string(screenonmess))); + wss.read(buffer); + } + else + { + ws.write(net::buffer(std::string(screenonmess))); + ws.read(buffer); + } + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) ON response 2: " : "[DEBUG] ON response 2: "; + st += tt; + Log(st); + } buffer.consume(buffer.size()); //retreive power state from device to determine if the device is powered on - ws.write(net::buffer(getpowerstatemess)); - ws.read(buffer); + if (SSL) + { + wss.write(net::buffer(getpowerstatemess)); + wss.read(buffer); + } + else + { + ws.write(net::buffer(getpowerstatemess)); + ws.read(buffer); + } + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) ON response 3: " : "[DEBUG] ON response 3: "; + st += tt; + Log(st); + } + j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); boost::string_view type = j["type"]; @@ -499,11 +639,21 @@ void DisplayPowerOnThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Cal logmsg = device; logmsg += ", power state is: ON"; Log(logmsg); - ws.close(websocket::close_code::normal); + + buffer.consume(buffer.size()); + if(SSL) + wss.close(websocket::close_code::normal); + else + ws.close(websocket::close_code::normal); break; } } buffer.consume(buffer.size()); + if (SSL) + wss.close(websocket::close_code::normal); + else + ws.close(websocket::close_code::normal); + } catch (std::exception const& e) { @@ -741,9 +891,11 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca } if (CallingSessionParameters->SessionKey != "") { - string host = CallingSessionParameters->IP; + string hostip = CallingSessionParameters->IP; string key = CallingSessionParameters->SessionKey; string device = CallingSessionParameters->DeviceId; + int hdmi_number = CallingSessionParameters->OnlyTurnOffIfCurrentHDMIInputNumberIs; + bool SSL = CallingSessionParameters->SSL; string logmsg; string getpowerstatemess = R"({"id": "1", "type": "request", "uri": "ssap://com.webos.service.tvpower/power/getPowerState"})"; string getactiveinputmess = R"({"id": "2", "type": "request", "uri": "ssap://com.webos.applicationManager/getForegroundAppInfo"})"; @@ -751,7 +903,6 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca string screenoffmess = R"({"id": "4", "type" : "request", "uri" : "ssap://com.webos.service.tvpower/power/turnOffScreen"})"; string handshake = narrow(HANDSHAKE_PAIRED); string ck = "CLIENTKEYx0x0x0"; - beast::flat_buffer buffer; json j; boost::string_view type; boost::string_view state; @@ -759,17 +910,35 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca try { + //BOOST::BEAST + beast::flat_buffer buffer; net::io_context ioc; - - size_t ckf = handshake.find(ck); - handshake.replace(ckf, ck.length(), key); tcp::resolver resolver{ ioc }; + + //context holds certificates + ssl::context ctx{ ssl::context::tlsv12_client }; + //load_root_certificates(ctx); + + websocket::stream> wss{ ioc, ctx }; websocket::stream ws{ ioc }; - auto const results = resolver.resolve(host, SERVICE_PORT); - auto ep = net::connect(ws.next_layer(), results); + string host = hostip; + + auto const results = resolver.resolve(host, SSL ? SERVICE_PORT_SSL : SERVICE_PORT); + auto ep = net::connect(SSL ? get_lowest_layer(wss) : ws.next_layer(), results); + + //SSL set SNI Hostname + if (SSL) + SSL_set_tlsext_host_name(wss.next_layer().native_handle(), host.c_str()); + //build the host string for the decorator host += ':' + std::to_string(ep.port()); + + //SSL handshake + if (SSL) + wss.next_layer().handshake(ssl::stream_base::client); + + if (time(0) - origtim > 10) // this thread should not run too long { logmsg = device; @@ -778,50 +947,89 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca goto threadoffend; } // Set a decorator to change the User-Agent of the handshake - ws.set_option(websocket::stream_base::decorator( - [](websocket::request_type& req) - { - req.set(http::field::user_agent, - std::string(BOOST_BEAST_VERSION_STRING) + - " websocket-client-LGTVsvc"); - })); - if (time(0) - origtim > 10) // this thread should not run too long + if (SSL) { - logmsg = device; - logmsg += ", WARNING! DisplayPowerOffThread() - forced exit"; - Log(logmsg); - goto threadoffend; + wss.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-LGTVsvc"); + })); + wss.handshake(host, "/"); } - - ws.handshake(host, "/"); - if (time(0) - origtim > 10) // this thread should not run too long + else { - logmsg = device; - logmsg += ", WARNING! DisplayPowerOffThread() - forced exit"; - Log(logmsg); - goto threadoffend; + ws.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-LGTVsvc"); + })); + ws.handshake(host, "/"); } //send a handshake and check validity of the handshake pairing key. - ws.write(net::buffer(std::string(handshake))); - ws.read(buffer); // read the response + size_t ckf = handshake.find(ck); + handshake.replace(ckf, ck.length(), key); + if (SSL) + { + wss.write(net::buffer(std::string(handshake))); + wss.read(buffer); // read the response + } + else + { + ws.write(net::buffer(std::string(handshake))); + ws.read(buffer); // read the response + } + + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) OFF response 1: " : "[DEBUG] OFF response 1: "; + st += tt; + Log(st); + } + j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); type = j["type"]; - + buffer.consume(buffer.size()); if (std::string(type) != "registered") { logmsg = device; logmsg += ", WARNING! DisplayPowerOffThread() - Pairing key is invalid."; Log(logmsg); - ws.close(websocket::close_code::normal); + if(SSL) + wss.close(websocket::close_code::normal); + else + ws.close(websocket::close_code::normal); goto threadoffend; } - buffer.consume(buffer.size()); - + + //retreive power state from device to determine if the device is powered on - ws.write(net::buffer(getpowerstatemess)); - ws.read(buffer); + if (SSL) + { + wss.write(net::buffer(getpowerstatemess)); + wss.read(buffer); + } + else + { + ws.write(net::buffer(getpowerstatemess)); + ws.read(buffer); + } + + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) OFF response 2: " : "[DEBUG] OFF response 2: "; + st += tt; + Log(st); + } + j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); + buffer.consume(buffer.size()); type = j["type"]; state = j["payload"]["state"]; @@ -833,7 +1041,10 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca logmsg += ", power state is: "; logmsg += std::string(state); Log(logmsg); - ws.close(websocket::close_code::normal); + if(SSL) + wss.close(websocket::close_code::normal); + else + ws.close(websocket::close_code::normal); goto threadoffend; } } @@ -842,10 +1053,12 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca logmsg = device; logmsg += ", WARNING! DisplayPowerOffThread() - Invalid response from device - PowerState."; Log(logmsg); - ws.close(websocket::close_code::normal); + if(SSL) + wss.close(websocket::close_code::normal); + else + ws.close(websocket::close_code::normal); goto threadoffend; } - buffer.consume(buffer.size()); bool shouldPowerOff = true; @@ -856,8 +1069,24 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca shouldPowerOff = false; // get information about active input - ws.write(net::buffer(std::string(getactiveinputmess))); - ws.read(buffer); + if (SSL) + { + wss.write(net::buffer(std::string(getactiveinputmess))); + wss.read(buffer); + } + else + { + ws.write(net::buffer(std::string(getactiveinputmess))); + ws.read(buffer); + } + + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) OFF response 3: " : "[DEBUG] OFF response 3: "; + st += tt; + Log(st); + } j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); appId = j["payload"]["appId"]; @@ -866,7 +1095,7 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca if (appId.starts_with(prefix)) { appId.remove_prefix(prefix.size()); - if (std::to_string(CallingSessionParameters->OnlyTurnOffIfCurrentHDMIInputNumberIs) == appId) { + if (std::to_string(hdmi_number) == appId) { logmsg = device; logmsg += ", HDMI"; logmsg += std::string(appId); @@ -878,7 +1107,7 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca { logmsg = device; logmsg += ", HDMI"; - logmsg += std::to_string(CallingSessionParameters->OnlyTurnOffIfCurrentHDMIInputNumberIs); + logmsg += std::to_string(hdmi_number); logmsg += " input inactive."; Log(logmsg); } @@ -899,8 +1128,24 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca if (shouldPowerOff) { // send device or screen off command to device - ws.write(net::buffer(std::string(BlankScreen ? screenoffmess : poweroffmess))); - ws.read(buffer); // read the response + if (SSL) + { + wss.write(net::buffer(std::string(BlankScreen ? screenoffmess : poweroffmess))); + wss.read(buffer); // read the response + } + else + { + ws.write(net::buffer(std::string(BlankScreen ? screenoffmess : poweroffmess))); + ws.read(buffer); // read the response + } + + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = SSL ? "[DEBUG] (SSL) OFF response 4: " : "[DEBUG] OFF response 4: "; + st += tt; + Log(st); + } logmsg = device; if (BlankScreen) @@ -917,7 +1162,10 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca Log(logmsg); } - ws.close(websocket::close_code::normal); + if(SSL) + wss.close(websocket::close_code::normal); + else + ws.close(websocket::close_code::normal); } catch (std::exception const& e) { @@ -959,6 +1207,7 @@ void SetDisplayHdmiInputThread(SESSIONPARAMETERS* CallingSessionParameters, bool string hostip = CallingSessionParameters->IP; string key = CallingSessionParameters->SessionKey; string device = CallingSessionParameters->DeviceId; + bool SSL = CallingSessionParameters->SSL; string logmsg; time_t origtim = time(0); string hdmino = "HDMIINPUT"; @@ -982,33 +1231,68 @@ void SetDisplayHdmiInputThread(SESSIONPARAMETERS* CallingSessionParameters, bool time_t looptim = time(0); try { + //BOOST::BEAST + beast::flat_buffer buffer; + string host = hostip; net::io_context ioc; tcp::resolver resolver{ ioc }; + + //context holds certificates + ssl::context ctx{ ssl::context::tlsv12_client }; + //load_root_certificates(ctx); + + websocket::stream> wss{ ioc, ctx }; websocket::stream ws{ ioc }; - beast::flat_buffer buffer; - string host = hostip; + // try communicating with the display - auto const results = resolver.resolve(host, SERVICE_PORT); - auto ep = net::connect(ws.next_layer(), results); + auto const results = resolver.resolve(host, SSL?SERVICE_PORT_SSL:SERVICE_PORT); + auto ep = net::connect(SSL ? get_lowest_layer(wss) : ws.next_layer(), results); + + //SSL set SNI Hostname + if (SSL) + SSL_set_tlsext_host_name(wss.next_layer().native_handle(), host.c_str()); + + //build the host string for the decorator host += ':' + std::to_string(ep.port()); - ws.set_option(websocket::stream_base::decorator( - [](websocket::request_type& req) - { - req.set(http::field::user_agent, - std::string(BOOST_BEAST_VERSION_STRING) + - " websocket-client-LGTVsvc"); - })); - ws.handshake(host, "/"); - ws.write(net::buffer(std::string(handshake))); - ws.read(buffer); // read the first response - - ws.write(net::buffer(std::string(setinputmess))); - ws.read(buffer); - - ws.close(websocket::close_code::normal); + //SSL handshake + if (SSL) + wss.next_layer().handshake(ssl::stream_base::client); + + if (SSL) + { + wss.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-LGTVsvc"); + })); + wss.handshake(host, "/"); + wss.write(net::buffer(std::string(handshake))); + wss.read(buffer); // read the first response + wss.write(net::buffer(std::string(setinputmess))); + wss.read(buffer); + wss.close(websocket::close_code::normal); + } + else + { + ws.set_option(websocket::stream_base::decorator( + [](websocket::request_type& req) + { + req.set(http::field::user_agent, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-client-LGTVsvc"); + })); + ws.handshake(host, "/"); + ws.write(net::buffer(std::string(handshake))); + ws.read(buffer); // read the first response + ws.write(net::buffer(std::string(setinputmess))); + ws.read(buffer); + ws.close(websocket::close_code::normal); + } logmsg = device; logmsg += ", Setting HDMI input: "; logmsg += std::to_string(HdmiInput); diff --git a/LGTV Companion Setup/Product.wxs b/LGTV Companion Setup/Product.wxs index e1b679d4..58465e78 100644 --- a/LGTV Companion Setup/Product.wxs +++ b/LGTV Companion Setup/Product.wxs @@ -3,7 +3,7 @@ - + diff --git a/LGTV Companion UI/LGTV Companion UI.cpp b/LGTV Companion UI/LGTV Companion UI.cpp index bf988202..881be57b 100644 --- a/LGTV Companion UI/LGTV Companion UI.cpp +++ b/LGTV Companion UI/LGTV Companion UI.cpp @@ -15,89 +15,9 @@ BACKGROUND response to power events in windows. With OLED monitors this is particularly important to prevent "burn-in", or more accurately pixel-wear. -INSTALLATION AND USAGE - 0) Important prerequisites: - a) Power ON all TVs - b) Ensure that the TV can be woken via the network. For the CX line of displays this is - accomplished by navigating to Settings (cog button on remote)->All Settings->Connection-> - Mobile Connection Management->TV On with Mobile and enable 'Turn On via Wi-Fi'. - c) Open the admin interface of your router, and set a static DHCP lease for your WebOS - devices, i.e. to ensure that the displays always have the same IP-address on your LAN. +INSTALLATION, USAGE ETC - 1) Download the setup package and install. This will install and start the service - (LGTVsvc.exe), and also install the user interface (LGTV Companion.exe). - - 2) Open the user interface from the Windows start menu. - - 3) Click the 'Scan' button to let the application try and automatically find network attached WebOs - devices (TVs) - - 4) Optionally, click the drop down button to manually add, remove, configure the parameters of respective - devices, this includes the network IP-address, and the physical address, i.e. the MAC(s). This information - can easily be found in the network settings of the TV. - - 5) In the main application window, use the checkboxes to select what power events (shutdown, restart, - suspend, resume, idle) the respective devices shall respond to. - - 6) Optionally, tweak additional settings, by clicking on the hamburger icon. Note that enabling logging can - be very useful if you are facing any issues. - - IMPORTANT NOTICE: if your OS is not localised in english, you must in the 'additional settings' - dialog click the correct checkboxes to indicate what words refer to the system restarting/rebooting - (as opposed to shutting down). This is needed because there is no better (at least known to me) - way for a service to know if the system is being restarted or shut down than looking at a certain event - in the event log. But the event log is localised, and this approach saves me from having to build a language - table for all languages in the world. Note that if you don't do this on a non-english OS the application - will not be able to determine if the system is being restarted or shut down. The difference is of course - that the displays should not power off when the system is restarted. - - 7) Click Apply, to save the configuration file and automatically restart the service. - - 8) At this point your respective WebOS TV will display a pairing dialog which you need to accept. - - All systems are now GO! - - 9) Please go ahead and use the drop down menu again and select 'Test', to ensure that the displays - properly react to power on/off commands. - -LIMITATIONS - The OLED displays cannot be turned on via network when an automatic pixel refresh is being performed. You can hear - an internal relay click after the display is actually powered down, at which point it can be turned on again at any - time by the application. - - The WebOS displays can only be turned on/off when both the PC and the display is connected to a network. - -SYSTEM REQUIREMENTS - - The application must be run in a modern windows environment, i.e. any potato running Windows 10 is fine. - - A LAN - -COMMAND LINE ARGUMENTS - - LGTV Companion.exe -[poweron|poweroff|screenon|screenoff|autoenable|autodisable|sethdmi1|sethdmi2|sethdmi3|sethdmi4] [Device1|Name] [Device2|Name] ... [DeviceX|Name] - - -poweron - power on a device. - -screenon - power on a device - -poweroff - power off a device. - -screenoff - disable emitters, i.e. enter power saving mode where screen is blanked. - -sethdmi1 - set HDMI input 1 - -sethdmi2 - set HDMI input 2 - -sethdmi3 - set HDMI input 3 - -sethdmi4 - set HDMI input 4 - -autoenable - temporarily enable the automatic management of a device, i.e. to respond to power events. This - is effective until next restart of the service. (I personally use this for my home automation system). - -autodisable - temporarily disable the automatic management of a device, i.e. to respond to power events. This - is effective until next restart of the service. - - [DeviceX|Name] - device identifier. Either use Device1, Device2, ..., DeviceX or the friendly device name as found - in the User Interface, for example OLED48CX. - - Example usage: LGTV Companion.exe -poweron Device1 Device2 "LG OLED48CX" -autodisable Device4 - - This command will power on device 1, device 2 and the device named LG OLED48CX, and additionally device4 is set to - temporarily not respond to automatic power events (on/off). - -ADDITIONAL NOTES - N/A +https://github.com/JPersson77/LGTVCompanion CHANGELOG v 1.0.0 - Initial release @@ -132,18 +52,24 @@ CHANGELOG v 1.8.0 - Option to conform to windows monitor topology (active display outputs only) + v 1.9.0 - More granular user idle mode + + v 2.0.0 - Support for LG's newest firmware which changed the connection method (SSL) + - Option to revert to legacy connection method for (presumably) older models + - Optional support for NVIDIA Gamestream, Steam Link, Sunshine and RDP + - Fixed a bug where the fullscreen detection for user idle mode was triggered by the NVIDIA GFE overlay sometimes + - Improved logic for the monitor topology feature + - More help texts + TODO: - v 1.9.0 - [ ] Enhanced support for GameStream/Moonlight/SteamLink - check added virtual devices - - [ ] Advanced configuration for user idle detection. Separate mouse/keyboard/controller, and implement app whitelist - https://learn.microsoft.com/en-us/windows/win32/inputdev/using-raw-input?source=recommendations - https://stackoverflow.com/questions/7009080/detecting-full-screen-mode-in-windows - - [x] bugfix random power onoff potentially related to wm_displaychange triggering not only on topology change - - [x] clearly explain that change of GPU will cause problems with the topology feature. Possible to identify GPU changes? - - [x] identify invalid pairing keys - - [x] more help texts + - [ ] Feature to power on only when PC input s selected on TV (if possible) + - [ ] Device on/off indicator + - [ ] compatibility mode for topology + - [ ] Exclusion list from fullscreen detection, or detect which monitor fullscreen is happening on. https://github.com/JPersson77/LGTVCompanion/issues/96 + LICENSE - Copyright (c) 2021-2022 Jörgen Persson + Copyright (c) 2021-2023 Jörgen Persson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, @@ -162,6 +88,7 @@ THANKS TO - Boost libs - Boost and Beast https://www.boost.org/ - Maassoft for initial inspo and understanding re the WebOS comms - https://github.com/Maassoft - Mohammed Boujemaoui - Author of WinToast https://github.com/mohabouje/WinToast + - OpenSSL https://github.com/openssl/openssl - Etienne Dechanmps - valuable contributions COPYRIGHT @@ -390,7 +317,15 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) CheckDlgButton(hDeviceWindow, IDC_SET_HDMI_INPUT_CHECKBOX, BST_UNCHECKED); EnableWindow(GetDlgItem(hDeviceWindow, IDC_SET_HDMI_INPUT_NUMBER), false); - SetWindowText(GetDlgItem(hDeviceWindow, IDC_HDMI_INPUT_NUMBER), L"1"); + SetWindowText(GetDlgItem(hDeviceWindow, IDC_SET_HDMI_INPUT_NUMBER), L"1"); + + wstring s; + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_RESETCONTENT, (WPARAM)0, (LPARAM)0); + s = L"Auto"; + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)s.c_str()); + s = L"Legacy"; + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)s.c_str()); + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_SETCURSEL, (WPARAM)0, (LPARAM)0); EnableWindow(GetDlgItem(hDeviceWindow, IDOK), false); @@ -426,6 +361,15 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (item != Devices[sel].MAC.back()) str += L"\r\n"; } + + wstring s; + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_RESETCONTENT, (WPARAM)0, (LPARAM)0); + s = L"Auto"; + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)s.c_str()); + s = L"Legacy"; + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)s.c_str()); + SendMessage(GetDlgItem(hDeviceWindow, IDC_COMBO_SSL), (UINT)CB_SETCURSEL, (WPARAM)Devices[sel].SSL ? 0 : 1, (LPARAM)0); + SetWindowText(GetDlgItem(hDeviceWindow, IDC_DEVICEMACS), str.c_str()); EnableWindow(GetDlgItem(hDeviceWindow, IDOK), false); @@ -984,6 +928,9 @@ LRESULT CALLBACK DeviceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP SendDlgItemMessage(hWnd, IDC_SUBNET, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_SET_HDMI_INPUT_NUMBER, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_SET_HDMI_INPUT_SPIN, UDM_SETRANGE, (WPARAM)NULL, MAKELPARAM(4, 1)); + SendDlgItemMessage(hWnd, IDC_COMBO_SSL, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); + + }break; case WM_NOTIFY: { @@ -1025,6 +972,14 @@ LRESULT CALLBACK DeviceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP " the PC is connected to when powering on.", L"Information", MB_OK | MB_ICONINFORMATION); } + // explain the firmware options + else if (wParam == IDC_SYSLINK10) + { + MessageBox(hWnd, + L"In Q1 2023 a firmware upgrade was pushed by LG to many WebOS-devices which caused connectivity issues. A new method for connection was implemented " + "and it is now recommended for most users to use the \"Auto\" option. The legacy option will force the usage of the original connection method.", + L"Information", MB_OK | MB_ICONINFORMATION); + } }break; } }break; @@ -1129,6 +1084,12 @@ LRESULT CALLBACK DeviceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP else Devices[sel].WOLtype = WOL_NETWORKBROADCAST; + int SSL_selection = (int)(SendMessage(GetDlgItem(hWnd, IDC_COMBO_SSL), (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0)); + if (SSL_selection != CB_ERR) + { + Devices[sel].SSL = SSL_selection == 0 ? true : false; + } + int ind = (int)SendMessage(GetDlgItem(hParentWnd, IDC_COMBO), (UINT)CB_DELETESTRING, (WPARAM)sel, (LPARAM)0); SendMessage(GetDlgItem(hParentWnd, IDC_COMBO), (UINT)CB_INSERTSTRING, (WPARAM)sel, (LPARAM)widen(Devices[sel].Name).c_str()); SendMessage(GetDlgItem(hParentWnd, IDC_COMBO), (UINT)CB_SETCURSEL, (WPARAM)sel, (LPARAM)0); @@ -1160,6 +1121,12 @@ LRESULT CALLBACK DeviceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP sess.SetHDMIInputOnResume = IsDlgButtonChecked(hWnd, IDC_SET_HDMI_INPUT_CHECKBOX) == BST_CHECKED; sess.SetHDMIInputOnResumeToNumber = atoi(narrow(GetWndText(GetDlgItem(hWnd, IDC_SET_HDMI_INPUT_NUMBER))).c_str()); + int SSL_selection = (int)(SendMessage(GetDlgItem(hWnd, IDC_COMBO_SSL), (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0)); + if (SSL_selection != CB_ERR) + { + sess.SSL = SSL_selection == 0 ? true : false; + } + Devices.push_back(sess); int index = (int)SendMessage(GetDlgItem(hParentWnd, IDC_COMBO), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)widen(sess.Name).c_str()); @@ -1226,6 +1193,10 @@ LRESULT CALLBACK DeviceWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP default:break; } }break; + case CBN_SELCHANGE: + { + EnableWindow(GetDlgItem(hWnd, IDOK), true); + }break; default:break; } }break; @@ -1287,7 +1258,7 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l vector str; SendDlgItemMessage(hWnd, IDC_STATIC_C, WM_SETFONT, (WPARAM)hEditSmallfont, MAKELPARAM(TRUE, 0)); - + SendDlgItemMessage(hWnd, IDC_COMBO_MODE, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_TIMEOUT, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_LIST, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_SPIN, UDM_SETRANGE, (WPARAM)NULL, MAKELPARAM(100, 1)); @@ -1388,8 +1359,20 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l CheckDlgButton(hWnd, IDC_LOGGING, Prefs.Logging ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hWnd, IDC_AUTOUPDATE, Prefs.AutoUpdate ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hWnd, IDC_CHECK_BLANK, Prefs.BlankScreenWhenIdle ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hWnd, IDC_CHECK_RDPBLANK, Prefs.PowerOffDuringRDP ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hWnd, IDC_CHECK_REMOTE, Prefs.RemoteStreamingCheck ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hWnd, IDC_CHECK_TOPOLOGY, Prefs.AdhereTopology ? BST_CHECKED : BST_UNCHECKED); + + wstring s; + SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_RESETCONTENT, (WPARAM)0, (LPARAM)0); + s = L"Power efficiency (display off)"; + SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)s.c_str()); + s = L"Compatibility (display blanked)"; + SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_ADDSTRING, (WPARAM)0, (LPARAM)s.c_str()); + SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_SETCURSEL, (WPARAM)Prefs.TopologyPreferPowerEfficiency ? 0 : 1, (LPARAM) 0); + +// EnableWindow(GetDlgItem(hWnd, IDC_COMBO_MODE), Prefs.AdhereTopology ? true : false); + EnableWindow(GetDlgItem(hWnd, IDC_COMBO_MODE), false); //REMOVE + EnableWindow(GetDlgItem(hWnd, IDOK), false); }break; case WM_COMMAND: @@ -1423,39 +1406,56 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l }break; case IDC_CHECK_TOPOLOGY: { - bool found = false; - if (Devices.size() == 0) - { - MessageBox(hWnd, L"Please configure devices before enabling and configuring this option", L"Error", MB_ICONEXCLAMATION | MB_OK); - CheckDlgButton(hWnd, IDC_CHECK_TOPOLOGY, BST_UNCHECKED); - } - else + if (Prefs.version < 2 && !Prefs.ResetAPIkeys) { - for (auto& k : Devices) + int mess = MessageBox(hWnd, L"Enabling this option will enforce re-pairing of all your devices.\n\n Do you want to enable this option?", L"Device pairing", MB_YESNO | MB_ICONQUESTION); + if (mess == IDNO) { - if (k.UniqueDeviceKey != "") - found = true; + CheckDlgButton(hWnd, IDC_CHECK_TOPOLOGY, BST_UNCHECKED); } - - if (found) + if (mess == IDYES) { + CheckDlgButton(hWnd, IDC_CHECK_TOPOLOGY, BST_CHECKED); + Prefs.ResetAPIkeys = true; EnableWindow(GetDlgItem(hWnd, IDOK), true); } + } + else + { + bool found = false; + if (Devices.size() == 0) + { + MessageBox(hWnd, L"Please configure all devices before enabling and configuring this option", L"Error", MB_ICONEXCLAMATION | MB_OK); + CheckDlgButton(hWnd, IDC_CHECK_TOPOLOGY, BST_UNCHECKED); + } else { - hTopologyWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_CONFIGURE_TOPOLOGY), hWnd, (DLGPROC)ConfigureTopologyWndProc); - EnableWindow(hWnd, false); - ShowWindow(hTopologyWindow, SW_SHOW); + for (auto& k : Devices) + { + if (k.UniqueDeviceKey != "") + found = true; + } + + if (found) + { + EnableWindow(GetDlgItem(hWnd, IDOK), true); + } + else + { + hTopologyWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_CONFIGURE_TOPOLOGY), hWnd, (DLGPROC)ConfigureTopologyWndProc); + EnableWindow(hWnd, false); + ShowWindow(hTopologyWindow, SW_SHOW); + } } } - +// EnableWindow(GetDlgItem(hWnd, IDC_COMBO_MODE), IsDlgButtonChecked(hWnd, IDC_CHECK_TOPOLOGY)); }break; case IDC_LOGGING: case IDC_AUTOUPDATE: - case IDC_CHECK_RDPBLANK: + case IDC_CHECK_REMOTE: { EnableWindow(GetDlgItem(hWnd, IDOK), true); }break; @@ -1476,9 +1476,15 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l } Prefs.AutoUpdate = IsDlgButtonChecked(hWnd, IDC_AUTOUPDATE); Prefs.BlankScreenWhenIdle = IsDlgButtonChecked(hWnd, IDC_CHECK_BLANK) == BST_CHECKED; - Prefs.PowerOffDuringRDP = IsDlgButtonChecked(hWnd, IDC_CHECK_RDPBLANK) == BST_CHECKED; + Prefs.RemoteStreamingCheck = IsDlgButtonChecked(hWnd, IDC_CHECK_REMOTE) == BST_CHECKED; Prefs.AdhereTopology = IsDlgButtonChecked(hWnd, IDC_CHECK_TOPOLOGY) == BST_CHECKED; + int sel = (int)(SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0)); + if (sel == 1) + Prefs.TopologyPreferPowerEfficiency = false; + else + Prefs.TopologyPreferPowerEfficiency = true; + int count = ListView_GetItemCount(GetDlgItem(hWnd, IDC_LIST)); Prefs.EventLogRestartString.clear(); Prefs.EventLogShutdownString.clear(); @@ -1524,6 +1530,10 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l default:break; } }break; + case CBN_SELCHANGE: + { + EnableWindow(GetDlgItem(hWnd, IDOK), true); + }break; default:break; } }break; @@ -1566,32 +1576,35 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l MessageBox(hWnd, L"The power on timeout determines the maximum number of seconds the app will attempt to connect to devices when powering on. " "Please consider increasing this value if your PC needs more time to establish contact with the WebOS-devices during system boot.\n\n" "The option to enable logging is very useful for troubleshooting and if you are experiencing issues with the operations of this app.\n\n" - "The option to automatically check for updates ensure that you are always notified when a new version of LGTV Companion is available for download.", + "The option to automatically notify when a new version is available ensure that you are always notified when a new version of LGTV Companion is available for download.", L"Global options", MB_OK | MB_ICONINFORMATION); } // explain the power saving options else if (wParam == IDC_SYSLINK5) { - MessageBox(hWnd, L"The option to automatically blank the screen, i e turn the transmitters off, is triggered in the absence of user input from keyboard, mouse and/or controllers. " - "The difference, when compared to both the screensaver and windows power plan settings, is that those OS implemented power saving functions " + MessageBox(hWnd, L"The user idle mode will automatically blank the screen, i e turn the transmitters off, in the absence of user input from keyboard, mouse and/or controllers. " + "The difference, when compared to both the screensaver and windows power plan settings, is that those OS implemented power saving features " "utilize more obscured variables for determining user idle / busy states, and which can also be programmatically overridden f.e. by games, " "media players, production software or your web browser, In short, and simplified, this option is a more aggressively configured screen and " - "power saver. Plese note that this feature is incompatible with-, and will therefore be disabled, while the system is remoted into, using " - "RDP.\n\nThe option to turn off the device while the system is being remoted into, using RDP, is useful to avoid the screen displaying a static " - "login screen during a remote RDP session. There is a delay of 10 seconds after RDP connection. Please note that depending on the configuration " - "of your system (primarily time to turn off screen in power options) there may be occasions where the display cannot be woken by using the local " - "mouse and keyboard and you must rely on the remote to see the login screen. It is recommended to combine this option with a short (<30 minutes) " - "time to turn off displays in Windows Power Plan Options.", + "power saver. \n\n" + "The option to support remote streaming hosts will power off managed devices while the system is acting as streaming host or being remoted into. Supported " + "hosts include Nvidia gamestream, Moonlight, Steam Link and RDP. Please note that the devices will remain powered off until the remote connection is disconnected. ", L"Andvanced power options", MB_OK | MB_ICONINFORMATION); } // explain the pmulti-monitor conf else if (wParam == IDC_SYSLINK6) { - MessageBox(hWnd, L"The option to automatically conform to the windows monitor topology ensures that the " + MessageBox(hWnd, L"The option to support the windows multi-monitor topology ensures that the " "power state of individual devices will match the enabled or disabled state in the Windows monitor configuration, i e " "when an HDMI-output is disabled in the graphics card configuration the associated device will also power off. \n\n" - "If you have a multi-monitor system it is recommended to configure and enable this option.\n\n" - "NOTE! A change of GPU or adding more displays may invalidate the configuration. If so, please run the configuration guide " + "The \"Power efficiency\" option will ensure that devices are set to a powered off state in response to changes in the topology and is the recommended option. " + "This option will however not work on all system configuration and may fail to powere on the devices again appropriately. " + "Enabling \"Always Ready\" in the settings of compatible WebOS devices (2022-models, A2, B2,C2 etc, and later) will ensure that " + "\"Power efficiency\" works properly.\n\n In case the \"Power efficiency\" option does not work the \"Compatibility\" option " + "will work on all configurations and will instead blank the screen (instead of powering off) in response to " + "changes in the monitor topology\n\n" + "If you have a multi-monitor system it is recommended to configure and enable this feature.\n\n" + "PLEASE NOTE! A change of GPU or adding more displays may invalidate the configuration. If so, please run the configuration guide " "again to ensure correct operation.", L"Multi-monitor support", MB_OK | MB_ICONINFORMATION); } @@ -1643,7 +1656,7 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l HDC hdcStatic = (HDC)wParam; SetTextColor(hdcStatic, COLORREF(COLOR_STATIC)); if ((HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_BLANK) - || (HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_RDPBLANK) + || (HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_REMOTE) || (HWND)lParam == GetDlgItem(hWnd, IDC_LOGGING) || (HWND)lParam == GetDlgItem(hWnd, IDC_AUTOUPDATE)) { @@ -2514,10 +2527,6 @@ bool ReadConfigFile() if (!j.empty() && j.is_number()) Prefs.BlankScreenWhenIdleDelay = j.get(); - j = jsonPrefs[JSON_PREFS_NODE][JSON_RDP_POWEROFF]; - if (!j.empty() && j.is_boolean()) - Prefs.PowerOffDuringRDP = j.get(); - j = jsonPrefs[JSON_PREFS_NODE][JSON_ADHERETOPOLOGY]; if (!j.empty() && j.is_boolean()) Prefs.AdhereTopology = j.get(); @@ -2530,6 +2539,15 @@ bool ReadConfigFile() if (!j.empty() && j.is_boolean()) Prefs.bFullscreenCheckEnabled = j.get(); + j = jsonPrefs[JSON_PREFS_NODE][JSON_REMOTESTREAM]; + if (!j.empty() && j.is_boolean()) + Prefs.RemoteStreamingCheck = j.get(); + + j = jsonPrefs[JSON_PREFS_NODE][JSON_TOPOLOGYMODE]; + if (!j.empty() && j.is_boolean()) + Prefs.TopologyPreferPowerEfficiency = j.get(); + + j = jsonPrefs[JSON_PREFS_NODE][JSON_WHITELIST]; if (!j.empty() && j.size() > 0) { @@ -2667,6 +2685,9 @@ void ReadDeviceConfig() if (item.value()["SetHDMIInputOnResume"].is_boolean()) params.SetHDMIInputOnResume = item.value()["SetHDMIInputOnResume"].get(); + if (item.value()["NewSockConnect"].is_boolean()) + params.SSL = item.value()["NewSockConnect"].get(); + if (item.value()["SetHDMIInputOnResumeToNumber"].is_number()) params.SetHDMIInputOnResumeToNumber = item.value()["SetHDMIInputOnResumeToNumber"].get(); @@ -2798,10 +2819,11 @@ void WriteConfigFile(void) prefs[JSON_PREFS_NODE][JSON_AUTOUPDATE] = (bool)Prefs.AutoUpdate; prefs[JSON_PREFS_NODE][JSON_IDLEBLANK] = (bool)Prefs.BlankScreenWhenIdle; prefs[JSON_PREFS_NODE][JSON_IDLEBLANKDELAY] = (int)Prefs.BlankScreenWhenIdleDelay; - prefs[JSON_PREFS_NODE][JSON_RDP_POWEROFF] = (bool)Prefs.PowerOffDuringRDP; prefs[JSON_PREFS_NODE][JSON_ADHERETOPOLOGY] = (bool)Prefs.AdhereTopology; prefs[JSON_PREFS_NODE][JSON_IDLEWHITELIST] = (bool)Prefs.bIdleWhitelistEnabled; prefs[JSON_PREFS_NODE][JSON_IDLEFULLSCREEN] = (bool)Prefs.bFullscreenCheckEnabled; + prefs[JSON_PREFS_NODE][JSON_REMOTESTREAM] = (bool)Prefs.RemoteStreamingCheck; + prefs[JSON_PREFS_NODE][JSON_TOPOLOGYMODE] = (bool)Prefs.TopologyPreferPowerEfficiency; for (auto& item : Prefs.EventLogRestartString) prefs[JSON_PREFS_NODE][JSON_EVENT_RESTART_STRINGS].push_back(item); @@ -2843,6 +2865,8 @@ void WriteConfigFile(void) prefs[dev.str()]["SetHDMIInputOnResume"] = (bool)item.SetHDMIInputOnResume; prefs[dev.str()]["SetHDMIInputOnResumeToNumber"] = item.SetHDMIInputOnResumeToNumber; + prefs[dev.str()]["NewSockConnect"] = (bool)item.SSL; + if (item.Subnet != "") prefs[dev.str()]["Subnet"] = item.Subnet; diff --git a/LGTV Companion UI/LGTV Companion UI.h b/LGTV Companion UI/LGTV Companion UI.h index b42e9d71..e1b7d225 100644 --- a/LGTV Companion UI/LGTV Companion UI.h +++ b/LGTV Companion UI/LGTV Companion UI.h @@ -53,7 +53,7 @@ #define APPNAME_SHORT L"LGTVcomp" #define APPNAME_FULL L"LGTV Companion" -#define APP_VERSION L"1.9.0" +#define APP_VERSION L"2.0.0" #define WINDOW_CLASS_UNIQUE L"YOLOx0x0x0181818" #define NOTIFY_NEW_COMMANDLINE 1 @@ -70,10 +70,11 @@ #define JSON_IDLEWHITELIST "IdleWhiteListEnabled" #define JSON_IDLEFULLSCREEN "IdleFullscreen" #define JSON_WHITELIST "IdleWhiteList" +#define JSON_REMOTESTREAM "RemoteStream" +#define JSON_TOPOLOGYMODE "TopologyPreferPowerEfficient" #define DEFAULT_RESTART {"restart"} #define DEFAULT_SHUTDOWN {"shutdown","power off"} -#define JSON_RDP_POWEROFF "PowerOffDuringRDP" #define COLOR_STATIC 0x00555555 #define COLOR_RED 0x00000099 @@ -131,11 +132,12 @@ struct PREFS { bool ResetAPIkeys = false; bool BlankScreenWhenIdle = false; int BlankScreenWhenIdleDelay = 10; - bool PowerOffDuringRDP = false; bool AdhereTopology = false; bool bIdleWhitelistEnabled = false; bool bFullscreenCheckEnabled = false; std::vector WhiteList; + bool RemoteStreamingCheck = false; + bool TopologyPreferPowerEfficiency = true; }; struct SESSIONPARAMETERS { std::string DeviceId; @@ -156,6 +158,7 @@ struct SESSIONPARAMETERS { int OnlyTurnOffIfCurrentHDMIInputNumberIs = 1; bool SetHDMIInputOnResume = false; int SetHDMIInputOnResumeToNumber = 1; + bool SSL = true; }; struct DISPLAY_INFO { DISPLAYCONFIG_TARGET_DEVICE_NAME target; diff --git a/LGTV Companion UI/LGTV Companion UI.rc b/LGTV Companion UI/LGTV Companion UI.rc index c7718a03999c3a9fceff224bf190872109b9440e..1d3dfea81be8c832574090d07a2be248b8fa42b5 100644 GIT binary patch delta 1581 zcmZ8hTTEP46g_vQyiyn#o--$`m~;BOUi=i9)l!i|ZgdKEWI2K=V5-F;K=xnNw_qDfF9TDiuk`K4jF$P~J; zcs8UQgZsk>l>S8@8j8zCg1U%Pr)Ob?HhvlIAh|Ty5h<(=!SN;_hX$ig$G%x#RPb6lllgtAFuI!oCEwNieL z`+Cvi>c;+aGa^*#q~~eMCtOSE&67&c(qDw_Ia$gpFJ)CH#1Szh8f2<@Cq%b6E(Wo_ z+a_rjf?H{`gP4RNTAlS+qtf_)^owq+Q6gM)@ui4iidZK_GotZ_+w8^JzLJYXb~T{q zAK=h1Btdjj=e<2U&Ci#FL!;tQQT_Jd+`M!MrxN!R6CG_2Ig-}D1~;O#B9nZV?^yzB z`)aZGD#fxjFgZHO;0(KGoXJbRN6CsiHdQh`uf`raZ*1c@P*j6A0*^Sqwp;mho?foP z!R{mI%xJjNttmQIu1iTJN{<=}+hIjj;_@FKUkgzl!g7BJZujlstHV@W>yY2(1dd#7 zgs$hNshH~vK|35`is@dJ|4;R*7+N+^+ILFITrZV)udhY1u-2Es z>7Gi7foLAQ8FtZ>WOm{7N(-?(-YfBiIHp_C`KHO}&S(8MYVpBj057Iq&5MwUd2z=1 IF16A44=mJnhyVZp delta 1415 zcmZWpO-x)>6h4oMEh)?|lo?8aab~~@kNJ5s@69}*(6KG1ln!Xsn7HYbv2_FnGSFg) zCbDVLMg8IHLW9xZLN~Q)p9@#DB(8{y+PG4aN>gJ^OgHXKB%X6;Y&6Ye-nsYOd%ydg z@0@eDZZuqX8ov11<4RN3x#L;aTp3Cs-6P0F%0DTlY+DSWL* z+-XWthJ6NG3*B$E!xPwXS(JgdrRYymlGjr8a~U$6)t2uYg0&+V1uA1p0hq`)C$JUs z;FGqM`W}mN3gCZxoHN0sR^MPh0*O^o?j4 zeBL+jHIP7y8Kz9<1qOGl zXn9#(E;>~h{=aI8dJF2>Fz+w(iJF2nJ$0VVFu4nKn$H!PbcsDR^3!Z9d{33FkQDV7 zu|3$1AIz_ij5m7!E&e1c&Uu5sw*|h(yKu{D#S4B9eAx_Q7x%9<_XZ7>6iKesn$ATj zWzVVLqJQ&H+kBC)D$-fLLg9OgGe-F0R5;O_A4CYxb%3S1;GRd>ZlR$N@a z54-E$NSN!k)>{!4sTrlwL;ScT@#*NgSLBo4u01hnY+veh8uLb@md56lAZ|^D@ai#L z<4Qg{fNzf(@QlR~8#i$CNC>{kkZNTASU)a4y-&?;egWorzI6NnZcHTk1!Sa&0k*bS zyfYD3&Axdh0SlqEwSD;I#3|KJ0#hl(u~j&QyGM22#vNH0z`gMRULWgn#-`qj>^?!y W(@A;`zgI75Pa^eN)cJ7nYX3iZ=2M3N diff --git a/LGTV Companion UI/resource.h b/LGTV Companion UI/resource.h index accfd1a1..1ab8b8a0 100644 --- a/LGTV Companion UI/resource.h +++ b/LGTV Companion UI/resource.h @@ -39,6 +39,7 @@ #define IDC_EDIT_BLANK 1015 #define IDC_SPIN2 1016 #define IDC_CHECK_RDPBLANK 1017 +#define IDC_CHECK_REMOTE 1017 #define IDC_LIST 1018 #define IDC_SYSLINK3 1019 #define IDC_SYSLINK5 1020 @@ -65,6 +66,7 @@ #define IDC_STATIC_NO_2 1033 #define IDC_SYSLINK9 1033 #define IDC_STATIC_NO_3 1034 +#define IDC_SYSLINK10 1034 #define IDC_STATIC_NO_4 1035 #define IDC_STATIC_T_1 1036 #define IDC_STATIC_T_11 1036 @@ -95,9 +97,10 @@ #define IDC_SYSLINK_INFO_2 1058 #define IDC_EDIT_PROCESS 1058 #define IDC_SYSLINK_INFO_3 1059 -#define IDC_SYSLINK1 1059 #define IDC_SYSLINK_BROWSE 1059 #define IDC_SYSLINK_INFO_1 1060 +#define IDC_COMBO_MODE 1061 +#define IDC_COMBO_SSL 1063 #define ID_ADD_MANAGE 32771 #define ID_ADD_MANAGE32772 32772 #define ID_ADD_REMOVE 32773 @@ -124,7 +127,7 @@ #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 155 #define _APS_NEXT_COMMAND_VALUE 32788 -#define _APS_NEXT_CONTROL_VALUE 1060 +#define _APS_NEXT_CONTROL_VALUE 1064 #define _APS_NEXT_SYMED_VALUE 110 #endif #endif diff --git a/LGTV Companion User/Daemon.cpp b/LGTV Companion User/Daemon.cpp index efe3faaa..542b4828 100644 --- a/LGTV Companion User/Daemon.cpp +++ b/LGTV Companion User/Daemon.cpp @@ -6,7 +6,7 @@ using json = nlohmann::json; // Globals: HINSTANCE hInstance; // current instance -HWND hMainWnd; +HWND hMainWnd = NULL; bool bIdle = false; bool bIdlePreventEarlyWakeup = false; bool bDaemonVisible = true; @@ -21,6 +21,9 @@ HANDLE hPipe = INVALID_HANDLE_VALUE; INT64 idToastFirstrun = NULL; INT64 idToastNewversion = NULL; vector Devices; +string sCurrentlyRunningWhitelistedProcess = ""; +UINT shellhookMessage; +DWORD daemon_startup_user_input_time = 0; //Application entry point int APIENTRY wWinMain(_In_ HINSTANCE Instance, @@ -59,6 +62,9 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, } } + + shellhookMessage = RegisterWindowMessageW(L"SHELLHOOK"); + //Initialize toast notifications WinToast::WinToastError error; WinToast::instance()->setAppName(L"LGTV Companion (Daemon)"); @@ -87,6 +93,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, return false; } + + // create main window (dialog) hMainWnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)WndProc); SetWindowText(hMainWnd, WindowTitle.c_str()); @@ -106,7 +114,10 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, SetTimer(hMainWnd, TIMER_IDLE, Prefs.BlankScreenWhenIdleDelay * 60 * 1000, (TIMERPROC)NULL); } if (Prefs.AdhereTopology) - SetTimer(hMainWnd, TIMER_TOPOLOGY, 8000, (TIMERPROC)NULL); + SetTimer(hMainWnd, TIMER_TOPOLOGY, TIMER_TOPOLOGY_DELAY, (TIMERPROC)NULL); + + if(Prefs.RemoteStreamingCheck || Prefs.bIdleWhitelistEnabled) + SetTimer(hMainWnd, TIMER_CHECK_PROCESSES, TIMER_CHECK_PROCESSES_DELAY, (TIMERPROC)NULL); wstring startupmess = WindowTitle; startupmess += L" is running."; @@ -114,16 +125,19 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, HPOWERNOTIFY rsrn = RegisterSuspendResumeNotification(hMainWnd, DEVICE_NOTIFY_WINDOW_HANDLE); HPOWERNOTIFY rpsn = RegisterPowerSettingNotification(hMainWnd, &(GUID_CONSOLE_DISPLAY_STATE), DEVICE_NOTIFY_WINDOW_HANDLE); - /* + DEV_BROADCAST_DEVICEINTERFACE NotificationFilter; - ZeroMemory(&NotificationFilter, sizeof(NotificationFilter)); - NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); - NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; - memcpy(&(NotificationFilter.dbcc_classguid), &(GUID_DEVINTERFACE_USB_DEVICE), sizeof(struct _GUID)); - HDEVNOTIFY dev_notify = RegisterDeviceNotification(hMainWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); - */ - - WTSRegisterSessionNotification(hMainWnd, NOTIFY_FOR_ALL_SESSIONS); + HDEVNOTIFY dev_notify = NULL; + if (Prefs.RemoteStreamingCheck) + { + ZeroMemory(&NotificationFilter, sizeof(NotificationFilter)); + NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); + NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + memcpy(&(NotificationFilter.dbcc_classguid), &(GUID_DEVINTERFACE_USB_DEVICE), sizeof(struct _GUID)); + dev_notify = RegisterDeviceNotification(hMainWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); + } + if(Prefs.RemoteStreamingCheck) + WTSRegisterSessionNotification(hMainWnd, NOTIFY_FOR_ALL_SESSIONS); CommunicateWithService("-daemon started"); @@ -148,11 +162,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, WinToast::instance()->clear(); } } - UnregisterSuspendResumeNotification(rsrn); UnregisterPowerSettingNotification(rpsn); -// UnregisterDeviceNotification(dev_notify); - WTSUnRegisterSessionNotification(hMainWnd); + if(dev_notify) + UnregisterDeviceNotification(dev_notify); + if(Prefs.RemoteStreamingCheck) + WTSUnRegisterSessionNotification(hMainWnd); return (int)msg.wParam; } @@ -252,6 +267,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) lii.cbSize = sizeof(LASTINPUTINFO); if (GetLastInputInfo(&lii)) { + // do this first time the timer is triggered + if (daemon_startup_user_input_time == 0) + daemon_startup_user_input_time = lii.dwTime; + + //fix for the fullscreen idle detection on system startup because windows will return QUNS_BUSY until the user has interacted with the PC + if (lii.dwTime != daemon_startup_user_input_time) + daemon_startup_user_input_time = -1; + if (bDaemonVisible) { wstring tick = widen(to_string(lii.dwTime)); @@ -269,11 +292,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { dwLastInputTick = lii.dwTime; bIdlePreventEarlyWakeup = true; - Prefs.DisableSendingViaIPC = false; } else { - SetTimer(hWnd, TIMER_MAIN, bDaemonVisible?(TIMER_MAIN_DELAY_WHEN_BUSY)/10: TIMER_MAIN_DELAY_WHEN_BUSY, (TIMERPROC)NULL); + SetTimer(hWnd, TIMER_MAIN, bDaemonVisible?1000: TIMER_MAIN_DELAY_WHEN_BUSY, (TIMERPROC)NULL); SetTimer(hWnd, TIMER_IDLE, Prefs.BlankScreenWhenIdleDelay * 60 * 1000, (TIMERPROC)NULL); dwLastInputTick = lii.dwTime; bIdlePreventEarlyWakeup = false; @@ -296,9 +318,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } return 0; }break; + case TIMER_IDLE: { - if (Prefs.bFullscreenCheckEnabled) + if (Prefs.bFullscreenCheckEnabled && (daemon_startup_user_input_time == -1)) if (FullscreenApplicationRunning()) { Log(L"Fullscreen application prohibiting idle"); @@ -306,11 +329,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } if (Prefs.bIdleWhitelistEnabled) { - string proc = WhitelistProcessRunning(); - if (proc != "") + if (sCurrentlyRunningWhitelistedProcess != "") { wstring mess = L"Whitelisted application prohibiting idle ("; - mess += widen(proc); + mess += widen(sCurrentlyRunningWhitelistedProcess); mess += L")"; Log(mess); return 0; @@ -326,33 +348,51 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return 0; }break; - - case TIMER_RDP: + case TIMER_CHECK_PROCESSES: { - KillTimer(hWnd, TIMER_RDP); - if (Prefs.BlankScreenWhenIdle) - { - if (bIdle) - CommunicateWithService("-daemon remoteconnect_idle"); - else - CommunicateWithService("-daemon remoteconnect_busy"); - } - else - CommunicateWithService("-daemon remoteconnect"); - Prefs.DisableSendingViaIPC = true; - return 0; + RemoteStreamingEvent(CheckRunningProcesses() ? REMOTE_STEAM_CONNECTED : REMOTE_STEAM_NOT_CONNECTED); + }break; + case REMOTE_STEAM_CONNECTED: + case REMOTE_NVIDIA_CONNECTED: + case REMOTE_RDP_CONNECTED: + { + KillTimer(hWnd, (UINT_PTR)wParam); + CommunicateWithService("-daemon remote_connect"); }break; + case REMOTE_STEAM_NOT_CONNECTED: + case REMOTE_NVIDIA_NOT_CONNECTED: + case REMOTE_RDP_NOT_CONNECTED: + { + KillTimer(hWnd, (UINT_PTR)wParam); + CommunicateWithService("-daemon remote_disconnect"); + }break; + case TIMER_TOPOLOGY: { KillTimer(hWnd, TIMER_TOPOLOGY); + if (!Prefs.AdhereTopology) + break; + + int result = VerifyTopology(); - if(VerifyTopology()) + if (result == TOPOLOGY_OK) PostMessage(hWnd, USER_DISPLAYCHANGE, NULL, NULL); - else + else if (result == TOPOLOGY_UNDETERMINED) { + Log(L"No active devices detected when verifying Windows Monitor Topology. Topology feature has been disabled"); + CommunicateWithService("-daemon topology undetermined"); + Prefs.AdhereTopology = false; + } + else if (result == TOPOLOGY_OK_DISABLE) + { + Prefs.AdhereTopology = false; + } + else if (result = TOPOLOGY_ERROR) + { + Prefs.AdhereTopology = false; CommunicateWithService("-daemon topology invalid"); - wstring s = L"A recent change to the system seem to have invalidated the monitor topology configuration. " + wstring s = L"A change to the system has invalidated the monitor topology configuration and the feature has been disabled. " "Please run the configuration guide in the global options to ensure correct operation."; Log(s); @@ -370,7 +410,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) templ.setImagePath(imgpath); templ.setTextField(L"Invalidated monitor topology configuration!", WinToastTemplate::FirstLine); - templ.setTextField(L"A recent change to the system seem to have invalidated the multi-monitor configuration. Please run the configuration guide in the global options again.", WinToastTemplate::SecondLine); + templ.setTextField(L"A change to the system has invalidated the multi-monitor configuration. Please run the configuration guide in the global options.", WinToastTemplate::SecondLine); // templ.addAction(L"Download"); @@ -461,17 +501,15 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_DISPLAYCHANGE: { - if (Prefs.AdhereTopology) { PostMessage(hWnd, USER_DISPLAYCHANGE, NULL, NULL); } - }break; - /* + case WM_DEVICECHANGE: { - if (lParam) + if (Prefs.RemoteStreamingCheck && lParam) { PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; PDEV_BROADCAST_DEVICEINTERFACE lpdbv = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; @@ -479,30 +517,29 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { path = wstring(lpdbv->dbcc_name); + transform(path.begin(), path.end(), path.begin(), ::tolower); + switch (wParam) { case DBT_DEVICEARRIVAL: { - for (auto& dev : usb_list) + for (auto& dev : Prefs.Remote.stream_usb_list) { if (path.find(dev, 0) != wstring::npos) { - wstring s = L"New device connected: "; - s += path; - Log(s); + RemoteStreamingEvent(REMOTE_NVIDIA_CONNECTED); + return true; } } }break; - case DBT_DEVICEREMOVECOMPLETE: { - for (auto& dev : usb_list) + for (auto& dev : Prefs.Remote.stream_usb_list) { if (path.find(dev, 0) != wstring::npos) { - wstring s = L"Device disconnected: "; - s += path; - Log(s); + RemoteStreamingEvent(REMOTE_NVIDIA_NOT_CONNECTED); + return true; } } }break; @@ -511,18 +548,16 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } } }break; - */ + case WM_WTSSESSION_CHANGE: { if (wParam == WTS_REMOTE_CONNECT) { - SetTimer(hMainWnd, TIMER_RDP, TIMER_RDP_DELAY, (TIMERPROC)NULL); + RemoteStreamingEvent(REMOTE_RDP_CONNECTED); } else if (wParam == WTS_REMOTE_DISCONNECT) { - Prefs.DisableSendingViaIPC = false; - CommunicateWithService("-daemon remotedisconnect"); - Prefs.DisableSendingViaIPC = true; // to prevent user idle screen blanking on login screen, which cannot be unblanked without using the remote. + RemoteStreamingEvent(REMOTE_RDP_NOT_CONNECTED); } }break; case WM_COPYDATA: @@ -642,6 +677,10 @@ bool ReadConfigFile() if (!j.empty() && j.is_boolean()) Prefs.bFullscreenCheckEnabled = j.get(); + j = jsonPrefs[JSON_PREFS_NODE][JSON_REMOTESTREAM]; + if (!j.empty() && j.is_boolean()) + Prefs.RemoteStreamingCheck = j.get(); + j = jsonPrefs[JSON_PREFS_NODE][JSON_WHITELIST]; if (!j.empty() && j.size() > 0) { @@ -697,31 +736,30 @@ string narrow(wstring sInput) { } // Send the commandline to the service -void CommunicateWithService(string input, bool OverrideDisable) +void CommunicateWithService(string input) { DWORD dwWritten; if (input == "") return; - if (!Prefs.DisableSendingViaIPC || OverrideDisable) - { - hPipe = CreateFile(PIPENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - if (hPipe != INVALID_HANDLE_VALUE) - { - WriteFile(hPipe, - input.c_str(), - (DWORD)input.length() + 1, // = length of string + terminating '\0' !!! - &dwWritten, - NULL); - Log(widen(input)); - } - else - Log(L"Failed to connect to named pipe. Service may be stopped."); + hPipe = CreateFile(PIPENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - if (hPipe != INVALID_HANDLE_VALUE) - CloseHandle(hPipe); + if (hPipe != INVALID_HANDLE_VALUE) + { + WriteFile(hPipe, + input.c_str(), + (DWORD)input.length() + 1, // = length of string + terminating '\0' !!! + &dwWritten, + NULL); + Log(widen(input)); } + else + Log(L"Failed to connect to named pipe. Service may be stopped."); + + if (hPipe != INVALID_HANDLE_VALUE) + CloseHandle(hPipe); + } void Log(wstring input) @@ -853,6 +891,7 @@ void ReadDeviceConfig() return; } +// get a vector of all currently active LG displays vector QueryDisplays() { vector targets; @@ -872,14 +911,12 @@ static BOOL CALLBACK meproc(HMONITOR hMonitor, HDC hdc, LPRECT lprcMonitor, LPAR UINT32 requiredPaths, requiredModes; vector paths; vector modes; -// DISPLAYCONFIG_TOPOLOGY_ID currentTopologyId; MONITORINFOEX mi; LONG isError = ERROR_INSUFFICIENT_BUFFER; ZeroMemory(&mi, sizeof(mi)); mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); - // wprintf(L"DisplayDevice: %s\n", mi.szDevice); isError = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, &requiredModes); if (isError) @@ -917,7 +954,8 @@ static BOOL CALLBACK meproc(HMONITOR hMonitor, HDC hdc, LPRECT lprcMonitor, LPAR name.header.id = p.targetInfo.id; DisplayConfigGetDeviceInfo(&name.header); wstring FriendlyName = name.monitorFriendlyDeviceName; - if (FriendlyName.find(L"LG TV") != wstring::npos) + transform(FriendlyName.begin(), FriendlyName.end(), FriendlyName.begin(), ::tolower); + if (FriendlyName.find(L"lg tv") != wstring::npos) { DISPLAY_INFO di; di.monitorinfo = mi; @@ -946,7 +984,11 @@ bool CheckDisplayTopology(void) { for (auto &dev : Devices) { - if (narrow(disp.target.monitorDevicePath) == dev.UniqueDeviceKey) + string ActiveDisplay = narrow(disp.target.monitorDevicePath); + string DeviceString = dev.UniqueDeviceKey; + transform(ActiveDisplay.begin(), ActiveDisplay.end(), ActiveDisplay.begin(), ::tolower); + transform(DeviceString.begin(), DeviceString.end(), DeviceString.begin(), ::tolower); + if (ActiveDisplay == DeviceString) { s << dev.DeviceId << " "; } @@ -954,88 +996,226 @@ bool CheckDisplayTopology(void) } } s << "*"; - CommunicateWithService(s.str(),true); + CommunicateWithService(s.str()); return true; } -bool VerifyTopology(void) +int VerifyTopology(void) { bool match = false; - vector displays = QueryDisplays(); if (!Prefs.AdhereTopology) - return true; + return TOPOLOGY_OK_DISABLE; if (Devices.size() == 0) - return true; - if (displays.size() > 0) + return TOPOLOGY_OK_DISABLE; + + vector displays = QueryDisplays(); + if (displays.size() == 0) + return TOPOLOGY_UNDETERMINED; + + for (auto& disp : displays) { + match = false; + for (auto& dev : Devices) { - match = false; - if (dev.Enabled && dev.UniqueDeviceKey != "") - { - for (auto& disp : displays) - { - if (narrow(disp.target.monitorDevicePath) == dev.UniqueDeviceKey) - { - match = true;; - } - } - if (!match) - return false; - } + string ActiveDisplay = narrow(disp.target.monitorDevicePath); + string DeviceString = dev.UniqueDeviceKey; + transform(ActiveDisplay.begin(), ActiveDisplay.end(), ActiveDisplay.begin(), ::tolower); + transform(DeviceString.begin(), DeviceString.end(), DeviceString.begin(), ::tolower); + if (ActiveDisplay == DeviceString) + match = true; } + if (!match) + return TOPOLOGY_ERROR; } - return true; + return TOPOLOGY_OK; } -string WhitelistProcessRunning(void) +bool CheckRunningProcesses(void) { - if (Prefs.WhiteList.size() > 0) - { - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); + bool bWhiteListConfigured = Prefs.WhiteList.size() > 0 ? true : false; + string sWhitelistProcessFound = ""; + bool bWhitelistProessFound = false; + bool bStreamingProcessFound = false; + PROCESSENTRY32 entry; - const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + entry.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(snapshot, &entry)) { - CloseHandle(snapshot); - Log(L"Failed to iterate running processes"); - return ""; - } + const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (!Process32First(snapshot, &entry)) { + CloseHandle(snapshot); + Log(L"Failed to iterate running processes"); + return ""; + } - do { - for (auto &w : Prefs.WhiteList) + do { + if (Prefs.bIdleWhitelistEnabled && bWhiteListConfigured && !bWhitelistProessFound) + { + for (auto& w : Prefs.WhiteList) { if (w.Application != L"") { - if (!_tcsicmp(entry.szExeFile, w.Application.c_str())) + if (!_tcsicmp(entry.szExeFile, w.Application.c_str())) { - CloseHandle(snapshot); - return narrow(w.Name); + if(w.Name != L"") + sWhitelistProcessFound = narrow(w.Name); + else + sWhitelistProcessFound = "Generic"; + bWhitelistProessFound = true; } } } - } while (Process32Next(snapshot, &entry)); + } + if (Prefs.RemoteStreamingCheck && !bStreamingProcessFound) + { + for (auto& w : Prefs.Remote.stream_proc_list) + { + if (!_tcsicmp(entry.szExeFile, w.c_str())) + { + bStreamingProcessFound = true; + } + } + } - CloseHandle(snapshot); - } - return ""; + } while (Process32Next(snapshot, &entry) && !(bStreamingProcessFound && bWhitelistProessFound)); + + CloseHandle(snapshot); + + sCurrentlyRunningWhitelistedProcess = sWhitelistProcessFound; + + return bStreamingProcessFound; } bool FullscreenApplicationRunning(void) { + + WorkaroundFalseFullscreenWindows(); + QUERY_USER_NOTIFICATION_STATE pquns; + if (SHQueryUserNotificationState(&pquns) == S_OK) { if (pquns == QUNS_RUNNING_D3D_FULL_SCREEN || pquns == QUNS_PRESENTATION_MODE || pquns == QUNS_BUSY || - pquns == QUNS_APP) + pquns == QUNS_APP) { return true; } } - return false; } +void WorkaroundFalseFullscreenWindows(void) +{ + EnumWindows(EnumWindowsProc, 0); +} +static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) +{ + if (!IsWindowVisible(hWnd)) + return true; + const wchar_t* const nonRude = L"NonRudeHWND"; + { + wstring window_name = GetWndText(hWnd); + transform(window_name.begin(), window_name.end(), window_name.begin(), ::tolower); + if (window_name.find(L"nvidia geforce overlay") != wstring::npos) + { + if (GetProp(hWnd, nonRude) == NULL) + { + if (SetProp(hWnd, nonRude, INVALID_HANDLE_VALUE)) + { + DWORD recipients = BSM_APPLICATIONS; + if (BroadcastSystemMessage(BSF_POSTMESSAGE | BSF_IGNORECURRENTTASK, &recipients, shellhookMessage, HSHELL_UNDOCUMENTED_FULLSCREEN_EXIT, (LPARAM)hWnd) < 0) + { + Log(L"BroadcastSystemMessage() failed"); + } + else { + CommunicateWithService("-daemon gfe"); + Log(L"Unset NVIDIA GFE overlay fullscreen"); + } + + + return false; + } + } + } + } + return true; +} +void RemoteStreamingEvent(int iEvent) +{ + if (Prefs.RemoteStreamingCheck) + { + bool bConnected = Prefs.Remote.bRemoteCurrentStatusSteam || Prefs.Remote.bRemoteCurrentStatusNvidia || Prefs.Remote.bRemoteCurrentStatusRDP; + switch (iEvent) + { + case REMOTE_STEAM_CONNECTED: + { + if (!bConnected) + { + Log(L"Steam Stream connected."); + Prefs.Remote.bRemoteCurrentStatusSteam = true; + SetTimer(hMainWnd, (UINT_PTR)iEvent, TIMER_REMOTE_DELAY, (TIMERPROC)NULL); + } + }break; + case REMOTE_STEAM_NOT_CONNECTED: + { + if (Prefs.Remote.bRemoteCurrentStatusSteam) + { + Log(L"Steam Stream disconnected."); + Prefs.Remote.bRemoteCurrentStatusSteam = false; + SetTimer(hMainWnd, (UINT_PTR)iEvent, 1000, (TIMERPROC)NULL); + } + }break; + case REMOTE_NVIDIA_CONNECTED: + { + if (!bConnected) + { + Log(L"Nvidia Gamestream connected."); + Prefs.Remote.bRemoteCurrentStatusNvidia = true; + SetTimer(hMainWnd, (UINT_PTR)iEvent, TIMER_REMOTE_DELAY, (TIMERPROC)NULL); + } + }break; + case REMOTE_NVIDIA_NOT_CONNECTED: + { + if (Prefs.Remote.bRemoteCurrentStatusNvidia) + { + Log(L"Nvidia Gamestream disconnected."); + Prefs.Remote.bRemoteCurrentStatusNvidia = false; + SetTimer(hMainWnd, (UINT_PTR)iEvent, 1000, (TIMERPROC)NULL); + } + }break; + case REMOTE_RDP_CONNECTED: + { + if (!bConnected) + { + Log(L"RDP connected."); + Prefs.Remote.bRemoteCurrentStatusRDP = true; + SetTimer(hMainWnd, (UINT_PTR)iEvent, TIMER_REMOTE_DELAY, (TIMERPROC)NULL); + } + }break; + case REMOTE_RDP_NOT_CONNECTED: + { + if (Prefs.Remote.bRemoteCurrentStatusRDP) + { + Log(L"RDP connected."); + Prefs.Remote.bRemoteCurrentStatusRDP = false; + SetTimer(hMainWnd, (UINT_PTR)iEvent, TIMER_REMOTE_DELAY, (TIMERPROC)NULL); + } + }break; + default:break; + } + } + return; +} +wstring GetWndText(HWND hWnd) +{ + int len = GetWindowTextLength(hWnd) + 1; + if (len == 1) + return L""; + vector buf(len); + GetWindowText(hWnd, &buf[0], len); + wstring text = &buf[0]; + return text; +} \ No newline at end of file diff --git a/LGTV Companion User/Daemon.h b/LGTV Companion User/Daemon.h index 13f2e438..c7822d1e 100644 --- a/LGTV Companion User/Daemon.h +++ b/LGTV Companion User/Daemon.h @@ -43,18 +43,20 @@ #define APPNAME_SHORT L"LGTVdaemon" #define APP_PATH L"LGTV Companion" #define APPNAME_FULL L"LGTV Companion Daemon" -#define APP_VERSION L"1.9.0" +#define APP_VERSION L"2.0.0" #define WINDOW_CLASS_UNIQUE L"YOLOx0x0x0181818" #define NOTIFY_NEW_PROCESS 1 #define TIMER_MAIN 18 #define TIMER_IDLE 19 -#define TIMER_RDP 20 -#define TIMER_TOPOLOGY 21 +#define TIMER_TOPOLOGY 20 +#define TIMER_CHECK_PROCESSES 21 -#define TIMER_MAIN_DELAY_WHEN_BUSY 10000 +#define TIMER_MAIN_DELAY_WHEN_BUSY 5000 #define TIMER_MAIN_DELAY_WHEN_IDLE 100 -#define TIMER_RDP_DELAY 10000 +#define TIMER_REMOTE_DELAY 10000 +#define TIMER_TOPOLOGY_DELAY 8000 +#define TIMER_CHECK_PROCESSES_DELAY 5000 #define APP_NEW_VERSION WM_USER+9 #define USER_DISPLAYCHANGE WM_USER+10 @@ -69,16 +71,39 @@ #define JSON_IDLEWHITELIST "IdleWhiteListEnabled" #define JSON_IDLEFULLSCREEN "IdleFullscreen" #define JSON_WHITELIST "IdleWhiteList" +#define JSON_REMOTESTREAM "RemoteStream" + +#define TOPOLOGY_OK 1 +#define TOPOLOGY_ERROR 2 +#define TOPOLOGY_UNDETERMINED 3 +#define TOPOLOGY_OK_DISABLE 4 #define PIPENAME TEXT("\\\\.\\pipe\\LGTVyolo") #define NEWRELEASELINK L"https://github.com/JPersson77/LGTVCompanion/releases" #define VERSIONCHECKLINK L"https://api.github.com/repos/JPersson77/LGTVCompanion/releases" #define DONATELINK L"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=jorgen.persson@gmail.com&lc=US&item_name=Friendly+gift+for+the+development+of+LGTV+Companion&no_note=0&cn=¤cy_code=EUR&bn=PP-DonationsBF:btn_donateCC_LG.gif:NonHosted" -const std::vector usb_list { - L"USB#HID_0955", //nvidia - L"HID#VID_0955", //nvidia - L"USB#VID_413D" //dummy +#define HSHELL_UNDOCUMENTED_FULLSCREEN_EXIT 0x36 + +#define REMOTE_STEAM_CONNECTED 100 +#define REMOTE_STEAM_NOT_CONNECTED 101 +#define REMOTE_NVIDIA_CONNECTED 102 +#define REMOTE_NVIDIA_NOT_CONNECTED 103 +#define REMOTE_RDP_CONNECTED 104 +#define REMOTE_RDP_NOT_CONNECTED 105 + +struct REMOTE_STREAM { + bool bRemoteCurrentStatusNvidia = false; + bool bRemoteCurrentStatusSteam = false; + bool bRemoteCurrentStatusRDP = false; + + const std::vector stream_proc_list{ + L"steam_monitor.exe" //steam server + // L"streaming_client.exe", //steam client + }; + const std::vector stream_usb_list{ + L"usb#vid_0955&pid_b4f0" //nvidia + }; }; struct WHITELIST { @@ -93,11 +118,12 @@ struct PREFS { bool Logging = false; int version = 2; bool ToastInitialised = false; - bool DisableSendingViaIPC = false; bool AdhereTopology = false; bool bIdleWhitelistEnabled = false; bool bFullscreenCheckEnabled = false; std::vector WhiteList; + bool RemoteStreamingCheck = false; + REMOTE_STREAM Remote; }; class WinToastHandler : public WinToastLib::IWinToastHandler @@ -130,19 +156,23 @@ struct DISPLAY_INFO { }; // Forward declarations of functions included in this code module: -LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -bool MessageExistingProcess(void); -bool ReadConfigFile(); -std::wstring widen(std::string); -std::string narrow(std::wstring); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +bool MessageExistingProcess(void); +bool ReadConfigFile(); +std::wstring widen(std::string); +std::string narrow(std::wstring); std::vector stringsplit(std::string, std::string); -void CommunicateWithService(std::string, bool OverrideDisable = false); -void VersionCheckThread(HWND); -void Log(std::wstring input); -void ReadDeviceConfig(); +void CommunicateWithService(std::string); +void VersionCheckThread(HWND); +void Log(std::wstring input); +void ReadDeviceConfig(); std::vector QueryDisplays(); -static BOOL CALLBACK meproc(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData); -bool CheckDisplayTopology(void); -bool VerifyTopology(); -std::string WhitelistProcessRunning(void); -bool FullscreenApplicationRunning(void); \ No newline at end of file +static BOOL CALLBACK meproc(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData); +bool CheckDisplayTopology(void); +int VerifyTopology(); +bool CheckRunningProcesses(void); +bool FullscreenApplicationRunning(void); +void RemoteStreamingEvent(int iEvent); +void WorkaroundFalseFullscreenWindows(void); +static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam); +std::wstring GetWndText(HWND); \ No newline at end of file diff --git a/LGTV Companion User/LGTV Companion User.rc b/LGTV Companion User/LGTV Companion User.rc index 3350749e..ba88904b 100644 --- a/LGTV Companion User/LGTV Companion User.rc +++ b/LGTV Companion User/LGTV Companion User.rc @@ -62,7 +62,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK IDD_MAIN DIALOGEX 0, 0, 309, 191 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Dialog" +CAPTION "- " FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN EDITTEXT IDC_EDIT,7,7,295,134,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | ES_WANTRETURN | WS_VSCROLL