diff --git a/LGTV Companion Service/Service.cpp b/LGTV Companion Service/Service.cpp index f178c7a..65f9992 100644 --- a/LGTV Companion Service/Service.cpp +++ b/LGTV Companion Service/Service.cpp @@ -15,7 +15,7 @@ PREFS Prefs; //App preferences vector DeviceCtrlSessions; //CSession objects manage network connections with Display DWORD EventCallbackStatus = NULL; WSADATA WSAData; -mutex g_mutex; +//mutex g_mutex; wstring DataPath; vector HostIPs; @@ -313,6 +313,15 @@ VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) thread thread_obj(IPCThread); thread_obj.detach(); + if (SetProcessShutdownParameters(0x1E1, 0)) + Log("Setting shutdown parameter level 0x1E1"); +// else if (SetProcessShutdownParameters(0x3ff, 0)) + // Log("Setting shutdown parameter level 0x3FF"); + else + Log("Could not set shutdown parameter level"); + + SetProcessShutdownParameters(0x3FF,0); + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); //register to receive power notifications gPs1 = RegisterPowerSettingNotification(gSvcStatusHandle, &(GUID_CONSOLE_DISPLAY_STATE), DEVICE_NOTIFY_SERVICE_HANDLE); @@ -348,7 +357,7 @@ VOID ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHi if (dwCurrentState == SERVICE_START_PENDING) gSvcStatus.dwControlsAccepted = 0; - else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_POWEREVENT;// | SERVICE_ACCEPT_USERMODEREBOOT; //does not work + else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN | SERVICE_ACCEPT_POWEREVENT; //SERVICE_ACCEPT_PRESHUTDOWN | // | SERVICE_ACCEPT_USERMODEREBOOT; //does not work if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) @@ -361,10 +370,26 @@ VOID ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHi // Called by SCM whenever a control code is sent to the service using the ControlService function. dwCtrl - control code DWORD SvcCtrlHandler(DWORD dwCtrl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) { + bool bThreadNotFinished; + switch (dwCtrl) { - case SERVICE_CONTROL_STOP: // works - ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + case SERVICE_CONTROL_STOP: + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 20000); + + do + { + bThreadNotFinished = false; + + for (auto& dev : DeviceCtrlSessions) + { + if (dev.IsBusy()) + bThreadNotFinished = true; + } + if (bThreadNotFinished) + Sleep(100); + } while (bThreadNotFinished); + // Signal the service to stop. SetEvent(ghSvcStopEvent); @@ -441,7 +466,7 @@ DWORD SvcCtrlHandler(DWORD dwCtrl, DWORD dwEventType, LPVOID lpEventData, LPVOI default:break; } break; - case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_PRESHUTDOWN: if (EventCallbackStatus == SYSTEM_EVENT_REBOOT) { Log("** System is restarting."); @@ -463,7 +488,29 @@ DWORD SvcCtrlHandler(DWORD dwCtrl, DWORD dwEventType, LPVOID lpEventData, LPVOI Log("WARNING! The application did not receive an Event Subscription Callback prior to system shutting down. Unable to determine if system is shutting down or restarting."); DispatchSystemPowerEvent(SYSTEM_EVENT_UNSURE); } + + //copy paste from the STOP event below + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 20000); + + do + { + bThreadNotFinished = false; + + for (auto& dev : DeviceCtrlSessions) + { + if (dev.IsBusy()) + bThreadNotFinished = true; + } + if(bThreadNotFinished) + Sleep(100); + } while (bThreadNotFinished); + + + // Signal the service to stop. + SetEvent(ghSvcStopEvent); + ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); break; + case SERVICE_CONTROL_INTERROGATE: Log("SERVICE_CONTROL_INTERROGATE"); break; diff --git a/LGTV Companion Service/Service.h b/LGTV Companion Service/Service.h index 043a174..4a95078 100644 --- a/LGTV Companion Service/Service.h +++ b/LGTV Companion Service/Service.h @@ -31,7 +31,7 @@ #pragma comment(lib, "Advapi32.lib") #define APPNAME L"LGTV Companion" -#define APPVERSION L"1.2.3" +#define APPVERSION L"1.3.0" #define SVCNAME L"LGTVsvc" #define SVCDISPLAYNAME L"LGTV Companion Service" #define SERVICE_PORT "3000" @@ -45,7 +45,7 @@ #define DEFAULT_RESTART {"restart"} #define DEFAULT_SHUTDOWN {"shutdown","power off"} -#define SERVICE_DEPENDENCIES NULL //L"Dhcp\0Dnscache\0\0" +#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 5 // wait to spawn new thread (seconds) @@ -103,9 +103,11 @@ class CSession { void Stop(); void SystemEvent(DWORD); SESSIONPARAMETERS GetParams(); + bool IsBusy(); private: - bool ThreadedOperationsRunning = false; - void TurnOnDisplay(void); + bool ThreadedOpDisplayOn = false; + bool ThreadedOpDisplayOff = false; + void TurnOnDisplay(void); void TurnOffDisplay(void); SESSIONPARAMETERS Parameters; }; diff --git a/LGTV Companion Service/Session.cpp b/LGTV Companion Service/Session.cpp index 7ccee34..4a2aa8f 100644 --- a/LGTV Companion Service/Session.cpp +++ b/LGTV Companion Service/Session.cpp @@ -45,6 +45,16 @@ SESSIONPARAMETERS CSession::GetParams(void) mMutex.unlock(); return copy; } +bool CSession::IsBusy(void) +{ + bool ret; + //thread safe section + while (!mMutex.try_lock()) + Sleep(MUTEX_WAIT); + ret = ThreadedOpDisplayOn||ThreadedOpDisplayOff; + mMutex.unlock(); + return ret; +} void CSession::TurnOnDisplay(void) { string s; @@ -54,14 +64,15 @@ void CSession::TurnOnDisplay(void) Sleep(MUTEX_WAIT); - if (!ThreadedOperationsRunning) + if (!ThreadedOpDisplayOn) { s = Parameters.DeviceId; s += ", spawning DisplayPowerOnThread()."; - ThreadedOperationsRunning = true; -// TimeStamp = time(0); - thread thread_obj(DisplayPowerOnThread, &Parameters, &ThreadedOperationsRunning, Parameters.PowerOnTimeout); + ThreadedOpDisplayOn = true; + // ThreadedOperationsTimeStamp = time(0); + + thread thread_obj(DisplayPowerOnThread, &Parameters, &ThreadedOpDisplayOn, Parameters.PowerOnTimeout); thread_obj.detach(); } else @@ -81,14 +92,14 @@ void CSession::TurnOffDisplay(void) while (!mMutex.try_lock()) Sleep(MUTEX_WAIT); - if (!ThreadedOperationsRunning && Parameters.SessionKey != "") + if (!ThreadedOpDisplayOff && Parameters.SessionKey != "") { s = Parameters.DeviceId; s += ", spawning DisplayPowerOffThread()."; - ThreadedOperationsRunning = true; - // TimeStamp = time(0); - thread thread_obj(DisplayPowerOffThread, &Parameters, &ThreadedOperationsRunning); + ThreadedOpDisplayOff = true; + // ThreadedOperationsTimeStamp = time(0); + thread thread_obj(DisplayPowerOffThread, &Parameters, &ThreadedOpDisplayOff); thread_obj.detach(); } else @@ -424,8 +435,14 @@ void WOLthread (SESSIONPARAMETERS* CallingSessionParameters, bool* CallingSessio // THREAD: Spawned when the device should power OFF. void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* CallingSessionThreadRunning) { + time_t origtim = time(0); + + // Log("DEBUG INFO: DisplayPowerOffThread() running..."); if (!CallingSessionParameters) + { +// Log("DEBUG INFO: DisplayPowerOffThread() :: CallingSessionParameters is zero"); return; + } if (CallingSessionParameters->SessionKey != "") //assume we have paired here. Doe not make sense to try pairing when display shall turn off. { string host = CallingSessionParameters->IP; @@ -438,16 +455,28 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca try { - net::io_context ioc; +// Log("DEBUG INFO: DisplayPowerOffThread() creating websocket..."); + + net::io_context ioc; size_t ckf = handshake.find(ck); handshake.replace(ckf, ck.length(), key); tcp::resolver resolver{ ioc }; websocket::stream ws{ ioc }; +// Log("DEBUG INFO: DisplayPowerOffThread() resolving..."); + auto const results = resolver.resolve(host, SERVICE_PORT); + // Log("DEBUG INFO: DisplayPowerOffThread() connecting..."); + auto ep = net::connect(ws.next_layer(), results); host += ':' + std::to_string(ep.port()); + if (time(0) - origtim > 10) // this thread should not run too long + { + Log("DisplayPowerOffThread() forced exit."); + goto threadoffend; + } +// Log("DEBUG INFO: DisplayPowerOffThread() setting options..."); // Set a decorator to change the User-Agent of the handshake ws.set_option(websocket::stream_base::decorator( @@ -457,13 +486,32 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-LGTVsvc"); })); + if (time(0) - origtim > 10) // this thread should not run too long + { + Log("DisplayPowerOffThread() forced exit."); + goto threadoffend; + } + + // Log("DEBUG INFO: DisplayPowerOffThread() handshake..."); + ws.handshake(host, "/"); - + + if (time(0) - origtim > 10) // this thread should not run too long + { + Log("WARNING! DisplayPowerOffThread() - forced exit"); + goto threadoffend; + } + +// Log("DEBUG INFO: DisplayPowerOffThread() sending..."); + beast::flat_buffer buffer; + ws.write(net::buffer(std::string(handshake))); ws.read(buffer); // read the response ws.write(net::buffer(std::string(poweroffmess))); ws.read(buffer); // read the response +// Log("DEBUG INFO: DisplayPowerOffThread() closing..."); + ws.close(websocket::close_code::normal); logmsg = device; @@ -479,6 +527,11 @@ void DisplayPowerOffThread(SESSIONPARAMETERS* CallingSessionParameters, bool* Ca Log(logmsg); } } + else + Log("WARNING! DisplayPowerOffThread() - no pairing key"); + +threadoffend: + //thread safe section while (!mMutex.try_lock()) Sleep(MUTEX_WAIT); diff --git a/LGTV Companion Setup/Product.wxs b/LGTV Companion Setup/Product.wxs index 35bd79d..2351bdd 100644 --- a/LGTV Companion Setup/Product.wxs +++ b/LGTV Companion Setup/Product.wxs @@ -1,6 +1,7 @@ + - + diff --git a/LGTV Companion UI/LGTV Companion UI.cpp b/LGTV Companion UI/LGTV Companion UI.cpp index 60db134..1887e0e 100644 --- a/LGTV Companion UI/LGTV Companion UI.cpp +++ b/LGTV Companion UI/LGTV Companion UI.cpp @@ -20,7 +20,7 @@ INSTALLATION AND USAGE a) Power ON all TVs b) Ensure that the TV can be woken via the network. For the CX line of displays this is accomplished by navigating to Settings (cog button on remote)->All Settings->Connection-> - Mobile Connection Management->TV On with Mobile and enable ''Turn On via Wi-Fi'. + Mobile Connection Management->TV On with Mobile and enable 'Turn On via Wi-Fi'. c) Open the admin interface of your router, and set a static DHCP lease for your WebOS devices, i.e. to ensure that the displays always have the same IP-address on your LAN. @@ -105,8 +105,12 @@ CHANGELOG - Minor bug fixes - Installer/upgrade bug fixed - v 1.3.0 - Output Host IP in log (for debugging purposes) - - Display warning in UI when TV is configured on different subnet + v 1.3.0 - Output Host IP in log (for debugging purposes) and additional logging + - Display warning in UI when TV is configured on a subnet different from PC + - Add service dependencies + - Set service shutdown priority + - Implemented option to automatically check for new application version (off by default) + - Bug fixes and optimisations LICENSE Copyright (c) 2021 Jörgen Persson @@ -226,6 +230,13 @@ int APIENTRY wWinMain(_In_ HINSTANCE Instance, ShowWindow(hMainWnd, SW_SHOW); UpdateWindow(hMainWnd); + // spawn thread to check for updated version of the app. + if (Prefs.AutoUpdate) + { + thread thread_obj(VersionCheckThread, hMainWnd); + thread_obj.detach(); + } + // message loop: while (GetMessage(&msg, NULL, 0, 0)) { @@ -277,6 +288,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) SetDlgItemText(hWnd, IDC_SPLIT, L"&Scan"); SendMessageW(GetDlgItem(hWnd, IDC_OPTIONS), BM_SETIMAGE, IMAGE_ICON, (LPARAM)hOptionsIcon); }break; + case APP_NEW_VERSION: + { + ShowWindow(GetDlgItem(hWnd, IDC_NEWVERSION), SW_SHOW); + }break; case APP_MESSAGE_ADD: { HWND hDeviceWnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DEVICE), hWnd, (DLGPROC)DeviceWndProc); @@ -611,10 +626,19 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) default:break; } }break; + case WM_NOTIFY: { switch (((NMHDR*)lParam)->code) { + case NM_CLICK: + { + //download new version + if (wParam == IDC_NEWVERSION) + { + ShellExecute(0, 0, NEWRELEASELINK, 0, 0, SW_SHOW); + } + } case BCN_DROPDOWN: { NMBCDROPDOWN* pDropDown = (NMBCDROPDOWN*)lParam; @@ -1094,7 +1118,8 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l i++; } CheckDlgButton(hWnd, IDC_LOGGING, Prefs.Logging ? BST_CHECKED : BST_UNCHECKED); - EnableWindow(GetDlgItem(hWnd, IDOK), true); + CheckDlgButton(hWnd, IDC_AUTOUPDATE, Prefs.AutoUpdate ? BST_CHECKED : BST_UNCHECKED); + EnableWindow(GetDlgItem(hWnd, IDOK), true); }break; case WM_COMMAND: { @@ -1105,6 +1130,7 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l switch (LOWORD(wParam)) { case IDC_LOGGING: + case IDC_AUTOUPDATE: { EnableWindow(GetDlgItem(hWnd, IDOK), true); }break; @@ -1117,6 +1143,14 @@ LRESULT CALLBACK OptionsWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l { Prefs.PowerOnTimeout = atoi(narrow(GetWndText(GetDlgItem(hWnd, IDC_TIMEOUT))).c_str()); Prefs.Logging = IsDlgButtonChecked(hWnd, IDC_LOGGING); + + bool TempAutoUpdate = IsDlgButtonChecked(hWnd, IDC_AUTOUPDATE); + if (TempAutoUpdate && !Prefs.AutoUpdate) + { + thread thread_obj(VersionCheckThread, hMainWnd); + thread_obj.detach(); + } + Prefs.AutoUpdate = IsDlgButtonChecked(hWnd, IDC_AUTOUPDATE); int count = ListView_GetItemCount(GetDlgItem(hWnd, IDC_LIST)); Prefs.EventLogRestartString.clear(); @@ -1313,7 +1347,11 @@ bool ReadConfigFile() j = jsonPrefs[JSON_PREFS_NODE][JSON_LOGGING]; if (!j.empty() && j.is_boolean()) Prefs.Logging = j.get(); - + + j = jsonPrefs[JSON_PREFS_NODE][JSON_AUTOUPDATE]; + if (!j.empty() && j.is_boolean()) + Prefs.AutoUpdate = j.get(); + return true; } } @@ -1539,6 +1577,7 @@ void WriteConfigFile(void) prefs[JSON_PREFS_NODE][JSON_VERSION] = (int)1; prefs[JSON_PREFS_NODE][JSON_PWRONTIMEOUT] = (int)Prefs.PowerOnTimeout; prefs[JSON_PREFS_NODE][JSON_LOGGING] = (bool)Prefs.Logging; + prefs[JSON_PREFS_NODE][JSON_AUTOUPDATE] = (bool)Prefs.AutoUpdate; for (auto& item : Prefs.EventLogRestartString) prefs[JSON_PREFS_NODE][JSON_EVENT_RESTART_STRINGS].push_back(item); @@ -1610,4 +1649,61 @@ vector GetOwnIP(void) } } return IPs; +} + +void VersionCheckThread(HWND hWnd) +{ + IStream* stream; + char buff[100]; + string s; + unsigned long bytesRead; + + + if (URLOpenBlockingStream(0, VERSIONCHECKLINK, &stream, 0, 0)) + return;// error + + while (true) + { + stream->Read(buff, 100, &bytesRead); + + if (0U == bytesRead) + break; + s.append(buff, bytesRead); + + }; + + stream->Release(); + + size_t find = s.find("\"tag_name\":", 0); + if (find != string::npos) + { + size_t begin = s.find_first_of("0123456789", find); + if (begin != string::npos) + { + size_t end = s.find("\"", begin); + string lastver = s.substr(begin, end - begin); + + vector local_ver = stringsplit(narrow(APP_VERSION), "."); + vector remote_ver = stringsplit(lastver, "."); + + if (local_ver.size() < 3 || remote_ver.size() < 3) + return; + int local_ver_major = atoi(local_ver[0].c_str()); + int local_ver_minor = atoi(local_ver[1].c_str()); + int local_ver_patch = atoi(local_ver[2].c_str()); + + int remote_ver_major = atoi(remote_ver[0].c_str()); + int remote_ver_minor = atoi(remote_ver[1].c_str()); + int remote_ver_patch = atoi(remote_ver[2].c_str()); + + + if ((remote_ver_major > local_ver_major) || + (remote_ver_major == local_ver_major) && (remote_ver_minor > local_ver_minor) || + (remote_ver_major == local_ver_major) && (remote_ver_minor == local_ver_minor) && (remote_ver_patch > local_ver_patch)) + { + PostMessage(hWnd, APP_NEW_VERSION, 0, 0); + } + } + } + return; } \ No newline at end of file diff --git a/LGTV Companion UI/LGTV Companion UI.h b/LGTV Companion UI/LGTV Companion UI.h index e7a7202..991c931 100644 --- a/LGTV Companion UI/LGTV Companion UI.h +++ b/LGTV Companion UI/LGTV Companion UI.h @@ -3,6 +3,7 @@ #pragma comment(lib, "wevtapi.lib") #pragma comment(lib, "SetupAPI.lib") #pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "urlmon.lib") #if defined _M_IX86 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +46,7 @@ #include //#include #include +#include #include "resource.h" #include "nlohmann/json.hpp" @@ -51,7 +54,7 @@ #define APPNAME_SHORT L"LGTVcomp" #define APPNAME_FULL L"LGTV Companion" -#define APP_VERSION L"1.2.3" +#define APP_VERSION L"1.3.0" #define WINDOW_CLASS_UNIQUE L"YOLOx0x0x0181818" #define NOTIFY_NEW_COMMANDLINE 1 @@ -60,6 +63,7 @@ #define JSON_EVENT_SHUTDOWN_STRINGS "LocalEventLogShutdownString" #define JSON_VERSION "Version" #define JSON_LOGGING "ExtendedLog" +#define JSON_AUTOUPDATE "AutoUpdate" #define JSON_PWRONTIMEOUT "PowerOnTimeOut" #define DEFAULT_RESTART {"restart"} #define DEFAULT_SHUTDOWN {"shutdown","power off"} @@ -72,6 +76,7 @@ #define APP_MESSAGE_TURNOFF WM_USER+6 #define APP_MESSAGE_REMOVE WM_USER+7 #define APP_MESSAGE_APPLY WM_USER+8 +#define APP_NEW_VERSION WM_USER+9 #define APP_CMDLINE_ON 1 #define APP_CMDLINE_OFF 2 @@ -82,6 +87,8 @@ #define DEVICEWINDOW_TITLE_MANAGE L"Configure device" #define PIPENAME TEXT("\\\\.\\pipe\\LGTVyolo") +#define NEWRELEASELINK L"https://github.com/JPersson77/LGTVCompanion/releases" +#define VERSIONCHECKLINK L"https://api.github.com/repos/JPersson77/LGTVCompanion/releases" struct PREFS { std::vector EventLogRestartString = DEFAULT_RESTART; @@ -89,6 +96,7 @@ struct PREFS { bool Logging = false; int version = 1; int PowerOnTimeout = 40; + bool AutoUpdate = false; }; struct SESSIONPARAMETERS { std::string DeviceId; @@ -120,4 +128,5 @@ std::vector stringsplit(std::string, std::string); void CommunicateWithService(std::string); void WriteConfigFile(void); -std::vector GetOwnIP(void); \ No newline at end of file +std::vector GetOwnIP(void); +void VersionCheckThread(HWND); diff --git a/LGTV Companion UI/LGTV Companion UI.rc b/LGTV Companion UI/LGTV Companion UI.rc index 8068f4e..098e852 100644 Binary files a/LGTV Companion UI/LGTV Companion UI.rc and b/LGTV Companion UI/LGTV Companion UI.rc differ diff --git a/LGTV Companion UI/resource.h b/LGTV Companion UI/resource.h index 271abce..8b3d7be 100644 --- a/LGTV Companion UI/resource.h +++ b/LGTV Companion UI/resource.h @@ -25,9 +25,12 @@ #define IDC_SPIN 1009 #define IDC_TIMEOUT 1010 #define IDC_LOGGING 1011 +#define IDC_LOGGING2 1012 +#define IDC_AUTOUPDATE 1012 #define IDC_SYSLINK 1014 #define IDC_LIST 1018 #define IDC_SYSLINK3 1019 +#define IDC_NEWVERSION 1021 #define ID_ADD_MANAGE 32771 #define ID_ADD_MANAGE32772 32772 #define ID_ADD_REMOVE 32773 @@ -54,7 +57,7 @@ #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 144 #define _APS_NEXT_COMMAND_VALUE 32788 -#define _APS_NEXT_CONTROL_VALUE 1020 +#define _APS_NEXT_CONTROL_VALUE 1022 #define _APS_NEXT_SYMED_VALUE 110 #endif #endif