From 5d7d1e0e23b088e6c4a43a0e3b4e900a39c501d5 Mon Sep 17 00:00:00 2001 From: JPersson77 Date: Thu, 20 Apr 2023 21:08:45 +0200 Subject: [PATCH] External api and ipc improvements --- Common/Common.cpp | 783 +++++++++++++++++- Common/Common.h | 105 ++- ...d pipe client - Read Events Example v2.ahk | 121 +++ ...d pipe client - Write Event Example v2.ahk | 70 ++ LGTV Companion Service/Service.cpp | 644 +++++++------- LGTV Companion Service/Service.h | 12 +- LGTV Companion Service/Session.cpp | 253 +++++- LGTV Companion Setup/Product.wxs | 2 +- LGTV Companion UI/LGTV Companion UI.cpp | 441 +++++++--- LGTV Companion UI/LGTV Companion UI.h | 4 +- LGTV Companion UI/LGTV Companion UI.rc | Bin 40994 -> 43292 bytes LGTV Companion UI/resource.h | 13 +- LGTV Companion User/Daemon.cpp | 98 ++- LGTV Companion User/Daemon.h | 1 + 14 files changed, 2094 insertions(+), 453 deletions(-) create mode 100644 Example scripts/Named pipe client - Read Events Example v2.ahk create mode 100644 Example scripts/Named pipe client - Write Event Example v2.ahk diff --git a/Common/Common.cpp b/Common/Common.cpp index 428365df..8516c39f 100644 --- a/Common/Common.cpp +++ b/Common/Common.cpp @@ -142,6 +142,10 @@ bool settings::Preferences::Initialize() { j = jsonPrefs[JSON_PREFS_NODE][JSON_PWRONTIMEOUT]; if (!j.empty() && j.is_number()) PowerOnTimeout = j.get(); + if (PowerOnTimeout < 1) + PowerOnTimeout = 1; + else if (PowerOnTimeout > 100) + PowerOnTimeout = 100; // Logging j = jsonPrefs[JSON_PREFS_NODE][JSON_LOGGING]; if (!j.empty() && j.is_boolean()) @@ -158,6 +162,10 @@ bool settings::Preferences::Initialize() { j = jsonPrefs[JSON_PREFS_NODE][JSON_IDLEBLANKDELAY]; if (!j.empty() && j.is_number()) BlankScreenWhenIdleDelay = j.get(); + if (BlankScreenWhenIdleDelay < 1) + BlankScreenWhenIdleDelay = 1; + else if (BlankScreenWhenIdleDelay > 240) + BlankScreenWhenIdleDelay = 240; // Multi-monitor topology support j = jsonPrefs[JSON_PREFS_NODE][JSON_ADHERETOPOLOGY]; if (!j.empty() && j.is_boolean()) @@ -166,6 +174,10 @@ bool settings::Preferences::Initialize() { j = jsonPrefs[JSON_PREFS_NODE][JSON_IDLEWHITELIST]; if (!j.empty() && j.is_boolean()) bIdleWhitelistEnabled = j.get(); + // User idle mode fullscreen exclusions enabled + j = jsonPrefs[JSON_PREFS_NODE][JSON_IDLE_FS_EXCLUSIONS_ENABLE]; + if (!j.empty() && j.is_boolean()) + bIdleFsExclusionsEnabled = j.get(); // User idle mode, prohibit fullscreen j = jsonPrefs[JSON_PREFS_NODE][JSON_IDLEFULLSCREEN]; if (!j.empty() && j.is_boolean()) @@ -178,18 +190,38 @@ bool settings::Preferences::Initialize() { j = jsonPrefs[JSON_PREFS_NODE][JSON_TOPOLOGYMODE]; if (!j.empty() && j.is_boolean()) TopologyPreferPowerEfficiency = j.get(); + // External API + j = jsonPrefs[JSON_PREFS_NODE][JSON_EXTERNAL_API]; + if (!j.empty() && j.is_boolean()) + ExternalAPI = j.get(); + // Mute Speakers + j = jsonPrefs[JSON_PREFS_NODE][JSON_MUTE_SPEAKERS]; + if (!j.empty() && j.is_boolean()) + MuteSpeakers = j.get(); // User idle mode whitelist j = jsonPrefs[JSON_PREFS_NODE][JSON_WHITELIST]; if (!j.empty() && j.size() > 0) { for (auto& elem : j.items()) { - WHITELIST w; + PROCESSLIST w; w.Application = common::widen(elem.value().get()); w.Name = common::widen(elem.key()); WhiteList.push_back(w); } } + // User idle mode fullscreen exclusions + j = jsonPrefs[JSON_PREFS_NODE][JSON_IDLE_FS_EXCLUSIONS]; + if (!j.empty() && j.size() > 0) + { + for (auto& elem : j.items()) + { + PROCESSLIST w; + w.Application = common::widen(elem.value().get()); + w.Name = common::widen(elem.key()); + FsExclusions.push_back(w); + } + } // initialize the configuration for WebOS devices Devices.clear(); @@ -214,6 +246,10 @@ bool settings::Preferences::Initialize() { if (item.value()[JSON_DEVICE_HDMICTRLNO].is_number()) params.OnlyTurnOffIfCurrentHDMIInputNumberIs = item.value()[JSON_DEVICE_HDMICTRLNO].get(); + if (params.OnlyTurnOffIfCurrentHDMIInputNumberIs < 1) + params.OnlyTurnOffIfCurrentHDMIInputNumberIs = 1; + else if (params.OnlyTurnOffIfCurrentHDMIInputNumberIs > 4) + params.OnlyTurnOffIfCurrentHDMIInputNumberIs = 4; if (item.value()[JSON_DEVICE_ENABLED].is_boolean()) params.Enabled = item.value()[JSON_DEVICE_ENABLED].get(); @@ -226,6 +262,10 @@ bool settings::Preferences::Initialize() { if (item.value()[JSON_DEVICE_WOLTYPE].is_number()) params.WOLtype = item.value()[JSON_DEVICE_WOLTYPE].get(); + if (params.WOLtype < 1) + params.WOLtype = 1; + else if (params.WOLtype > 3) + params.WOLtype = 3; if (item.value()[JSON_DEVICE_SETHDMI].is_boolean()) params.SetHDMIInputOnResume = item.value()[JSON_DEVICE_SETHDMI].get(); @@ -235,6 +275,10 @@ bool settings::Preferences::Initialize() { if (item.value()[JSON_DEVICE_SETHDMINO].is_number()) params.SetHDMIInputOnResumeToNumber = item.value()[JSON_DEVICE_SETHDMINO].get(); + if (params.SetHDMIInputOnResumeToNumber < 1) + params.SetHDMIInputOnResumeToNumber = 1; + else if (params.SetHDMIInputOnResumeToNumber > 4) + params.SetHDMIInputOnResumeToNumber = 4; j = item.value()[JSON_DEVICE_MAC]; if (!j.empty() && j.size() > 0) @@ -245,6 +289,7 @@ bool settings::Preferences::Initialize() { } } params.PowerOnTimeout = PowerOnTimeout; + params.MuteSpeakers = MuteSpeakers; Devices.push_back(params); } return true; @@ -304,8 +349,11 @@ void settings::Preferences::WriteToDisk(void) prefs[JSON_PREFS_NODE][JSON_ADHERETOPOLOGY] = (bool)AdhereTopology; prefs[JSON_PREFS_NODE][JSON_IDLEWHITELIST] = (bool)bIdleWhitelistEnabled; prefs[JSON_PREFS_NODE][JSON_IDLEFULLSCREEN] = (bool)bFullscreenCheckEnabled; + prefs[JSON_PREFS_NODE][JSON_IDLE_FS_EXCLUSIONS_ENABLE] = (bool)bIdleFsExclusionsEnabled; prefs[JSON_PREFS_NODE][JSON_REMOTESTREAM] = (bool)RemoteStreamingCheck; prefs[JSON_PREFS_NODE][JSON_TOPOLOGYMODE] = (bool)TopologyPreferPowerEfficiency; + prefs[JSON_PREFS_NODE][JSON_EXTERNAL_API] = (bool)ExternalAPI; + prefs[JSON_PREFS_NODE][JSON_MUTE_SPEAKERS] = (bool)MuteSpeakers; for (auto& item : EventLogRestartString) prefs[JSON_PREFS_NODE][JSON_EVENT_RESTART_STRINGS].push_back(item); for (auto& item : EventLogShutdownString) @@ -313,6 +361,9 @@ void settings::Preferences::WriteToDisk(void) if (WhiteList.size() > 0) for (auto& w : WhiteList) prefs[JSON_PREFS_NODE][JSON_WHITELIST][common::narrow(w.Name)] = common::narrow(w.Application); + if (FsExclusions.size() > 0) + for (auto& w : FsExclusions) + prefs[JSON_PREFS_NODE][JSON_IDLE_FS_EXCLUSIONS][common::narrow(w.Name)] = common::narrow(w.Application); //Iterate devices int deviceid = 1; @@ -364,4 +415,732 @@ void settings::Preferences::WriteToDisk(void) i.close(); } } -} \ No newline at end of file +} +// PIPESERVER Class Constructor. sName = name of Named Pipe, fcnPtr = pointer to callback function +ipc::PipeServer::PipeServer(std::wstring sName, void (*fcnPtr)(std::wstring)) +{ + if (sName == L"" || fcnPtr == NULL) + return; + + Log(L" "); + Log(L"-----------------------------"); + Log(L"PipeServer Started!"); + + for (int i = 0; i < PIPE_INSTANCES; i++) + { + Buffer[i][PIPE_BUF_SIZE] = '\0'; + Buffer[i][0] = '\0'; + } + + sPipeName = sName; + FunctionPtr = fcnPtr; + + //Security descriptor + PSECURITY_DESCRIPTOR psd = NULL; + BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH]; + psd = (PSECURITY_DESCRIPTOR)sd; + InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(psd, TRUE, (PACL)NULL, FALSE); + SECURITY_ATTRIBUTES sa = { sizeof(sa), psd, FALSE }; + + for (int i = 0; i < PIPE_INSTANCES; i++) + { + // Create a named pipe instance + if ((hPipeHandles[i] = CreateNamedPipe(sPipeName.c_str(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_INSTANCES, + PIPE_BUF_SIZE, PIPE_BUF_SIZE, 1000, &sa)) == INVALID_HANDLE_VALUE) + { + for (int j = 0; j < i; j++) + { + CloseHandle(hPipeHandles[j]); + hPipeHandles[j] = NULL; + } + OnEvent(PIPE_EVENT_ERROR, L"Failed to CreateNamedPipe()", i); + return; + } + // Create an event handle for each pipe instance. This + // will be used to monitor overlapped I/O activity on each pipe. + if ((hEvents[i] = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) + { + for (int j = 0; j < PIPE_INSTANCES; j++) + { + CloseHandle(hPipeHandles[j]); + hPipeHandles[j] = NULL; + } + for (int j = 0; j < i; j++) + { + CloseHandle(hEvents[j]); + hEvents[j] = NULL; + } + OnEvent(PIPE_EVENT_ERROR, L"Failed to CreateEvent()", i); + return; + } + + ZeroMemory(&Ovlap[i], sizeof(OVERLAPPED)); + Ovlap[i].hEvent = hEvents[i]; + + // Listen for client connections using ConnectNamedPipe() + bool bConnected = ConnectNamedPipe(hPipeHandles[i], &Ovlap[i]) == 0 ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + if (!bConnected) + { + if (GetLastError() != ERROR_IO_PENDING) + { + for (int j = 0; j < PIPE_INSTANCES; j++) + { + CloseHandle(hPipeHandles[j]); + hPipeHandles[j] = NULL; + CloseHandle(hEvents[j]); + hEvents[j] = NULL; + } + OnEvent(PIPE_EVENT_ERROR, L"Failed to ConnectNamedPipe() when initialising", i); + return; + } + } + } + //termination event + if((hEvents[PIPE_INSTANCES] = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) + { + for (int j = 0; j < PIPE_INSTANCES; j++) + { + CloseHandle(hPipeHandles[j]); + hPipeHandles[j] = NULL; + CloseHandle(hEvents[j]); + hEvents[j] = NULL; + } + OnEvent(PIPE_EVENT_ERROR, L"Failed to Ccreate termination event"); + } + //termination confirm event + if ((hEvents[PIPE_INSTANCES+1] = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) + { + for (int j = 0; j < PIPE_INSTANCES; j++) + { + CloseHandle(hPipeHandles[j]); + hPipeHandles[j] = NULL; + CloseHandle(hEvents[j]); + hEvents[j] = NULL; + } + CloseHandle(hPipeHandles[PIPE_INSTANCES]); hPipeHandles[PIPE_INSTANCES] = NULL; + OnEvent(PIPE_EVENT_ERROR, L"Failed to Create termination confirm event"); + } + std::thread thread_obj(&PipeServer::WorkerThread, this); + thread_obj.detach(); + +} +// PIPESERVER Class Destructor. +ipc::PipeServer::~PipeServer(void) +{ + Terminate(); +} +// Terminate the PIPESERVER before it is deleted +bool ipc::PipeServer::Terminate() +{ + if (!isRunning()) + return false; + + // Signal worker thread to exit + SetEvent(hEvents[PIPE_INSTANCES]); + //Create a new event and wait for the signal when worker thread is actually exiting + WaitForSingleObject(hEvents[PIPE_INSTANCES+1], 2000); + //Close all handles + for (int i = 0; i < PIPE_INSTANCES; i++) + { + if (hPipeHandles[i]) + { + CloseHandle(hPipeHandles[i]); + hPipeHandles[i] = NULL; + } + if (hEvents[i]) + { + CloseHandle(hEvents[i]); + hEvents[i] = NULL; + } + } + Log(L"PipeServer terminated!"); + return true; +} +// Is the server running +bool ipc::PipeServer::isRunning() +{ + return iState == PIPE_RUNNING ? TRUE : FALSE; +} +//Send message to named pipe(s). sData = message, iPipe(optional) = pipe instance or -1 to send to all instances +bool ipc::PipeServer::Send(std::wstring sData, int iPipe) +{ + return Write(sData, iPipe); +} + +//The worker thread managing the overlapped pipes +void ipc::PipeServer::WorkerThread() +{ + DWORD dwRet; + DWORD dwPipe; + Log(L"Worker Thread started!"); + OnEvent(PIPE_RUNNING); + while (1) + { + if ((dwRet = WaitForMultipleObjects(PIPE_INSTANCES+1, hEvents, FALSE, INFINITE)) == WAIT_FAILED) + { + OnEvent(PIPE_EVENT_ERROR, L"WaitForMultipleObjects() returned WAIT_FAILED."); + goto WORKER_THREAD_END; + } + + dwPipe = dwRet - WAIT_OBJECT_0; + ResetEvent(hEvents[dwPipe]); + + // Check if the termination event has been signalled + if (dwPipe == PIPE_INSTANCES) + { + goto WORKER_THREAD_END; + } + + // Check overlapped results, and if they fail, reestablish + // communication for a new client; otherwise, process read and write operations with the client + if (GetOverlappedResult(hPipeHandles[dwPipe], &Ovlap[dwPipe], &dwBytesTransferred, TRUE) == 0) + { + if (!DisconnectAndReconnect(dwPipe)) + { + OnEvent(PIPE_EVENT_ERROR, L"GetOverlappedResult failed. DisconnecAndReconnect() has Failed.", dwPipe); +// goto WORKER_THREAD_END; + } + else + OnEvent(PIPE_EVENT_ERROR, L"GetOverlappedResult failed. DisconnectAndReconnect() success.", dwPipe); + } + else + { + // Check the state of the pipe. If bWriteData equals + // FALSE, post a read on the pipe for incoming data. + if (!bWriteData[dwPipe]) // PERFORM READ + { + std::wstringstream ss; + ss << L"READ! dwBytesTransferred = " << dwBytesTransferred; + + Log(ss.str()); + + ZeroMemory(&Ovlap[dwPipe], sizeof(OVERLAPPED)); + Ovlap[dwPipe].hEvent = hEvents[dwPipe]; + if (ReadFile(hPipeHandles[dwPipe], Buffer[dwPipe], PIPE_BUF_SIZE, NULL, &Ovlap[dwPipe]) == 0) + { + if (GetLastError() != ERROR_IO_PENDING) + { + if (!DisconnectAndReconnect(dwPipe)) + { + OnEvent(PIPE_EVENT_ERROR, L"ReadFile() failed. DisconnectAndReconnect() failed.", dwPipe); +// goto WORKER_THREAD_END; + } + else + OnEvent(PIPE_EVENT_ERROR, L"ReadFile() failed. DisconnectAndReconnect() success.", dwPipe); + } + else + { + if (dwBytesTransferred > 0) + { + std::wstring sss = L"Read contents: "; + sss += Buffer[dwPipe]; + Log(sss); + OnEvent(PIPE_EVENT_READ, Buffer[dwPipe], dwPipe); + } + } + } + } + else + { + std::wstringstream ss; + ss << L"WRITE! dwBytesTransferred: " << dwBytesTransferred; + Log(ss.str()); + + OnEvent(PIPE_EVENT_SEND, L"", dwPipe); + } + } + } +WORKER_THREAD_END: + Log(L"Worker Thread is exiting!"); + OnEvent(PIPE_EVENT_TERMINATE); + SetEvent(hEvents[PIPE_INSTANCES + 1]); +} + +void ipc::PipeServer::OnEvent(int nEventID, std::wstring sData, int iPipe) +{ + std::wstring msg; + switch (nEventID) + { + case PIPE_RUNNING: + iState = PIPE_RUNNING; + break; + case PIPE_EVENT_TERMINATE: + iState = PIPE_NOT_RUNNING; + break; + case PIPE_EVENT_SEND: + bWriteData[iPipe] = false; + Buffer[iPipe][0] = '/0'; + break; + case PIPE_EVENT_READ: + FunctionPtr(sData); // Send message to external function/callback + Buffer[iPipe][0] = '/0'; + break; + case PIPE_EVENT_ERROR: + msg = L"[ERROR] "; + msg += sData; + Log(msg); +// FunctionPtr(msg); // Send message to externally defined function + break; + default:break; + } +} + +bool ipc::PipeServer::DisconnectAndReconnect(int iPipe) +{ + if (DisconnectNamedPipe(hPipeHandles[iPipe]) == 0) + { + OnEvent(PIPE_EVENT_ERROR, L"DisconnectNamedPipe() failed", iPipe); + return false; + } + + bool bConnected = ConnectNamedPipe(hPipeHandles[iPipe], &Ovlap[iPipe]) == 0 ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + if (!bConnected) + { + if (GetLastError() != ERROR_IO_PENDING) + { + OnEvent(PIPE_EVENT_ERROR, L"ConnectNamedPipe() failed. Severe error on pipe. Close this handle forever.", iPipe); + CloseHandle(hPipeHandles[iPipe]); + } + } + return true; +} +bool ipc::PipeServer::Write(std::wstring& sData, int iPipe) +{ + if (!isRunning()) + return false; + + // WRITE TO A SINGLE INSTANCE + if (iPipe >= 0 && iPipe < PIPE_INSTANCES) + { + ZeroMemory(&Ovlap[iPipe], sizeof(OVERLAPPED)); + Ovlap[iPipe].hEvent = hEvents[iPipe]; + if (WriteFile(hPipeHandles[iPipe], sData.c_str(), (DWORD)(sData.size() + 1) * sizeof(TCHAR), NULL, &Ovlap[iPipe]) == 0) + { + if (GetLastError() != ERROR_IO_PENDING) + { + OnEvent(PIPE_EVENT_ERROR, L"Single pipe WriteFile() failed", iPipe); + return false; + } + else + bWriteData[iPipe] = true; + } + else + bWriteData[iPipe] = true; + } + // WRITE TO ALL INSTANCES + else + { + for (int i = 0; i < PIPE_INSTANCES; i++) + { + ZeroMemory(&Ovlap[i], sizeof(OVERLAPPED)); + Ovlap[i].hEvent = hEvents[i]; + if (WriteFile(hPipeHandles[i], sData.c_str(), (DWORD)(sData.size() + 1) * sizeof(TCHAR), NULL, &Ovlap[i]) == 0) + { + if (GetLastError() == ERROR_IO_PENDING) + bWriteData[i] = true; + } + else + bWriteData[i] = true; + } + } + return true; +} + +// Write to log file +void ipc::PipeServer::Log(std::wstring ss) +{ + //disable + return; + + while (bLock2) + Sleep(MUTEX_WAIT); + bLock2 = true; + std::wstring path = L"c:/programdata/lgtv companion/log2.txt"; + std::ofstream m; + + char buffer[80]; + time_t rawtime; + struct tm timeinfo; + time(&rawtime); + localtime_s(&timeinfo, &rawtime); + strftime(buffer, 80, "%a %H:%M:%S > ", &timeinfo); + puts(buffer); + std::wstring s = common::widen(buffer); + s += ss; + s += L"\n"; + + m.open(path.c_str(), std::ios::out | std::ios::app); + if (m.is_open()) + { + m << common::narrow(s).c_str(); + m.close(); + } + bLock2 = false; +} + + + +// PIPECLIENT Class Constructor. sName = name of Named Pipe, fcnPtr = pointer to callback function +ipc::PipeClient::PipeClient(std::wstring sName, void (*fcnPtr)(std::wstring)) +{ + if (sName == L"" || fcnPtr == NULL) + return; + + Buffer[PIPE_BUF_SIZE] = '\0'; + + Log(L" "); + Log(L"-----------------------------"); + Log(L"PipeClient Started!"); + + sPipeName = sName; + FunctionPtr = fcnPtr; + + if(!Init()) + Log(L"PipeClient Init() failed!"); +} +// PIPECLIENT Class Destructor. +ipc::PipeClient::~PipeClient(void) +{ + Terminate(); +} +//Initialise the PipeClient, start WorkerThread etc +bool ipc::PipeClient::Init() +{ + if (isRunning()) + if(!Terminate()) + return false; + + Buffer[0] = '\0'; + + while (1) + { + + hFile = CreateFile(sPipeName.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) + break; + // Exit if an error other than ERROR_PIPE_BUSY occurs. + if (GetLastError() != ERROR_PIPE_BUSY) + { + hFile = NULL; + OnEvent(PIPE_EVENT_ERROR, L"Failed to open pipe with CreateFile()"); + return false; + } + // All pipe instances are busy, so wait for 2 seconds. + if (!WaitNamedPipe(sPipeName.c_str(), 2000)) + { + hFile = NULL; + OnEvent(PIPE_EVENT_ERROR, L"Failed to open pipe: 2 second wait timed out."); + return false; + } + } + + // Create event handles (incl termination and termination accept event) + for (int i = 0; i < 3; i++) + { + if ((hEvents[i] = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) + { + CloseHandle(hFile); hFile = NULL; + for (int j = 0; j < i; j++) + { + CloseHandle(hEvents[j]); + hEvents[j] = NULL; + } + OnEvent(PIPE_EVENT_ERROR, L"Failed to CreateEvent()"); + return false; + } + } + + ZeroMemory(&Ovlap, sizeof(OVERLAPPED)); + Ovlap.hEvent = hEvents[0]; + if (ReadFile(hFile, Buffer, PIPE_BUF_SIZE, NULL, &Ovlap) == 0) + { + if (GetLastError() != ERROR_IO_PENDING) + { + CloseHandle(hFile); hFile = NULL; + CloseHandle(hEvents[0]); hEvents[0] = NULL; + CloseHandle(hEvents[1]); hEvents[1] = NULL; + CloseHandle(hEvents[2]); hEvents[2] = NULL; + OnEvent(PIPE_EVENT_ERROR, L"Failed to overlapped ReadFile() when initialising"); + CloseHandle(hFile); + return false; + } + } + else + { + std::wstring msg = L"First call to ReadFile() is non-zero. Buffer = "; + msg += Buffer; + Log(msg); + } + + std::thread thread_obj(&PipeClient::WorkerThread, this); + thread_obj.detach(); + + return true; +} +// Terminate the PIPECLIENT +bool ipc::PipeClient::Terminate() +{ + if (!isRunning()) + return false; + + // Signal worker thread to exit + SetEvent(hEvents[1]); + //Wait for the signal when worker thread is actually exiting (max 2s) + WaitForSingleObject(hEvents[2], 2000); + + //Close handles + if(hFile) + { + CloseHandle(hFile); + hFile = NULL; + } + for(int i = 0; i < 3; i++) + { + if(hEvents[i]) + { + CloseHandle(hEvents[i]); + hEvents[i] = NULL; + } + } + + Log(L"PipeClient terminated!"); + return true; +} +// Is the client running +bool ipc::PipeClient::isRunning() +{ + return iState == PIPE_RUNNING ? TRUE : FALSE; +} +//Send message to named pipe. sData = message +bool ipc::PipeClient::Send(std::wstring sData) +{ + return Write(sData); +} + +//The worker thread managing the overlapped pipes +void ipc::PipeClient::WorkerThread() +{ + DWORD dwRet; + DWORD dwPipe; + Log(L"Worker Thread started!"); + OnEvent(PIPE_RUNNING); + while (1) + { + if ((dwRet = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE)) == WAIT_FAILED) + { + OnEvent(PIPE_EVENT_ERROR, L"WaitForMultipleObjects() returned WAIT_FAILED."); + goto WORKER_THREAD_END; + } + dwPipe = dwRet - WAIT_OBJECT_0; + ResetEvent(hEvents[dwPipe]); + + // Check if the termination event has been signalled + if (dwPipe == 1) + { + goto WORKER_THREAD_END;; + } + + // Check overlapped results, and if they fail, reestablish + // communication; otherwise, process read and write operations + if (GetOverlappedResult(hFile, &Ovlap, &dwBytesTransferred, TRUE) == 0) + { + if (!DisconnectAndReconnect()) + { + OnEvent(PIPE_EVENT_ERROR, L"GetOverlappedResult failed. DisconnectAndReconnect() failed."); + goto WORKER_THREAD_END;; + } + else + OnEvent(PIPE_EVENT_ERROR, L"GetOverlappedResult failed. DisconnectAndReconnect() success."); + } + else + { + // Check the state of the pipe. If bWriteData equals + // FALSE, post a read on the pipe for incoming data. + if (!bWriteData) // PERFORM READ + { + std::wstringstream ss; + ss << L"READ! dwBytesTransferred = " << dwBytesTransferred; + Log(ss.str()); + + ZeroMemory(&Ovlap, sizeof(OVERLAPPED)); + Ovlap.hEvent = hEvents[0]; + if (ReadFile(hFile, Buffer, PIPE_BUF_SIZE, NULL, &Ovlap) == 0) + { + if (GetLastError() != ERROR_IO_PENDING) + { + Log(L"ReadFile error. Disconnect and reconnect"); + if (!DisconnectAndReconnect()) + { + OnEvent(PIPE_EVENT_ERROR, L"ReadFile() failed. DisconnectAndReconnect() failed."); + goto WORKER_THREAD_END;; + } + else + OnEvent(PIPE_EVENT_ERROR, L"ReadFile() failed. DisconnectAndReconnect() success."); + } + else + OnEvent(PIPE_EVENT_READ, Buffer); + } + } + else + { + std::wstringstream ss; + ss << L"WRITE! dwBytesTransferred: " << dwBytesTransferred; + Log(ss.str()); + + OnEvent(PIPE_EVENT_SEND); + } + } + } +WORKER_THREAD_END: + Log(L"Worker Thread is exiting!"); + OnEvent(PIPE_EVENT_TERMINATE); + SetEvent(hEvents[2]); +} + +void ipc::PipeClient::OnEvent(int nEventID, std::wstring sData) +{ + std::wstring msg; + switch (nEventID) + { + case PIPE_RUNNING: + iState = PIPE_RUNNING; + break; + case PIPE_EVENT_TERMINATE: + iState = PIPE_NOT_RUNNING; + break; + case PIPE_EVENT_SEND: + bWriteData = false; + break; + case PIPE_EVENT_READ: + FunctionPtr(sData); // Send message to external function/callback + break; + case PIPE_EVENT_ERROR: + msg = L"[ERROR] "; + msg += sData; + Log(msg); + // FunctionPtr(msg); // Send message to externally defined function + break; + default:break; + } +} + +bool ipc::PipeClient::DisconnectAndReconnect() +{ + if (CloseHandle(hFile) == 0) + { + OnEvent(PIPE_EVENT_ERROR, L"CloseHandle() failed"); + return false; + } + + while (1) + { + hFile = CreateFile(sPipeName.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) + break; + // Exit if an error other than ERROR_PIPE_BUSY occurs. + if (GetLastError() != ERROR_PIPE_BUSY) + { + OnEvent(PIPE_EVENT_ERROR, L"DisconnectAndReconnect() - Failed to open pipe with CreateFile()"); + return false; + } + // All pipe instances are busy, so wait for 2 seconds. + if (!WaitNamedPipe(sPipeName.c_str(), 2000)) + { + OnEvent(PIPE_EVENT_ERROR, L"DisconnectAndReconnect() - Failed to open pipe: 2 second wait timed out."); + return false; + } + } + ZeroMemory(&Ovlap, sizeof(OVERLAPPED)); + Ovlap.hEvent = hEvents[0]; + if (ReadFile(hFile, Buffer, PIPE_BUF_SIZE, NULL, &Ovlap) == 0) + { + if (GetLastError() != ERROR_IO_PENDING) + { + CloseHandle(hFile); hFile = NULL; + CloseHandle(hEvents[0]); hEvents[0] = NULL; + CloseHandle(hEvents[1]); hEvents[1] = NULL; + CloseHandle(hEvents[2]); hEvents[2] = NULL; + OnEvent(PIPE_EVENT_ERROR, L"Failed to overlapped ReadFile() when initialising"); + CloseHandle(hFile); + return false; + } + } + return true; +} +bool ipc::PipeClient::Write(std::wstring& sData) +{ + if (!isRunning()) + if (!Init()) + return false; + + // WRITE TO THE PIPE INSTANCE + ZeroMemory(&Ovlap, sizeof(OVERLAPPED)); + Ovlap.hEvent = hEvents[0]; + if (WriteFile(hFile, sData.c_str(), (DWORD)(sData.size()+1)*sizeof(TCHAR), NULL, &Ovlap) == 0) + { + if (GetLastError() != ERROR_IO_PENDING) + { + OnEvent(PIPE_EVENT_ERROR, L"WriteFile() failed"); + return false; + } + else + bWriteData = true; + } + else + bWriteData = true; + return true; +} + +// Write to log file +void ipc::PipeClient::Log(std::wstring ss) +{ + //disable + return; + + while (bLock) + Sleep(MUTEX_WAIT); + bLock = true; + std::wstring path = L"c:/programdata/lgtv companion/log3.txt"; + std::ofstream m; + + char buffer[80]; + time_t rawtime; + struct tm timeinfo; + time(&rawtime); + localtime_s(&timeinfo, &rawtime); + strftime(buffer, 80, "%a %H:%M:%S > ", &timeinfo); + puts(buffer); + std::wstring s = common::widen(buffer); + WCHAR szPath[MAX_PATH]; + + GetModuleFileNameW(NULL, szPath, MAX_PATH); + s += L"["; s += szPath; s += L"] "; + s += ss; + s += L"\n"; + + + m.open(path.c_str(), std::ios::out | std::ios::app); + if (m.is_open()) + { + m << common::narrow(s).c_str(); + m.close(); + } + bLock = false; +} diff --git a/Common/Common.h b/Common/Common.h index 68c347a8..2de4a725 100644 --- a/Common/Common.h +++ b/Common/Common.h @@ -7,11 +7,15 @@ #include #include #include +#include +//#include +#include +#include #include // common general application definitions #define APPNAME L"LGTV Companion" -#define APP_VERSION L"2.1.6" +#define APP_VERSION L"2.2.0" #define CONFIG_FILE L"config.json" #define LOG_FILE L"Log.txt" #define WINDOW_CLASS_UNIQUE L"YOLOx0x0x0181818" @@ -19,6 +23,8 @@ #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" +#define DISCORDLINK L"https://discord.gg/7KkTPrP3fq" +#define MUTEX_WAIT 10 // thread wait in ms // common preferences definitions #define JSON_PREFS_NODE "LGTV Companion" @@ -34,8 +40,12 @@ #define JSON_IDLEWHITELIST "IdleWhiteListEnabled" #define JSON_IDLEFULLSCREEN "IdleFullscreen" #define JSON_WHITELIST "IdleWhiteList" +#define JSON_IDLE_FS_EXCLUSIONS_ENABLE "IdleFsExclusionsEnabled" +#define JSON_IDLE_FS_EXCLUSIONS "IdleFsExclusions" #define JSON_REMOTESTREAM "RemoteStream" #define JSON_TOPOLOGYMODE "TopologyPreferPowerEfficient" +#define JSON_EXTERNAL_API "ExternalAPI" +#define JSON_MUTE_SPEAKERS "MuteSpeakers" #define JSON_DEVICE_NAME "Name" #define JSON_DEVICE_IP "IP" #define JSON_DEVICE_UNIQUEKEY "UniqueDeviceKey" @@ -56,6 +66,7 @@ #define WOL_SUBNETBROADCAST 3 #define WOL_DEFAULTSUBNET L"255.255.255.0" +// commandline events #define APP_CMDLINE_ON 1 #define APP_CMDLINE_OFF 2 #define APP_CMDLINE_AUTOENABLE 3 @@ -67,11 +78,25 @@ #define APP_CMDLINE_SETHDMI2 9 #define APP_CMDLINE_SETHDMI3 10 #define APP_CMDLINE_SETHDMI4 11 +#define APP_CMDLINE_MUTE 12 +#define APP_CMDLINE_UNMUTE 13 +// WOL types #define WOL_NETWORKBROADCAST 1 #define WOL_IPSEND 2 #define WOL_SUBNETBROADCAST 3 +//IPC +#define PIPE_BUF_SIZE 1024 +#define PIPE_INSTANCES 5 +#define PIPE_NOT_RUNNING 99 +#define PIPE_EVENT_ERROR 100 +#define PIPE_EVENT_READ 101 // Data was read +#define PIPE_EVENT_SEND 102 // Write data +#define PIPE_RUNNING 103 // Server is running +#define PIPE_EVENT_TERMINATE 120 // Terminate + + // common forward function declarations namespace jpersson77 { namespace common { @@ -94,6 +119,7 @@ namespace jpersson77 { bool bRemoteCurrentStatusRDP = false; bool bRemoteCurrentStatusSunshine = false; std::wstring sCurrentlyRunningWhitelistedProcess = L""; + std::wstring sCurrentlyRunningFsExcludedProcess = L""; std::string Sunshine_Log_File = ""; uintmax_t Sunshine_Log_Size = 0; @@ -104,7 +130,7 @@ namespace jpersson77 { L"usb#vid_0955&pid_b4f0" //nvidia }; }; - struct WHITELIST { // whitelist info + struct PROCESSLIST { // whitelist info std::wstring Name; std::wstring Application; }; @@ -125,6 +151,7 @@ namespace jpersson77 { bool SetHDMIInputOnResume = false; int SetHDMIInputOnResumeToNumber = 1; bool SSL = true; + bool MuteSpeakers = false; //service.h int BlankScreenWhenIdleDelay = 10; @@ -148,9 +175,13 @@ namespace jpersson77 { bool AdhereTopology = false; bool bIdleWhitelistEnabled = false; bool bFullscreenCheckEnabled = false; - std::vector WhiteList; + std::vector WhiteList; + bool bIdleFsExclusionsEnabled = false; + std::vector FsExclusions; bool RemoteStreamingCheck = false; bool TopologyPreferPowerEfficiency = true; + bool ExternalAPI = false; + bool MuteSpeakers = false; std::wstring DataPath; std::vector Devices; @@ -162,4 +193,72 @@ namespace jpersson77 { REMOTE_STREAM Remote; }; } + namespace ipc + { + class PipeServer + { + public: + PipeServer(std::wstring, void (*fcnPtr)(std::wstring)); + ~PipeServer(void); + bool Send(std::wstring sData, int iPipe = -1); + bool Terminate(); + bool isRunning(); + + //DEBUG + void Log(std::wstring); + std::atomic_int bLock2 = false;; + + private: + void OnEvent(int, std::wstring sData = L"", int iPipe = -1); + void WorkerThread(); + bool DisconnectAndReconnect(int); + bool Write(std::wstring&, int); + + HANDLE hPipeHandles[PIPE_INSTANCES] = {}; + DWORD dwBytesTransferred; + TCHAR Buffer[PIPE_INSTANCES][PIPE_BUF_SIZE+1]; + OVERLAPPED Ovlap[PIPE_INSTANCES]; + HANDLE hEvents[PIPE_INSTANCES + 2] = {}; // include termination and termination confirm events + std::atomic_bool bWriteData[PIPE_INSTANCES] = {}; + void (*FunctionPtr)(std::wstring); + std::wstring sPipeName; + std::atomic_int iState = PIPE_NOT_RUNNING; + + }; + + class PipeClient + { + public: + PipeClient(std::wstring, void (*fcnPtr)(std::wstring)); + ~PipeClient(void); + bool Init(); + bool Send(std::wstring); + bool Terminate(); + bool isRunning(); + + + //DEBUG + void Log(std::wstring); + std::atomic_int bLock = false; + + private: + void OnEvent(int, std::wstring sData = L""); + void WorkerThread(); + bool DisconnectAndReconnect(); + bool Write(std::wstring&); + + HANDLE hFile = NULL; + + HANDLE hPipeHandle = {}; + DWORD dwBytesTransferred; + TCHAR Buffer[PIPE_BUF_SIZE+1]; + OVERLAPPED Ovlap; + HANDLE hEvents[3] = {}; // overlapped event, terminate event, terminate accept event + void (*FunctionPtr)(std::wstring); + std::wstring sPipeName; + std::atomic_int iState = PIPE_NOT_RUNNING; + std::atomic_bool bWriteData = {}; + + }; + } } \ No newline at end of file diff --git a/Example scripts/Named pipe client - Read Events Example v2.ahk b/Example scripts/Named pipe client - Read Events Example v2.ahk new file mode 100644 index 00000000..ae3589d0 --- /dev/null +++ b/Example scripts/Named pipe client - Read Events Example v2.ahk @@ -0,0 +1,121 @@ +/* + Version 1.1 + + This is a sample script using AutoHotKey (AHK) v2 for accessing the API for LGTV Companion. This sample script + illustrate how to listen to events sent from LGTV Companion. Note that bi-directional communication is + supported and sending commands/events to LGTV Companion is covered in another example script. + + Please install AHK by downloading the installer from here: https://www.autohotkey.com/ and then install. + + AutoHotkey is a free, open-source scripting language for Windows that allows users to easily create small + to complex scripts for all kinds of tasks such as: form fillers, auto-clicking, macros, etc. + + PLEASE NOTE that the LGTV Companion API is using named pipes for its intra-process communication so any + scripting or programming language which can access named pipes (which is surely the vast majority) can be used + to communicate with LGTV Companion. + + Remember to enable the "External API" option in LGTV Companion. + + For discussions, tips and tricks etc please use Discord: https://discord.gg/7KkTPrP3fq + +*/ + +; SCRIPT STARTS HERE + +; This script will run under v2 of AHK +#Requires AutoHotkey v2.0 + +;Only one instance of the script can run +#SingleInstance Ignore + +; The following DllCall() is optional: it tells the OS to shut down this script last (after all other applications). +DllCall("kernel32.dll\SetProcessShutdownParameters", "UInt", 0x101, "UInt", 0) + +; The name of the named pipe to connect to +PipeName := "\\.\pipe\LGTVyolo" + +; Connect to the Named Pipe using CreateFile() https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea +Pipe := DllCall("CreateFile", "Str", PipeName, "UInt", 0x80000000 | 0x40000000, "UInt", 0, "Ptr", 0, "UInt", 0x00000003, "UInt", 0x00000080, "Ptr", 0) + +; Set the max message length to 1024 bytes (maximum supported currently) +VarSetStrCapacity(&Buff , 1024) +Bytes := 0 + +;read messages in an infinite loop +Go := true +while (Go = true) +{ + if (Pipe != -1) + { + ; Read synchronously (i.e. blocking) using ReadFile() https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile + if (DllCall("ReadFile", "Ptr", Pipe, "Str", Buff, "UInt", 512, "UInt*", Bytes, "Ptr", 0)) + { + Log(Buff) + + Switch Buff + { + Case "SYSTEM_DISPLAYS_OFF": ; System signals displays OFF + { + ; Do stuff here + } + Case "SYSTEM_DISPLAYS_OFF_DIMMED": ; System signals displays OFF, BUT DIMMED (usually occurs in laptops) + { + ; Do stuff here + } + Case "SYSTEM_DISPLAYS_ON": ; System signals displays ON + { + ; Do stuff here + } + Case "SYSTEM_USER_BUSY": ; System is busy / user away mode is inactive + { + ; Do stuff here + } + Case "SYSTEM_USER_IDLE": ; System is idle / user away mode is active + { + ; Do stuff here + } + Case "SYSTEM_REBOOT": ; System is about to reboot + { + ; Do stuff here + } + Case "SYSTEM_SHUTDOWN": ; System is about to shutdown + { + ; Do stuff here + } + Case "SYSTEM_RESUME": ; System is resuming from sleep/hibernate + { + ; Do stuff here + } + Case "SYSTEM_SUSPEND": ; System is suspending to sleep/hibernate + { + ; Do stuff here + } + Default: + { + } + } + } + else + { + Log("ERROR, Failed to ReadFile(). Script will Terminate!") + Go := false + } + } + else + { + Log("ERROR, Pipe handle is invalid. Script will Terminate!") + Go := false + } +} + +; Close the handle +DllCall("CloseHandle", "Ptr", Pipe) + +ExitApp + +; This function will write 'message' to log.txt in the same directory as the script +Log(message) +{ + FileAppend a_hour . ":" . a_min . ":" . a_sec . " > " . message "`n", "log.txt" +} + diff --git a/Example scripts/Named pipe client - Write Event Example v2.ahk b/Example scripts/Named pipe client - Write Event Example v2.ahk new file mode 100644 index 00000000..0763ecc4 --- /dev/null +++ b/Example scripts/Named pipe client - Write Event Example v2.ahk @@ -0,0 +1,70 @@ +/* + Version 1.0 + + This is a sample script using AutoHotKey (AHK) for accessing the API for LGTV Companion. This sample script + illustrate how to send events to LGTV Companion. Note that bi-directional communication is + supported and receiving commands/events to LGTV Companion is covered in another example script. + + Please install AHK by downloading the installer from here: https://www.autohotkey.com/ and then install. + + AutoHotkey is a free, open-source scripting language for Windows that allows users to easily create small + to complex scripts for all kinds of tasks such as: form fillers, auto-clicking, macros, etc. + + PLEASE NOTE that the LGTV Companion API is using named pipes for its intra-process communication so any scripting or programming language which can access named pipes (which is surely the vast majority) can be used + to communicate with LGTV Companion. + + Remember to enable the "External API" option in LGTV Companion. + + For discussions, tips and tricks etc please use Discord: https://discord.gg/7KkTPrP3fq + +*/ + +; SCRIPT STARTS HERE + +; This script will run under v2 of AHK +#Requires AutoHotkey v2.0 + +;Only one instance of the script can run +#SingleInstance Ignore + +; Set keyboard shortcut CTRL+SHIFT+M to mute TV-speaker (Device1) +^+m:: +{ + Send_LGTV_Message("-mute device1") + return +} + +; Set keyboard shortcut CTRL+SHIFT+U to unmute TV-speaker (Device1) +^+u:: +{ + Send_LGTV_Message("-unmute device1") + return +} + +; This function will send 'message' to the LGTV Companion service (currently the messages that can be sent correspond to the command line parameters) +Send_LGTV_Message(message) +{ + ; The name of the named pipe to connect to + PipeName := "\\.\pipe\LGTVyolo" + + ; Connect to the Named Pipe using CreateFile() https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + Pipe := DllCall("CreateFile", "Str", PipeName, "UInt", 0x80000000 | 0x40000000, "UInt", 0x00000001 | 0x00000002, "Ptr", 0, "UInt", 0x00000003, "UInt", 0, "Ptr", 0) + if(Pipe = -1) + { + MsgBox "Could not connect to the LGTV Companion service. Check that the service is running!" + Return + } + + ;Write to the named pipe using WriteFile() https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile + If !DllCall("WriteFile", "Ptr", Pipe, "Str", message, "UInt", (StrLen(message)+1)*2, "UInt*", 0, "Ptr", 0) + MsgBox "WriteFile failed: %ErrorLevel%/%A_LastError%" + + ;Tiny bit of delay so that closing the handle doesn't abort the (overlapped) read by the LGTV Companion Service + Sleep 100 + + ;Close the handle + DllCall("CloseHandle", "Ptr", Pipe) + + Return +} + diff --git a/LGTV Companion Service/Service.cpp b/LGTV Companion Service/Service.cpp index e9e6eb09..248598d2 100644 --- a/LGTV Companion Service/Service.cpp +++ b/LGTV Companion Service/Service.cpp @@ -18,6 +18,9 @@ WSADATA WSAData; mutex log_mutex; vector HostIPs; bool bIdleLog = true; +//HANDLE h_API_Pipe = INVALID_HANDLE_VALUE; +ipc::PipeServer* pPipe; + settings::Preferences Prefs; vector DeviceCtrlSessions; //CSession objects manage network connections with Display @@ -234,7 +237,11 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) SvcReportEvent(EVENTLOG_ERROR_TYPE, L"RegisterServiceCtrlHandler"); return; } - + + //spawn PipeServer for managing intra process communication + ipc::PipeServer Pipe(PIPENAME, NamedPipeCallback); + pPipe = &Pipe; + // These SERVICE_STATUS members remain as set here gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; gSvcStatus.dwServiceSpecificExitCode = 0; @@ -305,10 +312,7 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) NULL, //Context (EVT_SUBSCRIBE_CALLBACK)SubCallback, //callback EvtSubscribeToFutureEvents); - - //spawn a thread for managing intra process communication - thread thread_obj(IPCThread); - thread_obj.detach(); + //make sure the process is shutdown as early as possible if (SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY)) @@ -318,16 +322,18 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); - //register to receive power notifications - gPs1 = RegisterPowerSettingNotification(gSvcStatusHandle, &(GUID_CONSOLE_DISPLAY_STATE), DEVICE_NOTIFY_SERVICE_HANDLE); - SvcReportEvent(EVENTLOG_INFORMATION_TYPE, L"The service has started."); DispatchSystemPowerEvent(SYSTEM_EVENT_BOOT); + //register to receive power notifications + gPs1 = RegisterPowerSettingNotification(gSvcStatusHandle, &(GUID_CONSOLE_DISPLAY_STATE), DEVICE_NOTIFY_SERVICE_HANDLE); + // Wait until service stops WaitForSingleObject(ghSvcStopEvent, INFINITE); + Pipe.Terminate(); + Sleep(1000); //terminate the device control sessions @@ -389,6 +395,7 @@ DWORD SvcCtrlHandler(DWORD dwCtrl, DWORD dwEventType, LPVOID lpEventData, LPVOI Sleep(100); } while (bThreadNotFinished); + // Signal the service to stop. SetEvent(ghSvcStopEvent); ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); @@ -512,6 +519,7 @@ DWORD SvcCtrlHandler(DWORD dwCtrl, DWORD dwEventType, LPVOID lpEventData, LPVOI Prefs.DisplayIsCurrentlyRequestedPoweredOnByWindows = false; } +// Sleep(5000); ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 20000); do @@ -644,6 +652,8 @@ bool SetSessionKey(string Key, string deviceid) // Broadcast power events (display on/off, resuming, rebooting etc) to the device objects bool DispatchSystemPowerEvent(DWORD dwMsg) { + SendToNamedPipe(dwMsg); + if (DeviceCtrlSessions.size() == 0) { Log("WARNING! No Devices in DispatchSystemPowerEvent()."); @@ -772,301 +782,13 @@ DWORD WINAPI SubCallback(EVT_SUBSCRIBE_NOTIFY_ACTION Action, PVOID UserContext, EventCallbackStatus = SYSTEM_EVENT_UNSURE; Log("WARNING! Event subscription callback: Could not detect whether system is shutting down or restarting. Please check 'additional settings' in the UI."); } - + + if(EventCallbackStatus) + SendToNamedPipe(EventCallbackStatus); + return 0; } -// THREAD: The intra process communications (named pipe) thread. Runs for the duration of the service -void IPCThread(void) -{ - HANDLE hPipe; - char buffer[1024]; - DWORD dwRead; - - PSECURITY_DESCRIPTOR psd = NULL; - BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH]; - psd = (PSECURITY_DESCRIPTOR)sd; - InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION); - SetSecurityDescriptorDacl(psd, TRUE, (PACL)NULL, FALSE); - SECURITY_ATTRIBUTES sa = { sizeof(sa), psd, FALSE }; - - hPipe = CreateNamedPipe(PIPENAME, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, // FILE_FLAG_FIRST_PIPE_INSTANCE is not needed but forces CreateNamedPipe(..) to fail if the pipe already exists... - 1, - 1024 * 16, - 1024 * 16, - NMPWAIT_USE_DEFAULT_WAIT, - &sa); - while (hPipe != INVALID_HANDLE_VALUE) - { - if (ConnectNamedPipe(hPipe, NULL) != FALSE) // wait for someone to connect to the pipe - { - while (ReadFile(hPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL) != FALSE) - { - /* add terminating zero */ - buffer[dwRead] = '\0'; - string t = buffer; - transform(t.begin(), t.end(), t.begin(), ::tolower); - - vector cmd = common::stringsplit(t, " "); - if (cmd.size() > 0) - { - int param1 = 0; - - for (auto& param : cmd) - { - if (param == "-daemon") - param1 = APP_IPC_DAEMON; - else if (param == "-poweron") - param1 = APP_CMDLINE_ON; - else if (param == "-poweroff") - param1 = APP_CMDLINE_OFF; - else if (param == "-autoenable") - param1 = APP_CMDLINE_AUTOENABLE; - else if (param == "-autodisable") - param1 = APP_CMDLINE_AUTODISABLE; - else if (param == "-screenon") - param1 = APP_CMDLINE_SCREENON; - else if (param == "-screenoff") - param1 = APP_CMDLINE_SCREENOFF; - else if (param == "-sethdmi1") - param1 = APP_CMDLINE_SETHDMI1; - else if (param == "-sethdmi2") - param1 = APP_CMDLINE_SETHDMI2; - else if (param == "-sethdmi3") - param1 = APP_CMDLINE_SETHDMI3; - else if (param == "-sethdmi4") - param1 = APP_CMDLINE_SETHDMI4; - else if (param == "-clearlog") - { - string w = "IPC, clear log "; - wstring log = Prefs.DataPath; - log += LOG_FILE; - w += common::narrow(log); - Log(w); - DeleteFile(log.c_str()); - } - else if (param == "-idle") - { - if (Prefs.BlankScreenWhenIdle) - Log("IPC, Forcing user idle mode!"); - else - Log("IPC, Can not force user idle mode, as the feature is not enabled in the global options!"); - } - else if (param == "-unidle") - { - if (Prefs.BlankScreenWhenIdle) - Log("IPC, Unsetting user idle mode!"); - else - Log("IPC, Can not unset user idle mode, as the feature is not enabled in the global options!"); - } - else if (param1 > 0) - { - if (param1 == APP_IPC_DAEMON) - { - if (param == "errorconfig") - Log("IPC, Daemon, Could not read configuration file. Daemon exiting!"); - else if (param == "started") - Log("IPC, Daemon has started."); - else if (param == "newversion") - { - wstring s = L"IPC, Daemon, a new version of this app is available for download here: "; - s += NEWRELEASELINK; - Log(common::narrow(s)); - } - else if (param == "userbusy") - { - if (!bIdleLog) - { - Log("IPC, User is not idle."); - bIdleLog = true; - } - DispatchSystemPowerEvent(SYSTEM_EVENT_USERBUSY); - } - else if (param == "useridle") - { - if (bIdleLog) - { - Log("IPC, User is idle."); - bIdleLog = false; - } - DispatchSystemPowerEvent(SYSTEM_EVENT_USERIDLE); - } - else if (param == "remote_connect") - { - Log("IPC, Remote streaming client connected. Managed devices will power off."); - DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYOFF); - - for (auto& d : DeviceCtrlSessions) - { - d.RemoteHostConnected(); - } - } - else if (param == "remote_disconnect") - { - for (auto& d : DeviceCtrlSessions) - { - d.RemoteHostDisconnected(); - } - if (Prefs.DisplayIsCurrentlyRequestedPoweredOnByWindows) - { - Log("IPC, Remote streaming client disconnected. Powering on managed displays"); - DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYON); - } - else - Log("IPC, Remote streaming client disconnected. Managed displays will remain powered off,"); - } - else if (param == "topology") - { - param1 = APP_IPC_DAEMON_TOPOLOGY; - for (auto& d : DeviceCtrlSessions) - { - 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 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; - string TopologyDevices; - for (auto& d : DeviceCtrlSessions) - { - TopologyDevices += d.DeviceID; - TopologyDevices += ":"; - TopologyDevices += d.GetTopology() ? "ON " : "OFF "; - } - - s = "IPC, windows monitor topology was changed. "; - if (TopologyDevices == "") - s += "No devices configured."; - else - s += TopologyDevices; - Log(s); - DispatchSystemPowerEvent(SYSTEM_EVENT_TOPOLOGY); - } - else - { - for (auto& d : DeviceCtrlSessions) - { - string id = d.DeviceID; - transform(id.begin(), id.end(), id.begin(), ::tolower); - if (param == id) - { - d.SetTopology(true); - } - } - } - } - - else - { - for (auto& device : DeviceCtrlSessions) - { - settings::DEVICE s = device.GetParams(); - string id = s.DeviceId; - string name = s.Name; - transform(id.begin(), id.end(), id.begin(), ::tolower); - transform(name.begin(), name.end(), name.begin(), ::tolower); - - if (param1 == APP_CMDLINE_ON && (id == param || name == param)) - { - string w = "IPC, force power ON: "; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCEON); - } - else if (param1 == APP_CMDLINE_OFF && (id == param || name == param)) - { - string w = "IPC, force power OFF: "; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCEOFF); - } - else if (param1 == APP_CMDLINE_SCREENON && (id == param || name == param)) - { - string w = "IPC, force screen ON: "; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCEON); - } - else if (param1 == APP_CMDLINE_SCREENOFF && (id == param || name == param)) - { - string w = "IPC, force screen OFF: "; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCESCREENOFF); - } - else if (param1 == APP_CMDLINE_AUTOENABLE && (id == param || name == param)) - { - string w = "IPC, automatic management is temporarily enabled (effective until restart of service): "; - w += param; - Log(w); - - device.Run(); - } - else if (param1 == APP_CMDLINE_AUTODISABLE && (id == param || name == param)) - { - string w = "IPC, automatic management is temporarily disabled (effective until restart of service): "; - w += param; - Log(w); - device.Stop(); - } - else if (param1 == APP_CMDLINE_SETHDMI1 && (id == param || name == param)) - { - string w = "IPC, set HDMI input 1:"; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 1); - } - else if (param1 == APP_CMDLINE_SETHDMI2 && (id == param || name == param)) - { - string w = "IPC, set HDMI input 2:"; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 2); - } - else if (param1 == APP_CMDLINE_SETHDMI3 && (id == param || name == param)) - { - string w = "IPC, set HDMI input 3:"; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 3); - } - else if (param1 == APP_CMDLINE_SETHDMI4 && (id == param || name == param)) - { - string w = "IPC, set HDMI input 4:"; - w += param; - Log(w); - device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 4); - } - } - } - } - } - } - } - } - DisconnectNamedPipe(hPipe); - } - return; -} - // Get the local host ip, e.g 192.168.1.x vector GetOwnIP(void) { @@ -1151,4 +873,324 @@ void InitSessions(void) DeviceCtrlSessions.push_back(S); Log(s.str()); } -} \ No newline at end of file +} +void NamedPipeCallback(std::wstring message) +{ + string t = common::narrow(message); + transform(t.begin(), t.end(), t.begin(), ::tolower); + + vector cmd = common::stringsplit(t, " "); + if (cmd.size() > 0) + { + int param1 = 0; + + for (auto& param : cmd) + { + if (param == "-daemon") + param1 = APP_IPC_DAEMON; + else if (param == "-poweron") + param1 = APP_CMDLINE_ON; + else if (param == "-poweroff") + param1 = APP_CMDLINE_OFF; + else if (param == "-autoenable") + param1 = APP_CMDLINE_AUTOENABLE; + else if (param == "-autodisable") + param1 = APP_CMDLINE_AUTODISABLE; + else if (param == "-screenon") + param1 = APP_CMDLINE_SCREENON; + else if (param == "-screenoff") + param1 = APP_CMDLINE_SCREENOFF; + else if (param == "-sethdmi1") + param1 = APP_CMDLINE_SETHDMI1; + else if (param == "-sethdmi2") + param1 = APP_CMDLINE_SETHDMI2; + else if (param == "-sethdmi3") + param1 = APP_CMDLINE_SETHDMI3; + else if (param == "-sethdmi4") + param1 = APP_CMDLINE_SETHDMI4; + else if (param == "-mute") + param1 = APP_CMDLINE_MUTE; + else if (param == "-unmute") + param1 = APP_CMDLINE_UNMUTE; + else if (param == "-clearlog") + { + string w = "[IPC] Clear log "; + wstring log = Prefs.DataPath; + log += LOG_FILE; + w += common::narrow(log); + Log(w); + DeleteFile(log.c_str()); + } + else if (param == "-idle") + { + if (Prefs.BlankScreenWhenIdle) + { + Log("[IPC] Force user idle mode!"); + } + else + Log("[IPC] Can not force user idle mode, as the feature is not enabled in the global options!"); + } + else if (param == "-unidle") + { + if (Prefs.BlankScreenWhenIdle) + { + Log("[IPC] Release user idle mode!"); + } + else + Log("[IPC] Can not unset user idle mode, as the feature is not enabled in the global options!"); + } + else if (param1 > 0) + { + if (param1 == APP_IPC_DAEMON) + { + if (param == "errorconfig") + Log("[IPC] Daemon could not read configuration file. Daemon exiting!"); + else if (param == "started") + Log("[IPC] Daemon has started."); + else if (param == "newversion") + { + wstring s = L"[IPC] A new version of this app is available for download here: "; + s += NEWRELEASELINK; + Log(common::narrow(s)); + } + else if (param == "userbusy") + { + if (!bIdleLog) + { + Log("[IPC] PC is not idle."); + bIdleLog = true; + } + DispatchSystemPowerEvent(SYSTEM_EVENT_USERBUSY); + } + else if (param == "useridle") + { + if (bIdleLog) + { + Log("[IPC] PC is idle."); + bIdleLog = false; + } + DispatchSystemPowerEvent(SYSTEM_EVENT_USERIDLE); + } + else if (param == "remote_connect") + { + Log("[IPC] Remote streaming client connected. Managed devices will power off."); + DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYOFF); + + for (auto& d : DeviceCtrlSessions) + { + d.RemoteHostConnected(); + } + } + else if (param == "remote_disconnect") + { + for (auto& d : DeviceCtrlSessions) + { + d.RemoteHostDisconnected(); + } + if (Prefs.DisplayIsCurrentlyRequestedPoweredOnByWindows) + { + Log("[IPC] Remote streaming client disconnected. Powering on managed devices."); + DispatchSystemPowerEvent(SYSTEM_EVENT_DISPLAYON); + } + else + Log("[IPC] Remote streaming client disconnected. Managed devices will remain powered off."); + } + else if (param == "topology") + { + param1 = APP_IPC_DAEMON_TOPOLOGY; + for (auto& d : DeviceCtrlSessions) + { + 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 has invalidated the monitor topology configuration. " + "Please run the configuration guide in the global options again to ensure correct operation."); + } + if (param == "undetermined") + { + Log("[IPC] Warning! No active devices detected when verifying Windows Monitor Topology."); + } + if (param == "*") + { + string s; + string TopologyDevices; + for (auto& d : DeviceCtrlSessions) + { + TopologyDevices += d.DeviceID; + TopologyDevices += ":"; + TopologyDevices += d.GetTopology() ? "ON " : "OFF "; + } + + s = "[IPC] Windows monitor topology was changed. "; + if (TopologyDevices == "") + s += "No devices configured."; + else + s += TopologyDevices; + Log(s); + DispatchSystemPowerEvent(SYSTEM_EVENT_TOPOLOGY); + } + else + { + for (auto& d : DeviceCtrlSessions) + { + string id = d.DeviceID; + transform(id.begin(), id.end(), id.begin(), ::tolower); + if (param == id) + { + d.SetTopology(true); + } + } + } + } + + else + { + for (auto& device : DeviceCtrlSessions) + { + settings::DEVICE s = device.GetParams(); + string id = s.DeviceId; + string name = s.Name; + transform(id.begin(), id.end(), id.begin(), ::tolower); + transform(name.begin(), name.end(), name.begin(), ::tolower); + + if (param1 == APP_CMDLINE_ON && (id == param || name == param)) + { + string w = "[IPC] Force power ON: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCEON); + } + else if (param1 == APP_CMDLINE_OFF && (id == param || name == param)) + { + string w = "[IPC] Force power OFF: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCEOFF); + } + else if (param1 == APP_CMDLINE_SCREENON && (id == param || name == param)) + { + string w = "[IPC] Force screen ON: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCEON); + } + else if (param1 == APP_CMDLINE_SCREENOFF && (id == param || name == param)) + { + string w = "[IPC] Force screen OFF: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCESCREENOFF); + } + else if (param1 == APP_CMDLINE_AUTOENABLE && (id == param || name == param)) + { + string w = "[IPC] Automatic management is temporarily enabled (effective until restart of service): "; + w += param; + Log(w); + + device.Run(); + } + else if (param1 == APP_CMDLINE_AUTODISABLE && (id == param || name == param)) + { + string w = "[IPC] Automatic management is temporarily disabled (effective until restart of service): "; + w += param; + Log(w); + device.Stop(); + } + else if (param1 == APP_CMDLINE_SETHDMI1 && (id == param || name == param)) + { + string w = "[IPC] Set HDMI input 1: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 1); + } + else if (param1 == APP_CMDLINE_SETHDMI2 && (id == param || name == param)) + { + string w = "[IPC] Set HDMI input 2: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 2); + } + else if (param1 == APP_CMDLINE_SETHDMI3 && (id == param || name == param)) + { + string w = "[IPC] Set HDMI input 3: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 3); + } + else if (param1 == APP_CMDLINE_SETHDMI4 && (id == param || name == param)) + { + string w = "[IPC] Set HDMI input 4: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCESETHDMI, 4); + } + else if (param1 == APP_CMDLINE_MUTE && (id == param || name == param)) + { + string w = "[IPC] Mute: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCE_MUTE); + } + else if (param1 == APP_CMDLINE_UNMUTE && (id == param || name == param)) + { + string w = "[IPC] Unmute: "; + w += param; + Log(w); + device.SystemEvent(SYSTEM_EVENT_FORCE_UNMUTE); + } + } + } + } + } + } +} +void SendToNamedPipe(DWORD dwEvent) +{ + if (!Prefs.ExternalAPI) + return; + + wstring sData = L""; + switch (dwEvent) + { + case SYSTEM_EVENT_DISPLAYOFF: + sData = L"SYSTEM_DISPLAYS_OFF"; + break; + case SYSTEM_EVENT_DISPLAYDIMMED: + sData = L"SYSTEM_DISPLAYS_OFF_DIMMED"; + break; + case SYSTEM_EVENT_DISPLAYON: + sData = L"SYSTEM_DISPLAYS_ON"; + break; + case SYSTEM_EVENT_USERBUSY: + sData = L"SYSTEM_USER_BUSY"; + break; + case SYSTEM_EVENT_USERIDLE: + sData = L"SYSTEM_USER_IDLE"; + break; + case SYSTEM_EVENT_REBOOT: + sData = L"SYSTEM_REBOOT"; + break; + case SYSTEM_EVENT_UNSURE: + case SYSTEM_EVENT_SHUTDOWN: + sData = L"SYSTEM_SHUTDOWN"; + break; + case SYSTEM_EVENT_RESUMEAUTO: + sData = L"SYSTEM_RESUME"; + break; + case SYSTEM_EVENT_SUSPEND: + sData = L"SYSTEM_SUSPEND"; + break; + default:break; + } + if(sData.size() > 0) + pPipe->Send(sData); +} diff --git a/LGTV Companion Service/Service.h b/LGTV Companion Service/Service.h index 715f3eb4..d21ac678 100644 --- a/LGTV Companion Service/Service.h +++ b/LGTV Companion Service/Service.h @@ -28,6 +28,7 @@ #include "../Common/Common.h" #include "Handshake.h" + #pragma comment(lib, "Powrprof.lib") #pragma comment(lib, "Wevtapi.lib") #pragma comment(lib, "Ws2_32.lib") @@ -40,7 +41,6 @@ #define SERVICE_PORT_SSL "3001" #define SERVICE_DEPENDENCIES L"Dhcp\0Dnscache\0LanmanServer\0\0" #define SERVICE_ACCOUNT NULL //L"NT AUTHORITY\\LocalService" -#define MUTEX_WAIT 10 // thread wait in ms #define THREAD_WAIT 1 // wait to spawn new thread (seconds) #define DIMMED_OFF_DELAY_WAIT 20 // delay after a screen dim request #define MAX_RECORD_BUFFER_SIZE 0x10000 // 64K @@ -61,7 +61,9 @@ #define SYSTEM_EVENT_FORCESETHDMI 15 #define SYSTEM_EVENT_BOOT 16 #define SYSTEM_EVENT_TOPOLOGY 17 -#define APP_IPC_DAEMON_TOPOLOGY 12 +#define SYSTEM_EVENT_FORCE_MUTE 18 +#define SYSTEM_EVENT_FORCE_UNMUTE 19 +#define APP_IPC_DAEMON_TOPOLOGY 100 class CSession { public: @@ -89,6 +91,7 @@ class CSession { void TurnOnDisplay(bool SendWOL); void TurnOffDisplay(bool forced, bool dimmed, bool blankscreen); void SetDisplayHdmiInput(int HdmiInput); + void SendAnyMessage(std::string, std::string payload = ""); bool bRemoteHostConnected = false; jpersson77::settings::DEVICE Parameters; }; @@ -107,7 +110,10 @@ DWORD WINAPI SubCallback(EVT_SUBSCRIBE_NOTIFY_ACTION Action, PVOID UserContext, void DisplayPowerOnThread(jpersson77::settings::DEVICE*, bool*, int, bool); void DisplayPowerOffThread(jpersson77::settings::DEVICE*, bool*, bool, bool); void SetDisplayHdmiInputThread(jpersson77::settings::DEVICE*, bool*, int, int); +void SendRequest(jpersson77::settings::DEVICE*, int, char*, char*); void IPCThread(void); void WOLthread(jpersson77::settings::DEVICE*, bool*, int); std::vector GetOwnIP(void); -void InitSessions(void); \ No newline at end of file +void InitSessions(void); +void NamedPipeCallback(std::wstring message); +void SendToNamedPipe(DWORD); \ No newline at end of file diff --git a/LGTV Companion Service/Session.cpp b/LGTV Companion Service/Session.cpp index 8f80f4b2..f3166c37 100644 --- a/LGTV Companion Service/Session.cpp +++ b/LGTV Companion Service/Session.cpp @@ -218,6 +218,48 @@ void CSession::TurnOnDisplay(bool SendWOL) Log(s); return; } +void CSession::SendAnyMessage(string message, string payload) +{ + if (message.size() == 0) + return; + + string s; + + //thread safe section + while (!mMutex.try_lock()) + Sleep(MUTEX_WAIT); + + s = Parameters.DeviceId; + s += ", spawning SendRequest("; + s += message; + if (payload.size() > 0) + { + s += ", "; + s += payload; + } + s += ") thread."; + + // declaring character array (+1 for null terminator) + char* sMess = NULL; + if (message.size() > 0) + { + sMess = new char[message.length() + 1]; + strcpy_s(sMess, (rsize_t)message.length()+1, message.c_str()); + } + char* sPayload = NULL; + if(payload.size() > 0) + { + sPayload = new char[payload.length() + 1]; + strcpy_s(sPayload, (rsize_t)payload.length()+1, payload.c_str()); + } + + thread thread_obj(SendRequest, &Parameters, Parameters.PowerOnTimeout, sMess, sPayload); + thread_obj.detach(); + + mMutex.unlock(); + Log(s); + return; +} void CSession::TurnOffDisplay(bool forced, bool dimmed, bool blankscreen) { string s; @@ -269,6 +311,17 @@ void CSession::SystemEvent(DWORD dwMsg, int param) TurnOffDisplay(true, false, true); if (dwMsg == SYSTEM_EVENT_FORCESETHDMI) SetDisplayHdmiInput(param); + if (dwMsg == SYSTEM_EVENT_FORCE_MUTE) + { + string message = "ssap://audio/setMute"; + string payload = "\"mute\" : \"true\""; + SendAnyMessage(message, payload); + } + if (dwMsg == SYSTEM_EVENT_FORCE_UNMUTE) + { + string message = "ssap://audio/setMute"; + SendAnyMessage(message); + } //thread safe section while (!mMutex.try_lock()) @@ -411,7 +464,8 @@ void DisplayPowerOnThread(settings::DEVICE* CallingSessionParameters, bool* Call { string screenonmess = R"({"id":"2","type" : "request","uri" : "ssap://com.webos.service.tvpower/power/turnOnScreen"})"; string getpowerstatemess = R"({"id": "1", "type": "request", "uri": "ssap://com.webos.service.tvpower/power/getPowerState"})"; - +// string mute_off_mess = R"({"id": "3", "type" : "request", "uri" : "ssap://audio/setMute", "payload" :{"mute":"false"}})"; + string mute_off_mess = R"({"id": "3", "type" : "request", "uri" : "ssap://audio/setMute"})"; if (!CallingSessionParameters) return; @@ -419,6 +473,8 @@ void DisplayPowerOnThread(settings::DEVICE* CallingSessionParameters, bool* Call string key = CallingSessionParameters->SessionKey; string device = CallingSessionParameters->DeviceId; bool SSL = CallingSessionParameters->SSL; + bool mute = CallingSessionParameters->MuteSpeakers; + bool bScreenWasBlanked = false; string logmsg; string ck = "CLIENTKEYx0x0x0"; string handshake; @@ -597,6 +653,18 @@ void DisplayPowerOnThread(settings::DEVICE* CallingSessionParameters, bool* Call st += tt; Log(st); } + + //Was the screen blanked + j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); + boost::string_view type = j["type"]; + boost::string_view state = j["payload"]["state"]; + if (std::string(type) == "response") + { + if (std::string(state) == "Active") + { + bScreenWasBlanked = true; + } + } buffer.consume(buffer.size()); //retreive power state from device to determine if the device is powered on @@ -620,8 +688,8 @@ void DisplayPowerOnThread(settings::DEVICE* CallingSessionParameters, bool* Call j = json::parse(static_cast(buffer.data().data()), static_cast(buffer.data().data()) + buffer.size()); - boost::string_view type = j["type"]; - boost::string_view state = j["payload"]["state"]; + type = j["type"]; + state = j["payload"]["state"]; if (std::string(type) == "response") { if (std::string(state) == "Active") @@ -630,6 +698,29 @@ void DisplayPowerOnThread(settings::DEVICE* CallingSessionParameters, bool* Call logmsg += ", power state is: ON"; Log(logmsg); + buffer.consume(buffer.size()); + + // unmute speakers + if (mute) //&& bScreenWasBlanked) + { + if (SSL) + { + wss.write(net::buffer(mute_off_mess)); + wss.read(buffer); + } + else + { + ws.write(net::buffer(mute_off_mess)); + ws.read(buffer); + } + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = device; st += SSL ? ", [DEBUG] (SSL) ON response 4: " : ", [DEBUG] ON response 4: "; + st += tt; + Log(st); + } + } buffer.consume(buffer.size()); if (SSL) wss.close(websocket::close_code::normal); @@ -892,11 +983,14 @@ void DisplayPowerOffThread(settings::DEVICE* CallingSessionParameters, bool* Cal string device = CallingSessionParameters->DeviceId; int hdmi_number = CallingSessionParameters->OnlyTurnOffIfCurrentHDMIInputNumberIs; bool SSL = CallingSessionParameters->SSL; + bool mute = CallingSessionParameters->MuteSpeakers; 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"})"; string poweroffmess = R"({"id": "3", "type" : "request", "uri" : "ssap://system/turnOff" })"; string screenoffmess = R"({"id": "4", "type" : "request", "uri" : "ssap://com.webos.service.tvpower/power/turnOffScreen"})"; + string mute_on_mess = R"({"id": "3", "type" : "request", "uri" : "ssap://audio/setMute", "payload" :{"mute":"true"}})"; + string handshake = common::narrow(HANDSHAKE_PAIRED); string ck = "CLIENTKEYx0x0x0"; json j; @@ -1029,7 +1123,8 @@ void DisplayPowerOffThread(settings::DEVICE* CallingSessionParameters, bool* Cal state = j["payload"]["state"]; if (std::string(type) == "response") { - if (std::string(state) != "Active") + if ((!BlankScreen && std::string(state) != "Active" && std::string(state) != "Screen Off") || + (BlankScreen && std::string(state) != "Active")) { logmsg = device; logmsg += ", power state is: "; @@ -1148,6 +1243,29 @@ void DisplayPowerOffThread(settings::DEVICE* CallingSessionParameters, bool* Cal logmsg += ", power state is: OFF."; Log(logmsg); buffer.consume(buffer.size()); + + // mute speakers + if (mute && BlankScreen) + { + if (SSL) + { + wss.write(net::buffer(mute_on_mess)); + wss.read(buffer); + } + else + { + ws.write(net::buffer(mute_on_mess)); + ws.read(buffer); + } + //debuggy + { + string tt = beast::buffers_to_string(buffer.data()); + string st = device; st += SSL ? ", [DEBUG] (SSL) ON response 5: " : ", [DEBUG] ON response 5: "; + st += tt; + Log(st); + } + } + buffer.consume(buffer.size()); } else { @@ -1309,4 +1427,131 @@ void SetDisplayHdmiInputThread(settings::DEVICE* CallingSessionParameters, bool* *CallingSessionThreadRunning = false; mMutex.unlock(); return; +} + +// THREAD: Send any message +void SendRequest(settings::DEVICE* CallingSessionParameters, int Timeout, char* Message, char* Payload) +{ + if (!CallingSessionParameters) + return; + if (CallingSessionParameters->SessionKey == "") + return; + + + string hostip = CallingSessionParameters->IP; + string key = CallingSessionParameters->SessionKey; + string device = CallingSessionParameters->DeviceId; + bool SSL = CallingSessionParameters->SSL; + string logmsg; + time_t origtim = time(0); + string ck = "CLIENTKEYx0x0x0"; + string handshake; + size_t ckf = NULL; + + string mess = R"({"id": "5", "type" : "request", "uri" : ")"; + mess += Message; + if (Payload == NULL) + { + mess += "\"}"; + } + else + { + mess += "\", \"payload\" :{"; + mess += Payload; + mess += "}}"; + } + if(Message) + delete[] Message; + if(Payload) + delete[] Payload; + + //build handshake + handshake = common::narrow(HANDSHAKE_PAIRED); + ckf = handshake.find(ck); + handshake.replace(ckf, ck.length(), key); + + //try, but not longer than timeout user preference + while (time(0) - origtim < (Timeout + 1)) + { + 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 }; + + // try communicating with the display + 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 (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(mess))); + 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(mess))); + ws.read(buffer); + ws.close(websocket::close_code::normal); + } + logmsg = device; + logmsg += ", Muting speaker"; + Log(logmsg); + break; + } + catch (std::exception const& e) + { + logmsg = device; + logmsg += ", WARNING! SendRequest() failed: "; + logmsg += e.what(); + Log(logmsg); + } + time_t endtim = time(0); + time_t execution_time = endtim - looptim; + if (execution_time >= 0 && execution_time < 2) + Sleep((2 - (DWORD)execution_time) * 1000); + } + return; } \ No newline at end of file diff --git a/LGTV Companion Setup/Product.wxs b/LGTV Companion Setup/Product.wxs index a501f3b5..00b6e996 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 14a05341..3e1cf9c3 100644 --- a/LGTV Companion UI/LGTV Companion UI.cpp +++ b/LGTV Companion UI/LGTV Companion UI.cpp @@ -99,7 +99,7 @@ CHANGELOG - [ ] 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 - + - [ ] Build restart word phrases, see issue #132 LICENSE Copyright (c) 2021-2023 Jörgen Persson @@ -149,11 +149,12 @@ HMENU hPopupMeuMain; HICON hOptionsIcon; HICON hTopologyIcon; HICON hCogIcon; -HANDLE hPipe = INVALID_HANDLE_VALUE; WSADATA WSAData; int iTopConfPhase; int iTopConfDisplay; -vector WhitelistTemp; +vector WhitelistTemp; +vector ExclusionsTemp; +ipc::PipeClient* pPipeClient; settings::Preferences Prefs; @@ -211,12 +212,21 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, } Prefs.AdhereTopology = bTop ? Prefs.AdhereTopology : false; + // Initiate PipeClient IPC + ipc::PipeClient PipeCl(PIPENAME, NamedPipeCallback); + pPipeClient = &PipeCl; + //parse and execute command line parameters when applicable and then exit if (Prefs.Devices.size() > 0 && CommandLineParameters.size() > 0) { + int max_wait = 1000; + while (!PipeCl.isRunning() && max_wait > 0) + { + Sleep(25); + max_wait -= 25; + } CommunicateWithService(common::narrow(CommandLineParameters)); - if (hPipe != INVALID_HANDLE_VALUE) - CloseHandle(hPipe); + PipeCl.Terminate(); return false; } @@ -269,6 +279,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, } } + PipeCl.Terminate(); + //clean up DeleteObject(hBackbrush); DeleteObject(hEditMediumBoldfont); @@ -671,7 +683,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) exe = path; exe += L"LGTVdaemon.exe"; ShellExecute(NULL, L"open", exe.c_str(), L"-hide", path.c_str(), SW_SHOWNORMAL); - + pPipeClient->Init(); EnableWindow(hWnd, true); EnableWindow(GetDlgItem(hWnd, IDOK), false); }break; @@ -749,7 +761,13 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) //care to support your coder if (wParam == IDC_DONATE) { - if (MessageBox(hWnd, L"This is free software, but your support is appreciated and there is a donation page set up over at PayPal. PayPal allows you to use a credit- or debit card or your PayPal balance to make a donation, even without a PayPal account.\n\nClick 'Yes' to continue to the PayPal donation web page!", L"Donate via PayPal?", MB_YESNO | MB_ICONQUESTION) == IDYES) + if (MessageBox(hWnd, L"This is free software, but your support is appreciated and there " + "is a donation page set up over at PayPal. PayPal allows you to use a credit- or debit " + "card or your PayPal balance to make a donation, even without a PayPal account.\n\n" + "Click 'Yes' to continue to the PayPal donation web page (a PayPal account is not " + "required to make a donation)!" + "\n\nAlternatively you can go to the following URL in your web browser (a PayPal " + "account is required for this link).\n\nhttps://paypal.me/jpersson77", L"Donate via PayPal?", MB_YESNO | MB_ICONQUESTION) == IDYES) ShellExecute(0, 0, DONATELINK, 0, 0, SW_SHOW); } }break; @@ -1420,6 +1438,7 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l CheckDlgButton(hWnd, IDC_CHECK_BLANK, Prefs.BlankScreenWhenIdle ? 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); + CheckDlgButton(hWnd, IDC_CHECK_API, Prefs.ExternalAPI ? BST_CHECKED : BST_UNCHECKED); wstring s; SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_RESETCONTENT, (WPARAM)0, (LPARAM)0); @@ -1513,6 +1532,8 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l case IDC_LOGGING: case IDC_AUTOUPDATE: case IDC_CHECK_REMOTE: + case IDC_CHECK_API: + { EnableWindow(GetDlgItem(hWnd, IDOK), true); }break; @@ -1535,6 +1556,7 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l Prefs.BlankScreenWhenIdle = IsDlgButtonChecked(hWnd, IDC_CHECK_BLANK) == BST_CHECKED; Prefs.RemoteStreamingCheck = IsDlgButtonChecked(hWnd, IDC_CHECK_REMOTE) == BST_CHECKED; Prefs.AdhereTopology = IsDlgButtonChecked(hWnd, IDC_CHECK_TOPOLOGY) == BST_CHECKED; + Prefs.ExternalAPI = IsDlgButtonChecked(hWnd, IDC_CHECK_API) == BST_CHECKED; int sel = (int)(SendMessage(GetDlgItem(hWnd, IDC_COMBO_MODE), (UINT)CB_GETCURSEL, (WPARAM)0, (LPARAM)0)); if (sel == 1) @@ -1652,6 +1674,7 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l // explain the pmulti-monitor conf else if (wParam == IDC_SYSLINK6) { + /* 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" @@ -1665,6 +1688,34 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l "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); + */ + 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" + + "This option will however not work on all system configuration and may fail to power 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 " + "the feature works properly.\n\n" + "If you have a multi-monitor system and a compatible WebOS device 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); + } + + // explain the pmulti-monitor conf + else if (wParam == IDC_SYSLINK11) + { + if (MessageBox(hWnd, L"Scripts or other applications can use the \"External API\" to be notified of power events. The " + "usability of this is up to the creativity of the user, but can for example be used to trigger power state changes " + "of non-LG devices, execute scripts, trigger RGB profiles etc. The functionality and implementation is currently in BETA and " + "is preferrably discussed in the Discord #external-api channel (https://discord.gg/7KkTPrP3fq)\n\n" + "The current implementation implements intra-process-communication via a named pipe and example scripts are available " + "in the #external-api channel.\n\nDo you wish to go to Discord? Please click \"Yes\" to go to Discord or click \"No\" " + "to close this information window", L"External API", MB_YESNO | MB_ICONQUESTION) == IDYES) + { + ShellExecute(0, 0, DISCORDLINK, 0, 0, SW_SHOW); + } + } else if (wParam == IDC_SYSLINK_CONF) { @@ -2086,105 +2137,232 @@ LRESULT CALLBACK UserIdleConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LPA case WM_INITDIALOG: { SendDlgItemMessage(hWnd, IDC_LIST, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); + SendDlgItemMessage(hWnd, IDC_LIST2, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_EDIT_TIME, WM_SETFONT, (WPARAM)hEditMediumfont, MAKELPARAM(TRUE, 0)); SendDlgItemMessage(hWnd, IDC_SPIN, UDM_SETRANGE, (WPARAM)NULL, MAKELPARAM(240, 1)); SendDlgItemMessage(hWnd, IDC_SPIN, UDM_SETPOS, (WPARAM)NULL, (LPARAM)Prefs.BlankScreenWhenIdleDelay); WhitelistTemp = Prefs.WhiteList; - SendMessage(hWnd, APP_LISTBOX_REDRAW, 0, 0); + ExclusionsTemp = Prefs.FsExclusions; + SendMessage(hWnd, APP_LISTBOX_REDRAW, 1, 0); + SendMessage(hWnd, APP_LISTBOX_REDRAW, 2, 0); CheckDlgButton(hWnd, IDC_CHECK_WHITELIST, Prefs.bIdleWhitelistEnabled ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hWnd, IDC_CHECK_FULLSCREEN, Prefs.bFullscreenCheckEnabled ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hWnd, IDC_CHECK_FULLSCREEN, !Prefs.bFullscreenCheckEnabled ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hWnd, IDC_CHECK_FS_EXCLUSIONS, Prefs.bIdleFsExclusionsEnabled ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hWnd, IDC_CHECK_MUTE, Prefs.MuteSpeakers ? BST_CHECKED : BST_UNCHECKED); + EnableWindow(GetDlgItem(hWnd, IDC_LIST), Prefs.bIdleWhitelistEnabled); EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_ADD), Prefs.bIdleWhitelistEnabled); EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR), Prefs.bIdleWhitelistEnabled); EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE), Prefs.bIdleWhitelistEnabled); + + EnableWindow(GetDlgItem(hWnd, IDC_CHECK_FS_EXCLUSIONS), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN)); + EnableWindow(GetDlgItem(hWnd, IDC_LIST2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN) ? IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS) : false); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_ADD2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN) ? IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS) : false); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN) ? IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS) : false); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN) ? IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS) : false); + EnableWindow(GetDlgItem(hWnd, IDOK), false); }break; case APP_LISTBOX_EDIT: { - int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETCURSEL, 0, 0); - if (index != LB_ERR) + switch (wParam) { - int data = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETITEMDATA, index, 0); - if (data != LB_ERR && data < WhitelistTemp.size()) + case 1: + { + int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { - hWhitelistConfWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_WHITELIST_EDIT), hWnd, (DLGPROC)WhitelistConfWndProc); - SetWindowText(hWhitelistConfWindow, L"Edit whitelisted process"); - SetWindowText(GetDlgItem(hWhitelistConfWindow, IDOK), L"Change"); - SetWindowText(GetDlgItem(hWhitelistConfWindow, IDC_EDIT_NAME), WhitelistTemp[data].Name.c_str()); - SetWindowText(GetDlgItem(hWhitelistConfWindow, IDC_EDIT_PROCESS), WhitelistTemp[data].Application.c_str()); - EnableWindow(hWnd, false); - ShowWindow(hWhitelistConfWindow, SW_SHOW); + int data = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETITEMDATA, index, 0); + if (data != LB_ERR && data < WhitelistTemp.size()) + { + hWhitelistConfWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_WHITELIST_EDIT), hWnd, (DLGPROC)WhitelistConfWndProc); + SetWindowText(hWhitelistConfWindow, L"Edit whitelisted process"); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDOK), L"Change"); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDC_EDIT_NAME), WhitelistTemp[data].Name.c_str()); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDC_EDIT_PROCESS), WhitelistTemp[data].Application.c_str()); + EnableWindow(hWnd, false); + ShowWindow(hWhitelistConfWindow, SW_SHOW); + } } + }break; + case 2: + { + int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETCURSEL, 0, 0); + if (index != LB_ERR) + { + int data = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETITEMDATA, index, 0); + if (data != LB_ERR && data < ExclusionsTemp.size()) + { + hWhitelistConfWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_WHITELIST_EDIT), hWnd, (DLGPROC)WhitelistConfWndProc); + SetWindowText(hWhitelistConfWindow, L"Edit fullscreen exclusion process"); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDOK), L"Change"); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDC_EDIT_NAME), ExclusionsTemp[data].Name.c_str()); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDC_EDIT_PROCESS), ExclusionsTemp[data].Application.c_str()); + EnableWindow(hWnd, false); + ShowWindow(hWhitelistConfWindow, SW_SHOW); + } + } + }break; + default:break; } }break; case APP_LISTBOX_ADD: { - hWhitelistConfWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_WHITELIST_EDIT), hWnd, (DLGPROC)WhitelistConfWndProc); - SetWindowText(hWhitelistConfWindow, L"Add whitelisted process"); - SetWindowText(GetDlgItem(hWhitelistConfWindow, IDOK), L"Add"); - EnableWindow(hWnd, false); - ShowWindow(hWhitelistConfWindow, SW_SHOW); + switch (wParam) + { + case 1: + { + hWhitelistConfWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_WHITELIST_EDIT), hWnd, (DLGPROC)WhitelistConfWndProc); + SetWindowText(hWhitelistConfWindow, L"Add whitelisted process"); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDOK), L"Add"); + EnableWindow(hWnd, false); + ShowWindow(hWhitelistConfWindow, SW_SHOW); + }break; + case 2: + { + hWhitelistConfWindow = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_WHITELIST_EDIT), hWnd, (DLGPROC)WhitelistConfWndProc); + SetWindowText(hWhitelistConfWindow, L"Add fullscreen exclusion process"); + SetWindowText(GetDlgItem(hWhitelistConfWindow, IDOK), L"Add"); + EnableWindow(hWnd, false); + ShowWindow(hWhitelistConfWindow, SW_SHOW); + }break; + default:break; + } }break; case APP_LISTBOX_DELETE: { - int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETCURSEL, 0, 0); - if (index != LB_ERR) + switch (wParam) { - TCHAR* text; - int len = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETTEXTLEN, index, 0); - text = new TCHAR[len + 1]; - if (SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETTEXT, (WPARAM)index, (LPARAM)text) != LB_ERR) + case 1: + { + int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { - wstring s = L"Do you want to delete '"; - s += text; - s += L"' from the whitelist?"; - if (MessageBox(hWnd, s.c_str(), L"Delete item", MB_YESNO | MB_ICONQUESTION) == IDYES) + TCHAR* text; + int len = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETTEXTLEN, index, 0); + text = new TCHAR[len + 1]; + if (SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETTEXT, (WPARAM)index, (LPARAM)text) != LB_ERR) { - int data = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETITEMDATA, index, 0); - if (data != LB_ERR && data < WhitelistTemp.size()) + wstring s = L"Do you want to delete '"; + s += text; + s += L"' from the whitelist?"; + if (MessageBox(hWnd, s.c_str(), L"Delete item", MB_YESNO | MB_ICONQUESTION) == IDYES) { - WhitelistTemp.erase(WhitelistTemp.begin() + data); - SendMessage(hWnd, APP_LISTBOX_REDRAW, 0, 0); - EnableWindow(GetDlgItem(hWnd, IDOK), true); - } - else - { - MessageBox(hWnd, L"Could not delete item!", L"Error", MB_OK | MB_ICONINFORMATION); + int data = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETITEMDATA, index, 0); + if (data != LB_ERR && data < WhitelistTemp.size()) + { + WhitelistTemp.erase(WhitelistTemp.begin() + data); + SendMessage(hWnd, APP_LISTBOX_REDRAW, 1, 0); + EnableWindow(GetDlgItem(hWnd, IDOK), true); + } + else + { + MessageBox(hWnd, L"Could not delete item!", L"Error", MB_OK | MB_ICONINFORMATION); + } } } + else + { + MessageBox(hWnd, L"There was an error when managing the listbox", L"Error", MB_OK | MB_ICONINFORMATION); + } } - else + }break; + case 2: + { + int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { - MessageBox(hWnd, L"There was an error when managing the listbox", L"Error", MB_OK | MB_ICONINFORMATION); + TCHAR* text; + int len = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETTEXTLEN, index, 0); + text = new TCHAR[len + 1]; + if (SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETTEXT, (WPARAM)index, (LPARAM)text) != LB_ERR) + { + wstring s = L"Do you want to delete '"; + s += text; + s += L"' from the fullscreen exclusions?"; + if (MessageBox(hWnd, s.c_str(), L"Delete item", MB_YESNO | MB_ICONQUESTION) == IDYES) + { + int data = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETITEMDATA, index, 0); + if (data != LB_ERR && data < ExclusionsTemp.size()) + { + ExclusionsTemp.erase(ExclusionsTemp.begin() + data); + SendMessage(hWnd, APP_LISTBOX_REDRAW, 2, 0); + EnableWindow(GetDlgItem(hWnd, IDOK), true); + } + else + { + MessageBox(hWnd, L"Could not delete item!", L"Error", MB_OK | MB_ICONINFORMATION); + } + } + } + else + { + MessageBox(hWnd, L"There was an error when managing the listbox", L"Error", MB_OK | MB_ICONINFORMATION); + } } + + }break; + default:break; } }break; case APP_LISTBOX_REDRAW: { - int i = 0; - SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_RESETCONTENT, 0, 0); - for (auto& w : WhitelistTemp) + switch (wParam) { - if (w.Application != L"" && w.Name != L"") + case 1: + { + int i = 0; + SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_RESETCONTENT, 0, 0); + for (auto& w : WhitelistTemp) { - int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_ADDSTRING, 0, (LPARAM)(w.Name.c_str())); - SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_SETITEMDATA, index, (LPARAM)i); - i++; + if (w.Application != L"" && w.Name != L"") + { + int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_ADDSTRING, 0, (LPARAM)(w.Name.c_str())); + SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_SETITEMDATA, index, (LPARAM)i); + i++; + } } - } - if (SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETCOUNT, 0, 0) > 0) - { - SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_SETCURSEL, 0, 0); - ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR), SW_SHOW); - ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE), SW_SHOW); - } - else + if (SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_GETCOUNT, 0, 0) > 0) + { + SendMessage(GetDlgItem(hWnd, IDC_LIST), LB_SETCURSEL, 0, 0); + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR), SW_SHOW); + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE), SW_SHOW); + } + else + { + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR), SW_HIDE); + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE), SW_HIDE); + } + }break; + case 2: { - ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR), SW_HIDE); - ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE), SW_HIDE); + int i = 0; + SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_RESETCONTENT, 0, 0); + for (auto& w : ExclusionsTemp) + { + if (w.Application != L"" && w.Name != L"") + { + int index = (int)SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_ADDSTRING, 0, (LPARAM)(w.Name.c_str())); + SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_SETITEMDATA, index, (LPARAM)i); + i++; + } + } + if (SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_GETCOUNT, 0, 0) > 0) + { + SendMessage(GetDlgItem(hWnd, IDC_LIST2), LB_SETCURSEL, 0, 0); + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR2), SW_SHOW); + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE2), SW_SHOW); + } + else + { + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR2), SW_HIDE); + ShowWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE2), SW_HIDE); + } + }break; + default:break; } }break; case WM_COMMAND: @@ -2193,7 +2371,18 @@ LRESULT CALLBACK UserIdleConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LPA { case LBN_DBLCLK: { - PostMessage(hWnd, APP_LISTBOX_EDIT, 0, 0); + switch (LOWORD(wParam)) + { + case IDC_LIST: + { + PostMessage(hWnd, APP_LISTBOX_EDIT, 1, 0); + }break; + case IDC_LIST2: + { + PostMessage(hWnd, APP_LISTBOX_EDIT, 2, 0); + }break; + default:break; + } }break; case BN_CLICKED: @@ -2208,16 +2397,36 @@ LRESULT CALLBACK UserIdleConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LPA EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE), IsDlgButtonChecked(hWnd, IDC_CHECK_WHITELIST)); EnableWindow(GetDlgItem(hWnd, IDOK), true); }break; + case IDC_CHECK_FS_EXCLUSIONS: + { + EnableWindow(GetDlgItem(hWnd, IDC_LIST2), IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS)); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_ADD2), IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS)); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR2), IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS)); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE2), IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS)); + EnableWindow(GetDlgItem(hWnd, IDOK), true); + }break; case IDC_CHECK_FULLSCREEN: + { + EnableWindow(GetDlgItem(hWnd, IDC_CHECK_FS_EXCLUSIONS), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN)); + EnableWindow(GetDlgItem(hWnd, IDC_LIST2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN)?IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS):false); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_ADD2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN)? IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS):false); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_EDIR2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN)?IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS):false); + EnableWindow(GetDlgItem(hWnd, IDC_SYSLINK_DELETE2), IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN)?IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS):false); + EnableWindow(GetDlgItem(hWnd, IDOK), true); + }break; + case IDC_CHECK_MUTE: { EnableWindow(GetDlgItem(hWnd, IDOK), true); }break; case IDOK: { Prefs.BlankScreenWhenIdleDelay = atoi(common::narrow(common::GetWndText(GetDlgItem(hWnd, IDC_EDIT_TIME))).c_str()); - Prefs.bFullscreenCheckEnabled = IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN); + Prefs.bFullscreenCheckEnabled = !IsDlgButtonChecked(hWnd, IDC_CHECK_FULLSCREEN); Prefs.bIdleWhitelistEnabled = IsDlgButtonChecked(hWnd, IDC_CHECK_WHITELIST); + Prefs.bIdleFsExclusionsEnabled = IsDlgButtonChecked(hWnd, IDC_CHECK_FS_EXCLUSIONS); + Prefs.MuteSpeakers = IsDlgButtonChecked(hWnd, IDC_CHECK_MUTE); Prefs.WhiteList = WhitelistTemp; + Prefs.FsExclusions = ExclusionsTemp; EndDialog(hWnd, 0); EnableWindow(GetParent(hWnd), true); EnableWindow(GetDlgItem(GetParent(hWnd), IDOK), true); @@ -2257,25 +2466,38 @@ LRESULT CALLBACK UserIdleConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LPA } else if (wParam == IDC_SYSLINK_INFO_2) { - MessageBox(hWnd, L"The option prevents user idle mode when a whitelisted process is running on the system. Enable the option to prevent user idle mode " - "while running a movie player, for example.", L"User idle mode whitelist configuration", MB_OK | MB_ICONINFORMATION); + MessageBox(hWnd, L"Enable to ensure that when a whitelisted process is running the user idle mode will not be triggered. This can be useful to prevent user idle mode " + "while running a specific movie player, for example.", L"User idle mode whitelist configuration", MB_OK | MB_ICONINFORMATION); } else if (wParam == IDC_SYSLINK_INFO_3) { - MessageBox(hWnd, L"The option prevents user idle mode when an application or game is running in fullscreen " - "mode.", L"User idle mode fullscreen configuration", MB_OK | MB_ICONINFORMATION); + MessageBox(hWnd, L"Enable the fullscreen user idle mode to ensure that user idle mode triggers also when running an application or game in fullscreen." + "\n\nAdditionally, it is possible to configure processes which are excluded from the fullscreen user idle mode detection.", L"User idle mode fullscreen configuration", MB_OK | MB_ICONINFORMATION); } else if (wParam == IDC_SYSLINK_ADD) { - PostMessage(hWnd, APP_LISTBOX_ADD, 0, 0); + PostMessage(hWnd, APP_LISTBOX_ADD, 1, 0); } else if (wParam == IDC_SYSLINK_EDIR) { - PostMessage(hWnd, APP_LISTBOX_EDIT, 0, 0); + PostMessage(hWnd, APP_LISTBOX_EDIT, 1, 0); } else if (wParam == IDC_SYSLINK_DELETE) { - PostMessage(hWnd, APP_LISTBOX_DELETE, 0, 0); + PostMessage(hWnd, APP_LISTBOX_DELETE, 1, 0); + } + + else if (wParam == IDC_SYSLINK_ADD2) + { + PostMessage(hWnd, APP_LISTBOX_ADD, 2, 0); + } + else if (wParam == IDC_SYSLINK_EDIR2) + { + PostMessage(hWnd, APP_LISTBOX_EDIT, 2, 0); + } + else if (wParam == IDC_SYSLINK_DELETE2) + { + PostMessage(hWnd, APP_LISTBOX_DELETE, 2, 0); } }break; default:break; @@ -2286,7 +2508,8 @@ LRESULT CALLBACK UserIdleConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LPA HDC hdcStatic = (HDC)wParam; SetTextColor(hdcStatic, COLORREF(COLOR_STATIC)); if ((HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_WHITELIST) - || (HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_FULLSCREEN)) + || (HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_FULLSCREEN) + || (HWND)lParam == GetDlgItem(hWnd, IDC_CHECK_FS_EXCLUSIONS)) { SetBkMode(hdcStatic, TRANSPARENT); } @@ -2363,6 +2586,7 @@ LRESULT CALLBACK WhitelistConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LP { case IDOK: { + wstring wnd = common::GetWndText(hWnd); wstring name = common::GetWndText(GetDlgItem(hWnd, IDC_EDIT_NAME)); wstring proc = common::GetWndText(GetDlgItem(hWnd, IDC_EDIT_PROCESS)); @@ -2381,30 +2605,54 @@ LRESULT CALLBACK WhitelistConfWndProc(HWND hWnd, UINT message, WPARAM wParam, LP { if (common::GetWndText(GetDlgItem(hWnd, IDOK)) == L"Add") // add item { - settings::WHITELIST w; + settings::PROCESSLIST w; w.Name = name; w.Application = proc; - WhitelistTemp.push_back(w); + if (wnd.find(L"fullscreen") == wstring::npos) + { + WhitelistTemp.push_back(w); + } + else + { + ExclusionsTemp.push_back(w); + } } else //change item { if (hUserIdleConfWindow) { - int index = (int)SendMessage(GetDlgItem(hUserIdleConfWindow, IDC_LIST), LB_GETCURSEL, 0, 0); - if (index != LB_ERR) + if (wnd.find(L"fullscreen") == wstring::npos) + { + int index = (int)SendMessage(GetDlgItem(hUserIdleConfWindow, IDC_LIST), LB_GETCURSEL, 0, 0); + if (index != LB_ERR) + { + int data = (int)SendMessage(GetDlgItem(hUserIdleConfWindow, IDC_LIST), LB_GETITEMDATA, index, 0); + if (data != LB_ERR && data < WhitelistTemp.size()) + { + WhitelistTemp[data].Application = proc; + WhitelistTemp[data].Name = name; + } + } + } + else { - int data = (int)SendMessage(GetDlgItem(hUserIdleConfWindow, IDC_LIST), LB_GETITEMDATA, index, 0); - if (data != LB_ERR && data < WhitelistTemp.size()) + int index = (int)SendMessage(GetDlgItem(hUserIdleConfWindow, IDC_LIST2), LB_GETCURSEL, 0, 0); + if (index != LB_ERR) { - WhitelistTemp[data].Application = proc; - WhitelistTemp[data].Name = name; + int data = (int)SendMessage(GetDlgItem(hUserIdleConfWindow, IDC_LIST2), LB_GETITEMDATA, index, 0); + if (data != LB_ERR && data < ExclusionsTemp.size()) + { + ExclusionsTemp[data].Application = proc; + ExclusionsTemp[data].Name = name; + } } } } } EndDialog(hWnd, 0); - SendMessage(GetParent(hWnd), APP_LISTBOX_REDRAW, 0, 0); + SendMessage(GetParent(hWnd), APP_LISTBOX_REDRAW, 1, 0); + SendMessage(GetParent(hWnd), APP_LISTBOX_REDRAW, 2, 0); EnableWindow(GetParent(hWnd), true); EnableWindow(GetDlgItem(GetParent(hWnd), IDOK), true); } @@ -2617,28 +2865,10 @@ void SvcReportEvent(WORD Type, wstring string) } } // Send the commandline to the service -void CommunicateWithService(string cmdline) +void CommunicateWithService(string sData) { - DWORD dwWritten; - - if (cmdline == "") - return; - - hPipe = CreateFile(PIPENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - - if (hPipe != INVALID_HANDLE_VALUE) - { - WriteFile(hPipe, - cmdline.c_str(), - (DWORD)cmdline.length() + 1, // = length of string + terminating '\0' !!! - &dwWritten, - NULL); - } - else + if(!pPipeClient->Send(common::widen(sData))) MessageBox(hMainWnd, L"Failed to connect to named pipe. Service may be stopped.", L"Error", MB_OK | MB_ICONEXCLAMATION); - - if (hPipe != INVALID_HANDLE_VALUE) - CloseHandle(hPipe); } // Discover the local host ip, e.g 192.168.1.x vector GetOwnIP(void) @@ -2813,4 +3043,9 @@ bool MessageDaemon(wstring cmd) SendMessage(Daemon_hWnd, APP_USER_IDLE_OFF, NULL, NULL); } return false; -} \ No newline at end of file +} + +void NamedPipeCallback(std::wstring message) +{ + return; +} diff --git a/LGTV Companion UI/LGTV Companion UI.h b/LGTV Companion UI/LGTV Companion UI.h index b0b39c20..2a180065 100644 --- a/LGTV Companion UI/LGTV Companion UI.h +++ b/LGTV Companion UI/LGTV Companion UI.h @@ -90,4 +90,6 @@ std::vector GetOwnIP(void); void VersionCheckThread(HWND); static BOOL CALLBACK meproc(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData); std::vector QueryDisplays(); -bool MessageDaemon(std::wstring); \ No newline at end of file +bool MessageDaemon(std::wstring); +void NamedPipeCallback(std::wstring message); + diff --git a/LGTV Companion UI/LGTV Companion UI.rc b/LGTV Companion UI/LGTV Companion UI.rc index d6cec2a74bd2aff11f2eb6bbb7a0d514a607d5d8..4da908ac5744a193e22c66b31337078148f7c7ef 100644 GIT binary patch delta 1165 zcmZuwO-xi*6h3cWD-8a1_?wwvWo%Q!&l`Df=FK=0s4zAVNsvO3v^J&&1ud2WGFV+y z$^Zf-r3#+~iCqv^Hccb)5*Ds7acOKYUA9S$>7q3;ZCBmt_wG1`H0EaBz4x3s=ljlg z&b_hj`Tn72LiAYjn+-)(j;jO{Vv2CdkWBwo6B`3Lh zB+V6K)&YKd$-1(}CUra$l|$lM@|iT247w2nvEjbzrM2wL=8&fIvB=H;K1DT-8K3Dzf8 zX^Ak@kgR1nQ-QThSG`e{z}4XTbOygn9?NqpW!-4sTL#m++ih$(q{bkvn%6DoNVb>l zJ5A!JcT_D$Vs7aPHVo5GsI7&}*Bwfsf%;AuGyVAZ zodUFt@AF59oh`}b>T0h_!rb@|E|0fgiKhnY0`dFEC5>kVC3rfuBiJFwIaVLZd(tAE z(jm?AzO-`RJXkHk9GXeJl(a8|G|^QYL&JoracGMDn4H+{&2h?UdW^n*E`3OQR6T{w z%GPClz6d?8qEtAWpO3|=Jf|8Tk`rXyOWwm0R*HtG;~*95lF#@Z%8o(aVe&sBjreV> zbEmD466)&?!raLstnbKoR%SdNHQ}|hR&MAJm@V|#FGe^EpOC0iJNu(Yw#o7X$Z7ER%<_gQz zqCB=B#D76gMZtxh=Sz=ay#*5Mxd`i}r~cgQ_R!0{aL&2sch2uFf0Q>$@|&^4lFlUa zb%|}er)7giVQfBi;Iq=q9(v1GNkgV^2H(2suoc_pbOf)vUSS+6J06eJBQem9M4yqp z9C{%cIw%O~f*qxkHdY={J$tyEqWC@g3%PUy)VcQto%?6zd{A?KYk&gep$^hW=XZoc zf>8!(no@L;ve@*vtUCGs)99qb74zXw&dt3;JdDVC%-P1$xAQVuOe&7f+i*ouVQFf_ zEmO_nBa2xmJrtxUKgIk{^CzqoEX+|4pC6B7ZNhM0djpWc~3q z6c^5NJ8jpf^3;z}+Y~19sl6g7uY{r21Mn-2wQ;&kS80^SXaarVL$%{HLs^ccDT%*H zN4<}doSo!Vo9Gf2atCp)vkpu5iw5E1eehM%G1%(BzNu!ME;LjNXJR9GS4hJhSD)X6 z%(XgZpUhap)kTREqMVRHx^Qm+v^ zE8W=Y>BY}p6+6o+%B$^C0AE&*NI|#;yiyoH`n^c4E%9j8kH!r+5nq4H6Send(common::widen(sData))) Log(L"Failed to connect to named pipe. Service may be stopped."); - - if (hPipe != INVALID_HANDLE_VALUE) - CloseHandle(hPipe); + else + Log(common::widen(sData)); } void Log(wstring input) @@ -980,10 +990,13 @@ int VerifyTopology(void) DWORD CheckRemoteStreamingProcesses(void) { bool bWhiteListConfigured = Prefs.WhiteList.size() > 0 ? true : false; + bool bFsExclusionListConfigured = Prefs.FsExclusions.size() > 0 ? true : false; bool bWhitelistProcessFound = false; + bool bFsExclusionProcessFound = false; bool bStreamingProcessFound = false; bool bSunshineSvcProcessFound = false; wstring sWhitelistProcessFound = L""; + wstring sFsExclusionProcessFound = L""; DWORD ReturnValue = 0; // Iterate over currently running processes @@ -1009,6 +1022,19 @@ DWORD CheckRemoteStreamingProcesses(void) sWhitelistProcessFound = L""; bWhitelistProcessFound = true; } + // look for user idle mode fullscreen excluded processes + if (Prefs.bIdleFsExclusionsEnabled && bFsExclusionListConfigured && !bFsExclusionProcessFound) + for (auto& w : Prefs.FsExclusions) + if (w.Application != L"") + if (!_tcsicmp(entry.szExeFile, w.Application.c_str())) + { + if (w.Name != L"") + sFsExclusionProcessFound = w.Name; + else + sFsExclusionProcessFound = L""; + bFsExclusionProcessFound = true; + } + // look for currently running known streaming processes if (Prefs.RemoteStreamingCheck && !bStreamingProcessFound) for (auto& w : Prefs.Remote.stream_proc_list) @@ -1019,7 +1045,7 @@ DWORD CheckRemoteStreamingProcesses(void) if (Prefs.RemoteStreamingCheck && !bSunshineSvcProcessFound) if (!_tcsicmp(entry.szExeFile, SUNSHINE_FILE_SVC)) bSunshineSvcProcessFound = true; - } while (!(bStreamingProcessFound && bWhitelistProcessFound && bSunshineSvcProcessFound) && Process32Next(snapshot, &entry)); + } while (!(bStreamingProcessFound && bWhitelistProcessFound && bSunshineSvcProcessFound && bFsExclusionProcessFound) && Process32Next(snapshot, &entry)); CloseHandle(snapshot); @@ -1040,8 +1066,9 @@ DWORD CheckRemoteStreamingProcesses(void) // was a remote streaming process currently running? ReturnValue ^= bStreamingProcessFound ? REMOTE_STEAM_CONNECTED : REMOTE_STEAM_NOT_CONNECTED; - // was a user idle mode whitelisted process currently running? + // was a user idle mode whitelisted / FS excluded process currently running? Prefs.Remote.sCurrentlyRunningWhitelistedProcess = sWhitelistProcessFound; + Prefs.Remote.sCurrentlyRunningFsExcludedProcess = sFsExclusionProcessFound; return ReturnValue; } bool FullscreenApplicationRunning(void) @@ -1293,4 +1320,9 @@ string Sunshine_GetConfVal(string buf, string conf_item) return buf.substr(pos + conf_item.length(), newl - (pos + conf_item.length())); } return ""; +} + +void NamedPipeCallback(std::wstring message) +{ + return; } \ No newline at end of file diff --git a/LGTV Companion User/Daemon.h b/LGTV Companion User/Daemon.h index dc02cb30..846a7615 100644 --- a/LGTV Companion User/Daemon.h +++ b/LGTV Companion User/Daemon.h @@ -110,3 +110,4 @@ static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam); DWORD Sunshine_CheckLog(void); std::string Sunshine_GetConfVal(std::string, std::string); std::string Sunshine_GetLogFile(); +void NamedPipeCallback(std::wstring); \ No newline at end of file