From 329e7111fc54eb91808af74ef7fcefc47171125a Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 04:17:40 -0400 Subject: [PATCH 01/22] Create StayAwakeCore Class. Add SimulateInput function. --- StayAwake/StayAwake.vcxproj | 3 +- StayAwake/StayAwake.vcxproj.filters | 9 ++++-- StayAwake/StayAwakeCore.cpp | 49 +++++++++++++++++++++++++++++ StayAwake/StayAwakeCore.h | 37 ++++++++++++++++++++++ StayAwake/StayAwakeDlg.cpp | 44 +------------------------- StayAwake/StayAwakeDlg.h | 22 +------------ 6 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 StayAwake/StayAwakeCore.cpp create mode 100644 StayAwake/StayAwakeCore.h diff --git a/StayAwake/StayAwake.vcxproj b/StayAwake/StayAwake.vcxproj index c9cac35..23c26c9 100644 --- a/StayAwake/StayAwake.vcxproj +++ b/StayAwake/StayAwake.vcxproj @@ -212,8 +212,8 @@ - + @@ -224,6 +224,7 @@ + Create diff --git a/StayAwake/StayAwake.vcxproj.filters b/StayAwake/StayAwake.vcxproj.filters index 20383b7..856a3d4 100644 --- a/StayAwake/StayAwake.vcxproj.filters +++ b/StayAwake/StayAwake.vcxproj.filters @@ -15,9 +15,6 @@ - - Header Files - Header Files @@ -42,6 +39,9 @@ Header Files + + Header Files + @@ -62,6 +62,9 @@ Source Files + + Source Files + diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp new file mode 100644 index 0000000..5685489 --- /dev/null +++ b/StayAwake/StayAwakeCore.cpp @@ -0,0 +1,49 @@ +#include "pch.h" +#include "StayAwakeCore.h" + +void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) +{ + switch (inputCode) + { + case 1: + inputName = L"Volume Down && Up"; + keybd_event(VK_VOLUME_DOWN, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_VOLUME_DOWN, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + Sleep(10); + keybd_event(VK_VOLUME_UP, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_VOLUME_UP, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + break; + + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + { + inputName = L"Unassigned Key #" + to_wstring(inputCode - 1); + BYTE keycode{ static_cast(VK_UNASSIGNED_01 + inputCode - 2) }; + keybd_event(keycode, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(keycode, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + break; + } + + case 11: + inputName = L"Unassigned Key #10"; + keybd_event(VK_UNASSIGNED_10, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_UNASSIGNED_10, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + break; + + default: + inputName = L"Scroll Lock cycling"; + keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + Sleep(10); + keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + break; + } +} diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h new file mode 100644 index 0000000..fe4d7d1 --- /dev/null +++ b/StayAwake/StayAwakeCore.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include + +constexpr auto VK_UNASSIGNED_01 = 0x97; +constexpr auto VK_UNASSIGNED_10 = 0xE8; + +constexpr auto PREF_INI_FILE = L"StayAwake.ini"; +constexpr auto PREF_DEFAULTS = L"Defaults"; +constexpr auto PREF_LEGACY_KEYCODE = L"AwakeKeyCode"; +constexpr auto PREF_SELECTED_KEYCODES = L"SelectedKeyCodes"; +constexpr auto PREF_AWAKE_PAUSED = L"AwakePaused"; +constexpr auto PREF_START_MINIMIZED = L"StartMinimized"; +constexpr auto PREF_MULTI_INSTANCE = L"MultipleInstancesAllowed"; +constexpr auto PREF_INTERVAL_LEGACY = L"TimerIntervalInSeconds"; +constexpr auto PREF_INTERVAL_MINIMUM = L"MinimumIntervalInSeconds"; +constexpr auto PREF_INTERVAL_MAXIMUM = L"MaximumIntervalInSeconds"; + +constexpr auto MIN_PERIOD{ 10 }; +constexpr auto MAX_PERIOD{ 9990 }; +constexpr auto DEF_PERIOD{ 240 }; + +constexpr auto LEN_ROSTER_KEYCODES{ 12 }; +constexpr auto DEF_SELECTED_KEYCODES = L"001111111111"; + + +using std::string; +using std::wstring; +using std::to_wstring; + + +class StayAwakeCore +{ +public: + static void SimulateInput(int inputCode, wstring& inputName); +}; + diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 55ff507..ad26d92 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -379,49 +379,7 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() UINT nAwakeKeyCode{ m_RosterKeyCodes[rand() % m_RosterLength] }; wstring sAwakeKeyCode{}; - switch (nAwakeKeyCode) - { - case 1: - sAwakeKeyCode = L"Volume Down && Up"; - keybd_event(VK_VOLUME_DOWN, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_VOLUME_DOWN, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - Sleep(10); - keybd_event(VK_VOLUME_UP, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_VOLUME_UP, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - break; - - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - case 9: - case 10: - { - sAwakeKeyCode = L"Unassigned Key #" + to_wstring(nAwakeKeyCode - 1); - BYTE keycode{ static_cast(VK_UNASSIGNED_01 + nAwakeKeyCode - 2) }; - keybd_event(keycode, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(keycode, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - break; - } - - case 11: - sAwakeKeyCode = L"Unassigned Key #10"; - keybd_event(VK_UNASSIGNED_10, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_UNASSIGNED_10, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - break; - - default: - sAwakeKeyCode = L"Scroll Lock cycling"; - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - Sleep(10); - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - break; - } + StayAwakeCore::SimulateInput(nAwakeKeyCode, sAwakeKeyCode); SYSTEMTIME lastTime{}; GetLocalTime(&lastTime); diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index 7d453d5..d5d83f5 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -1,35 +1,15 @@ #pragma once +#include "StayAwakeCore.h" #include "Utils.h" #define WM_POST_OPEN (WM_APP + 1) #define WM_TRAY_NOTIFY (WM_APP + 2) #define WM_RESTORE_DIALOG theApp.WM_SHOWFIRSTINSTANCE -constexpr auto VK_UNASSIGNED_01 = 0x97; -constexpr auto VK_UNASSIGNED_10 = 0xE8; - -constexpr auto PREF_INI_FILE = L"StayAwake.ini"; -constexpr auto PREF_DEFAULTS = L"Defaults"; -constexpr auto PREF_LEGACY_KEYCODE = L"AwakeKeyCode"; -constexpr auto PREF_SELECTED_KEYCODES = L"SelectedKeyCodes"; -constexpr auto PREF_AWAKE_PAUSED = L"AwakePaused"; -constexpr auto PREF_START_MINIMIZED = L"StartMinimized"; -constexpr auto PREF_MULTI_INSTANCE = L"MultipleInstancesAllowed"; -constexpr auto PREF_INTERVAL_LEGACY = L"TimerIntervalInSeconds"; -constexpr auto PREF_INTERVAL_MINIMUM = L"MinimumIntervalInSeconds"; -constexpr auto PREF_INTERVAL_MAXIMUM = L"MaximumIntervalInSeconds"; - constexpr auto BTN_TEXT_PAUSE = L"&Pause"; constexpr auto BTN_TEXT_RESUME = L"&Resume"; -constexpr auto MIN_PERIOD{ 10 }; -constexpr auto MAX_PERIOD{ 9990 }; -constexpr auto DEF_PERIOD{ 240 }; - -constexpr auto LEN_ROSTER_KEYCODES{ 12 }; -constexpr auto DEF_SELECTED_KEYCODES = L"001111111111"; - const wstring MIN_MAX_PERIOD = to_wstring(MIN_PERIOD) + L" and " + to_wstring(MAX_PERIOD); const wstring INTERVAL_TOOLTIP = L"Number between " + MIN_MAX_PERIOD; const wstring INTERVAL_WARNING = L"Please enter a value between " + MIN_MAX_PERIOD; From a7f6fc81174d42439300e538f7e624d471167446 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 05:35:40 -0400 Subject: [PATCH 02/22] Replace keybd_event (deprecated) with SendInput DRY code with functions PressOneKey and PressTwoKeys --- StayAwake/StayAwakeCore.cpp | 67 ++++++++++++++++++++++++++++--------- StayAwake/StayAwakeCore.h | 4 +++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index 5685489..673b036 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -7,11 +7,7 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) { case 1: inputName = L"Volume Down && Up"; - keybd_event(VK_VOLUME_DOWN, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_VOLUME_DOWN, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - Sleep(10); - keybd_event(VK_VOLUME_UP, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_VOLUME_UP, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + PressTwoKeys(VK_VOLUME_DOWN, VK_VOLUME_UP, true); break; case 2: @@ -25,25 +21,66 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) case 10: { inputName = L"Unassigned Key #" + to_wstring(inputCode - 1); - BYTE keycode{ static_cast(VK_UNASSIGNED_01 + inputCode - 2) }; - keybd_event(keycode, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(keycode, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + PressOneKey(static_cast(VK_UNASSIGNED_01 + inputCode - 2), false); break; } case 11: inputName = L"Unassigned Key #10"; - keybd_event(VK_UNASSIGNED_10, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_UNASSIGNED_10, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + PressOneKey(VK_UNASSIGNED_10, false); break; default: inputName = L"Scroll Lock cycling"; - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); - Sleep(10); - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); - keybd_event(VK_SCROLL, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + PressTwoKeys(VK_SCROLL, VK_SCROLL, false); break; } } + +void StayAwakeCore::PressOneKey(BYTE vk, bool extended) +{ + INPUT input[2] = {}; + + auto flags = (extended) ? KEYEVENTF_EXTENDEDKEY : 0; + + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = vk; + input[0].ki.wScan = 0; + input[0].ki.dwFlags = flags; + + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = vk; + input[1].ki.wScan = 0; + input[1].ki.dwFlags = KEYEVENTF_KEYUP | flags; + + SendInput(2, input, sizeof(INPUT)); +} + +void StayAwakeCore::PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended) +{ + INPUT input[4] = {}; + + auto flags = (extended) ? KEYEVENTF_EXTENDEDKEY : 0; + + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = vkFirst; + input[0].ki.wScan = 0; + input[0].ki.dwFlags = flags; + + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = vkFirst; + input[1].ki.wScan = 0; + input[1].ki.dwFlags = KEYEVENTF_KEYUP | flags; + + input[2].type = INPUT_KEYBOARD; + input[2].ki.wVk = vkSecond; + input[2].ki.wScan = 0; + input[2].ki.dwFlags = flags; + + input[3].type = INPUT_KEYBOARD; + input[3].ki.wVk = vkSecond; + input[3].ki.wScan = 0; + input[3].ki.dwFlags = KEYEVENTF_KEYUP | flags; + + SendInput(4, input, sizeof(INPUT)); +} diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index fe4d7d1..bd2cd51 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -33,5 +33,9 @@ class StayAwakeCore { public: static void SimulateInput(int inputCode, wstring& inputName); + +private: + static void PressOneKey(BYTE vk, bool extended = false); + static void PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended = false); }; From 537c56dd77b6a477100f5506d46544665f835148 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 06:00:37 -0400 Subject: [PATCH 03/22] DEBUG_DISPLAY_IDLE_TICKS using GetIdleTimeMilliseconds() --- StayAwake/StayAwakeCore.cpp | 11 +++++++++++ StayAwake/StayAwakeCore.h | 1 + StayAwake/StayAwakeDlg.cpp | 7 +++++++ StayAwake/StayAwakeDlg.h | 2 ++ 4 files changed, 21 insertions(+) diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index 673b036..9bbf671 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -37,6 +37,17 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) } } +ULONGLONG StayAwakeCore::GetIdleTimeMilliseconds() +{ + LASTINPUTINFO lii = {}; + lii.cbSize = sizeof(LASTINPUTINFO); + + if (!GetLastInputInfo(&lii)) + return 0; // error case + + return GetTickCount64() - lii.dwTime; +} + void StayAwakeCore::PressOneKey(BYTE vk, bool extended) { INPUT input[2] = {}; diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index bd2cd51..a535ac0 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -33,6 +33,7 @@ class StayAwakeCore { public: static void SimulateInput(int inputCode, wstring& inputName); + static ULONGLONG GetIdleTimeMilliseconds(); private: static void PressOneKey(BYTE vk, bool extended = false); diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index ad26d92..9d47a46 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -384,7 +384,14 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() SYSTEMTIME lastTime{}; GetLocalTime(&lastTime); SetDlgItemText(IDC_STAYAWAKE_LAST_EVENT, Utils::formatSystemTime(lastTime, L"Last StayAwake event").c_str()); + +#ifdef DEBUG_DISPLAY_IDLE_TICKS + Sleep((rand() % 5) + 1); // simulate a small delay to get a more accurate idle time + ULONGLONG idleTime = StayAwakeCore::GetIdleTimeMilliseconds(); + SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L":" + to_wstring(idleTime) + L" ms]").c_str()); +#else SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L"]").c_str()); +#endif UINT nTimerSeconds{ m_IntervalMinSeconds }; if (m_IntervalMinSeconds != m_IntervalMaxSeconds) diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index d5d83f5..d697a4a 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -7,6 +7,8 @@ #define WM_TRAY_NOTIFY (WM_APP + 2) #define WM_RESTORE_DIALOG theApp.WM_SHOWFIRSTINSTANCE +#define DEBUG_DISPLAY_IDLE_TICKS + constexpr auto BTN_TEXT_PAUSE = L"&Pause"; constexpr auto BTN_TEXT_RESUME = L"&Resume"; From 1ba30aad72e5d67cb4fd8dd7dfbefc6b7c0db3b6 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 06:50:17 -0400 Subject: [PATCH 04/22] Add simulations for F13...F24 keys. Make Scroll Lock non-default. --- StayAwake/StayAwakeCore.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index 9bbf671..d7db4c5 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -5,6 +5,11 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) { switch (inputCode) { + case 0: + inputName = L"Scroll Lock cycling"; + PressTwoKeys(VK_SCROLL, VK_SCROLL, false); + break; + case 1: inputName = L"Volume Down && Up"; PressTwoKeys(VK_VOLUME_DOWN, VK_VOLUME_UP, true); @@ -30,9 +35,26 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) PressOneKey(VK_UNASSIGNED_10, false); break; + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + { + inputName = L"F" + to_wstring(inputCode); + PressOneKey(static_cast(VK_F13 + inputCode - 13), false); + break; + } + default: - inputName = L"Scroll Lock cycling"; - PressTwoKeys(VK_SCROLL, VK_SCROLL, false); + inputName = L"Invalid Awake Key"; break; } } From e706a6822942c6493295f1be96a6c0f70c9f7646 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 07:23:23 -0400 Subject: [PATCH 05/22] Add MouseMove & MouseMoveZero simulations --- StayAwake/StayAwakeCore.cpp | 38 +++++++++++++++++++++++++++++++++++++ StayAwake/StayAwakeCore.h | 2 ++ StayAwake/StayAwakeDlg.cpp | 2 +- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index d7db4c5..a5c51b0 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -53,6 +53,12 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) break; } + case 25: + inputName = L"Invisible Mouse Move"; + MouseMove(); + break; + + default: inputName = L"Invalid Awake Key"; break; @@ -117,3 +123,35 @@ void StayAwakeCore::PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended) SendInput(4, input, sizeof(INPUT)); } + +void StayAwakeCore::MouseMove() +{ + INPUT input[2] = {}; + + // Tiny move + input[0].type = INPUT_MOUSE; + input[0].mi.dx = 1; + input[0].mi.dy = 1; + input[0].mi.dwFlags = MOUSEEVENTF_MOVE; + + // Move back + input[1].type = INPUT_MOUSE; + input[1].mi.dx = -1; + input[1].mi.dy = -1; + input[1].mi.dwFlags = MOUSEEVENTF_MOVE; + + SendInput(2, input, sizeof(INPUT)); +} + +void StayAwakeCore::MouseMoveZero() +{ + INPUT input[1] = {}; + + // Zero move + input[0].type = INPUT_MOUSE; + input[0].mi.dx = 0; + input[0].mi.dy = 0; + input[0].mi.dwFlags = MOUSEEVENTF_MOVE; + + SendInput(1, input, sizeof(INPUT)); +} diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index a535ac0..fbf45f5 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -38,5 +38,7 @@ class StayAwakeCore private: static void PressOneKey(BYTE vk, bool extended = false); static void PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended = false); + static void MouseMove(); + static void MouseMoveZero(); }; diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 9d47a46..82d7be6 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -386,7 +386,7 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() SetDlgItemText(IDC_STAYAWAKE_LAST_EVENT, Utils::formatSystemTime(lastTime, L"Last StayAwake event").c_str()); #ifdef DEBUG_DISPLAY_IDLE_TICKS - Sleep((rand() % 5) + 1); // simulate a small delay to get a more accurate idle time + Sleep((rand() % 20) + 1); // simulate a small delay to get a more accurate idle time ULONGLONG idleTime = StayAwakeCore::GetIdleTimeMilliseconds(); SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L":" + to_wstring(idleTime) + L" ms]").c_str()); #else From 50f583db16c4f9c3d754b7d400956b7a1bf4131b Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 12:22:03 -0400 Subject: [PATCH 06/22] Refactor StayAwakeCore as an instantiable class Refactor StayAwakeCore from static utility to instantiable class - Remove static modifier from member SimulateInput() - Add explicit default constructor and destructor - Change calls to StayAwakeCore methods using instance variable --- StayAwake/StayAwakeCore.cpp | 4 ---- StayAwake/StayAwakeCore.h | 5 ++++- StayAwake/StayAwakeDlg.cpp | 4 ++-- StayAwake/StayAwakeDlg.h | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index a5c51b0..06020ef 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -24,11 +24,9 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) case 8: case 9: case 10: - { inputName = L"Unassigned Key #" + to_wstring(inputCode - 1); PressOneKey(static_cast(VK_UNASSIGNED_01 + inputCode - 2), false); break; - } case 11: inputName = L"Unassigned Key #10"; @@ -47,11 +45,9 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) case 22: case 23: case 24: - { inputName = L"F" + to_wstring(inputCode); PressOneKey(static_cast(VK_F13 + inputCode - 13), false); break; - } case 25: inputName = L"Invisible Mouse Move"; diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index fbf45f5..daada64 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -32,7 +32,10 @@ using std::to_wstring; class StayAwakeCore { public: - static void SimulateInput(int inputCode, wstring& inputName); + StayAwakeCore() {}; + ~StayAwakeCore() {}; + + void SimulateInput(int inputCode, wstring& inputName); static ULONGLONG GetIdleTimeMilliseconds(); private: diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 82d7be6..b981ff0 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -379,7 +379,7 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() UINT nAwakeKeyCode{ m_RosterKeyCodes[rand() % m_RosterLength] }; wstring sAwakeKeyCode{}; - StayAwakeCore::SimulateInput(nAwakeKeyCode, sAwakeKeyCode); + m_AwakeCore.SimulateInput(nAwakeKeyCode, sAwakeKeyCode); SYSTEMTIME lastTime{}; GetLocalTime(&lastTime); @@ -387,7 +387,7 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() #ifdef DEBUG_DISPLAY_IDLE_TICKS Sleep((rand() % 20) + 1); // simulate a small delay to get a more accurate idle time - ULONGLONG idleTime = StayAwakeCore::GetIdleTimeMilliseconds(); + ULONGLONG idleTime = m_AwakeCore.GetIdleTimeMilliseconds(); SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L":" + to_wstring(idleTime) + L" ms]").c_str()); #else SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L"]").c_str()); diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index d697a4a..815444a 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -53,6 +53,7 @@ class CStayAwakeDlg : public CDialogEx UINT m_IntervalMinSeconds{}; UINT m_IntervalMaxSeconds{}; + StayAwakeCore m_AwakeCore; NOTIFYICONDATA m_TrayData{}; CMenu m_menu; From 61891427a5c7bee330c4b4ddf01bd15cff673043 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 12:33:46 -0400 Subject: [PATCH 07/22] Preserve Volume Muted state with Volume Down & Up simulations --- StayAwake/StayAwakeCore.cpp | 81 ++++++++++++++++++++++++++++++++++++- StayAwake/StayAwakeCore.h | 10 +++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index 06020ef..26ccd5e 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -12,7 +12,10 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) case 1: inputName = L"Volume Down && Up"; - PressTwoKeys(VK_VOLUME_DOWN, VK_VOLUME_UP, true); + if (GetMuteState()) + PressThreeKeys(VK_VOLUME_DOWN, VK_VOLUME_UP, VK_VOLUME_MUTE, true); + else + PressTwoKeys(VK_VOLUME_DOWN, VK_VOLUME_UP, true); break; case 2: @@ -120,6 +123,45 @@ void StayAwakeCore::PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended) SendInput(4, input, sizeof(INPUT)); } +void StayAwakeCore::PressThreeKeys(BYTE vkFirst, BYTE vkSecond, BYTE vkThird, bool extended) +{ + INPUT input[6] = {}; + + auto flags = (extended) ? KEYEVENTF_EXTENDEDKEY : 0; + + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = vkFirst; + input[0].ki.wScan = 0; + input[0].ki.dwFlags = flags; + + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = vkFirst; + input[1].ki.wScan = 0; + input[1].ki.dwFlags = KEYEVENTF_KEYUP | flags; + + input[2].type = INPUT_KEYBOARD; + input[2].ki.wVk = vkSecond; + input[2].ki.wScan = 0; + input[2].ki.dwFlags = flags; + + input[3].type = INPUT_KEYBOARD; + input[3].ki.wVk = vkSecond; + input[3].ki.wScan = 0; + input[3].ki.dwFlags = KEYEVENTF_KEYUP | flags; + + input[4].type = INPUT_KEYBOARD; + input[4].ki.wVk = vkThird; + input[4].ki.wScan = 0; + input[4].ki.dwFlags = flags; + + input[5].type = INPUT_KEYBOARD; + input[5].ki.wVk = vkThird; + input[5].ki.wScan = 0; + input[5].ki.dwFlags = KEYEVENTF_KEYUP | flags; + + SendInput(6, input, sizeof(INPUT)); +} + void StayAwakeCore::MouseMove() { INPUT input[2] = {}; @@ -151,3 +193,40 @@ void StayAwakeCore::MouseMoveZero() SendInput(1, input, sizeof(INPUT)); } + +bool StayAwakeCore::GetMuteState() +{ + HRESULT hr{}; + + hr = CoInitialize(NULL); + if (FAILED(hr)) return false; + + IMMDeviceEnumerator* enumerator = nullptr; + IMMDevice* device = nullptr; + IAudioEndpointVolume* endpointVolume = nullptr; + + hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), + (void**)&enumerator); + if (FAILED(hr)) return false; + + hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + if (FAILED(hr)) { enumerator->Release(); return false; } + + hr = device->Activate(__uuidof(IAudioEndpointVolume), + CLSCTX_INPROC_SERVER, NULL, + (void**)&endpointVolume); + if (FAILED(hr)) { + device->Release(); + enumerator->Release(); + return false; + } + + BOOL bMuted{}; + hr = endpointVolume->GetMute(&bMuted); + if (FAILED(hr)) return false; + + return (bMuted==TRUE); +} + diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index daada64..bba1a5f 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -1,7 +1,13 @@ #pragma once + +#include +#include #include #include +#pragma comment(lib, "Ole32.lib") + + constexpr auto VK_UNASSIGNED_01 = 0x97; constexpr auto VK_UNASSIGNED_10 = 0xE8; @@ -41,7 +47,11 @@ class StayAwakeCore private: static void PressOneKey(BYTE vk, bool extended = false); static void PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended = false); + static void PressThreeKeys(BYTE vkFirst, BYTE vkSecond, BYTE vkThird, bool extended = false); + static void MouseMove(); static void MouseMoveZero(); + + bool GetMuteState(); }; From cb0a1b270122a3a75dbc3243b2c9dffd15525f73 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 13:39:27 -0400 Subject: [PATCH 08/22] Suppress StayAwake simulations if Windows is in Locked state --- StayAwake/StayAwakeCore.h | 3 ++- StayAwake/StayAwakeDlg.cpp | 26 ++++++++++++++++++++++++++ StayAwake/StayAwakeDlg.h | 3 ++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index bba1a5f..ef310f9 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -4,8 +4,9 @@ #include #include #include +#include -#pragma comment(lib, "Ole32.lib") +#pragma comment(lib, "Wtsapi32.lib") constexpr auto VK_UNASSIGNED_01 = 0x97; diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index b981ff0..4347b54 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -91,6 +91,7 @@ BEGIN_MESSAGE_MAP(CStayAwakeDlg, CDialogEx) ON_BN_CLICKED(IDC_ABOUT_BUTTON, &CStayAwakeDlg::OnClickedAboutButton) ON_BN_CLICKED(IDC_STAYAWAKE_PAUSE_RESUME_BTN, &CStayAwakeDlg::OnPauseResume) ON_BN_CLICKED(IDC_START_MINIMIZED, &CStayAwakeDlg::OnStartMinimized) + ON_WM_WTSSESSION_CHANGE() END_MESSAGE_MAP() @@ -160,6 +161,8 @@ void CStayAwakeDlg::OnSysCommand(UINT nID, LPARAM lParam) afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) { + WTSRegisterSessionNotification(m_hWnd, NOTIFY_FOR_THIS_SESSION); + if (GetPreference(PREF_MULTI_INSTANCE, L"N") != L"Y" && Utils::getProcessRunCount(L"StayAwake.exe") > 1) { ::PostMessage(HWND_BROADCAST, theApp.WM_SHOWFIRSTINSTANCE, 0, 0); @@ -368,6 +371,7 @@ void CStayAwakeDlg::OnDestroy() if (m_bMinimized) Shell_NotifyIcon(NIM_DELETE, &m_TrayData); m_menu.DestroyMenu(); + WTSUnRegisterSessionNotification(m_hWnd); CDialogEx::OnDestroy(); } @@ -376,6 +380,12 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() { if (!m_RosterLength) InitRosterKeyCodes(); + if (m_bSystemLocked) + { + SetDlgItemText(IDC_STAYAWAKE_NEXT_EVENT, L"PAUSED since Windows is LOCKED"); + return; + } + UINT nAwakeKeyCode{ m_RosterKeyCodes[rand() % m_RosterLength] }; wstring sAwakeKeyCode{}; @@ -478,3 +488,19 @@ void CStayAwakeDlg::OnStartMinimized() WritePrivateProfileString(PREF_DEFAULTS, PREF_START_MINIMIZED, (IsDlgButtonChecked(IDC_START_MINIMIZED) == BST_CHECKED) ? L"Y" : L"N", m_IniFilePath); } + +void CStayAwakeDlg::OnSessionChange(UINT nSessionState, UINT nId) +{ + switch (nSessionState) + { + case WTS_SESSION_LOCK: + m_bSystemLocked = true; + break; + + case WTS_SESSION_UNLOCK: + m_bSystemLocked = false; + break; + } + + CDialogEx::OnSessionChange(nSessionState, nId); +} diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index 815444a..10a69c2 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -44,7 +44,7 @@ class CStayAwakeDlg : public CDialogEx private: wchar_t m_IniFilePath[MAX_PATH]{}; - bool m_bMinimized{}; + bool m_bMinimized{}, m_bSystemLocked{}; UINT_PTR m_TimerID{42}; UINT m_RosterKeyCodes[LEN_ROSTER_KEYCODES]{}; @@ -74,6 +74,7 @@ class CStayAwakeDlg : public CDialogEx afx_msg void OnExit() { DestroyWindow(); }; afx_msg void OnDestroy(); afx_msg void OnStartMinimized(); + afx_msg void OnSessionChange(UINT nSessionState, UINT nId); void InitConfigFilePath(); void InitIntervals(); From 01d4e089a484c90b56d286553ccb11bc4508608d Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:43:44 -0400 Subject: [PATCH 09/22] Select Inputs dialog UI: Add controls for FN keys & mouse move --- StayAwake/StayAwake.rc | 50 +++++++++++++++++++++++++------------- StayAwake/resource.h | 55 +++++++++++++++++++++++++++--------------- 2 files changed, 69 insertions(+), 36 deletions(-) diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index 7201241..e3dbf19 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -89,7 +89,7 @@ EXSTYLE WS_EX_APPWINDOW CAPTION "StayAwake" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN - PUSHBUTTON "Select multiple &Key Codes for random simulation",IDC_STAYAWAKE_KEYS_ROSTER_BTN,13,12,179,14 + PUSHBUTTON "Select multiple &options for random simulation",IDC_STAYAWAKE_KEYS_ROSTER_BTN,13,12,179,14 RTEXT "&Minimum Awake Seconds:",IDC_STATIC,1,35,100,8 EDITTEXT IDC_STAYAWAKE_INTERVAL_MIN,102,32,27,13,ES_NUMBER RTEXT "Ma&ximum Awake Seconds:",IDC_STATIC,1,55,100,8 @@ -106,27 +106,43 @@ BEGIN PUSHBUTTON "...",IDC_ABOUT_BUTTON,188,126,15,13,BS_BITMAP END -IDD_SELECT_KEYCODES_DIALOG DIALOGEX 0, 0, 180, 205 +IDD_SELECT_KEYCODES_DIALOG DIALOGEX 0, 0, 330, 205 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Select multiple Key Codes" +CAPTION "Select multiple simulation options" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN - CONTROL "&Scroll Lock cycling",IDC_KEY_SCROLL_LOCK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,15,90,10 + CONTROL "&Scroll Lock cycling",IDC_KEY_SCROLL_LOCK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,15,90,10 CONTROL "&Volume Down && Up",IDC_KEY_VOLUME_UP_DOWN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,30,90,10 - CONTROL "Unassigned Key #&1",IDC_KEY_UNASSIGNED_1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,45,90,10 - CONTROL "Unassigned Key #&2",IDC_KEY_UNASSIGNED_2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,60,90,10 - CONTROL "Unassigned Key #&3",IDC_KEY_UNASSIGNED_3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,75,90,10 - CONTROL "Unassigned Key #&4",IDC_KEY_UNASSIGNED_4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,90,90,10 - CONTROL "Unassigned Key #&5",IDC_KEY_UNASSIGNED_5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,105,90,10 - CONTROL "Unassigned Key #&6",IDC_KEY_UNASSIGNED_6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,120,90,10 - CONTROL "Unassigned Key #&7",IDC_KEY_UNASSIGNED_7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,135,90,10 - CONTROL "Unassigned Key #&8",IDC_KEY_UNASSIGNED_8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,150,90,10 - CONTROL "Unassigned Key #&9",IDC_KEY_UNASSIGNED_9,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,165,90,10 + CONTROL "Unassigned Key #&1",IDC_KEY_UNASSIGNED_1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,45,90,10 + CONTROL "Unassigned Key #&2",IDC_KEY_UNASSIGNED_2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,60,90,10 + CONTROL "Unassigned Key #&3",IDC_KEY_UNASSIGNED_3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,75,90,10 + CONTROL "Unassigned Key #&4",IDC_KEY_UNASSIGNED_4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,90,90,10 + CONTROL "Unassigned Key #&5",IDC_KEY_UNASSIGNED_5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,105,90,10 + CONTROL "Unassigned Key #&6",IDC_KEY_UNASSIGNED_6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,120,90,10 + CONTROL "Unassigned Key #&7",IDC_KEY_UNASSIGNED_7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,135,90,10 + CONTROL "Unassigned Key #&8",IDC_KEY_UNASSIGNED_8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,150,90,10 + CONTROL "Unassigned Key #&9",IDC_KEY_UNASSIGNED_9,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,165,90,10 CONTROL "Unassigned Key #1&0",IDC_KEY_UNASSIGNED_10,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,180,90,10 - PUSHBUTTON "Select &All",IDC_KEY_SELECT_ALL_BTN,115,15,50,14 - PUSHBUTTON "Select &None",IDC_KEY_SELECT_NONE_BTN,115,35,50,14 - DEFPUSHBUTTON "A&pply",IDOK,115,55,50,14 - PUSHBUTTON "&Cancel",IDCANCEL,115,75,50,14 + CONTROL "Extended FN Key: F13",IDC_KEY_F13,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,15,90,10 + CONTROL "Extended FN Key: F14",IDC_KEY_F14,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,30,90,10 + CONTROL "Extended FN Key: F15",IDC_KEY_F15,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,45,90,10 + CONTROL "Extended FN Key: F16",IDC_KEY_F16,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,60,90,10 + CONTROL "Extended FN Key: F17",IDC_KEY_F17,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,75,90,10 + CONTROL "Extended FN Key: F18",IDC_KEY_F18,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,90,90,10 + CONTROL "Extended FN Key: F19",IDC_KEY_F19,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,105,90,10 + CONTROL "Extended FN Key: F20",IDC_KEY_F20,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,120,90,10 + CONTROL "Extended FN Key: F21",IDC_KEY_F21,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,135,90,10 + CONTROL "Extended FN Key: F22",IDC_KEY_F22,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,150,90,10 + CONTROL "Extended FN Key: F23",IDC_KEY_F23,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,165,90,10 + CONTROL "Extended FN Key: F24",IDC_KEY_F24,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,180,90,10 + CONTROL "Invisible &Mouse Move",IDC_MOUSE_MOVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,15,90,10 + CONTROL "&Zero-pixel move",IDC_MOUSE_MOVE_ZERO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,245,25,70,10 + PUSHBUTTON "Select &None",IDC_KEY_SELECT_NONE_BTN,225,55,85,14 + PUSHBUTTON "Select All &Unassigned", IDC_KEY_SELECT_ALL_UNASSGND_BTN,225,80,85,14 + PUSHBUTTON "Select All Ext. FN Keys", IDC_KEY_SELECT_ALL_EXT_FN_BTN,225,105,85,14 + PUSHBUTTON "Select &All",IDC_KEY_SELECT_ALL_BTN,225,130,85,14 + DEFPUSHBUTTON "A&pply",IDOK,225,155,85,14 + PUSHBUTTON "&Cancel",IDCANCEL,225,180,85,14 END ///////////////////////////////////////////////////////////////////////////// diff --git a/StayAwake/resource.h b/StayAwake/resource.h index cd1de7e..5b80db5 100644 --- a/StayAwake/resource.h +++ b/StayAwake/resource.h @@ -25,26 +25,43 @@ #define IDC_START_MINIMIZED 1014 #define IDC_ABOUT_BUTTON 1015 -#define IDC_KEY_SCROLL_LOCK 1051 -#define IDC_KEY_VOLUME_UP_DOWN 1052 -#define IDC_KEY_UNASSIGNED_1 1053 -#define IDC_KEY_UNASSIGNED_2 1054 -#define IDC_KEY_UNASSIGNED_3 1055 -#define IDC_KEY_UNASSIGNED_4 1056 -#define IDC_KEY_UNASSIGNED_5 1057 -#define IDC_KEY_UNASSIGNED_6 1058 -#define IDC_KEY_UNASSIGNED_7 1059 -#define IDC_KEY_UNASSIGNED_8 1060 -#define IDC_KEY_UNASSIGNED_9 1061 -#define IDC_KEY_UNASSIGNED_10 1062 -#define IDC_KEY_SELECT_ALL_BTN 1071 -#define IDC_KEY_SELECT_NONE_BTN 1072 +#define IDC_KEY_SCROLL_LOCK 1101 +#define IDC_KEY_VOLUME_UP_DOWN 1102 +#define IDC_KEY_UNASSIGNED_1 1103 +#define IDC_KEY_UNASSIGNED_2 1104 +#define IDC_KEY_UNASSIGNED_3 1105 +#define IDC_KEY_UNASSIGNED_4 1106 +#define IDC_KEY_UNASSIGNED_5 1107 +#define IDC_KEY_UNASSIGNED_6 1108 +#define IDC_KEY_UNASSIGNED_7 1109 +#define IDC_KEY_UNASSIGNED_8 1110 +#define IDC_KEY_UNASSIGNED_9 1111 +#define IDC_KEY_UNASSIGNED_10 1112 +#define IDC_KEY_F13 1113 +#define IDC_KEY_F14 1114 +#define IDC_KEY_F15 1115 +#define IDC_KEY_F16 1116 +#define IDC_KEY_F17 1117 +#define IDC_KEY_F18 1118 +#define IDC_KEY_F19 1119 +#define IDC_KEY_F20 1120 +#define IDC_KEY_F21 1121 +#define IDC_KEY_F22 1122 +#define IDC_KEY_F23 1123 +#define IDC_KEY_F24 1124 +#define IDC_MOUSE_MOVE 1125 +#define IDC_MOUSE_MOVE_ZERO 1126 -#define IDC_ABOUT_NAME 1101 -#define IDC_ABOUT_VERSION 1102 -#define IDC_ABOUT_BUILD_TIME 1103 -#define IDC_ABOUT_ATTRIBUTION 1104 -#define IDC_ABOUT_PROD_URL 1105 +#define IDC_KEY_SELECT_NONE_BTN 1151 +#define IDC_KEY_SELECT_ALL_UNASSGND_BTN 1152 +#define IDC_KEY_SELECT_ALL_EXT_FN_BTN 1153 +#define IDC_KEY_SELECT_ALL_BTN 1154 + +#define IDC_ABOUT_NAME 1201 +#define IDC_ABOUT_VERSION 1202 +#define IDC_ABOUT_BUILD_TIME 1203 +#define IDC_ABOUT_ATTRIBUTION 1204 +#define IDC_ABOUT_PROD_URL 1205 #define IDM_EXIT 32771 #define IDM_RESTORE 32772 From 03f4a2d17bb9cdd37a8edd5284d57a26afeeeda8 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:51:39 -0400 Subject: [PATCH 10/22] Add controls & simulation logic and for FN keys & mouse move --- StayAwake/SelectKeyCodesDlg.cpp | 95 ++++++++++++++++---------- StayAwake/SelectKeyCodesDlg.h | 12 +++- StayAwake/StayAwakeCore.cpp | 111 ++++++++++++++++++++++++++++-- StayAwake/StayAwakeCore.h | 36 ++++++---- StayAwake/StayAwakeDlg.cpp | 117 ++++++-------------------------- StayAwake/StayAwakeDlg.h | 11 +-- 6 files changed, 220 insertions(+), 162 deletions(-) diff --git a/StayAwake/SelectKeyCodesDlg.cpp b/StayAwake/SelectKeyCodesDlg.cpp index 5958d04..11992b0 100644 --- a/StayAwake/SelectKeyCodesDlg.cpp +++ b/StayAwake/SelectKeyCodesDlg.cpp @@ -1,22 +1,19 @@ -// CSelectKeyCodesDlg.cpp : implementation file -// - #include "pch.h" #include "StayAwake.h" #include "afxdialogex.h" #include "SelectKeyCodesDlg.h" -static_assert(IDC_KEY_UNASSIGNED_10 - IDC_KEY_SCROLL_LOCK + 1 == LEN_ROSTER_KEYCODES, "Roster checkbox IDs must be consecutive"); +static_assert(IDC_MOUSE_MOVE - IDC_KEY_SCROLL_LOCK + 1 == LEN_ROSTER_KEYCODES, "Roster checkbox IDs must be consecutive"); // CSelectKeyCodesDlg dialog IMPLEMENT_DYNAMIC(CSelectKeyCodesDlg, CDialogEx) -CSelectKeyCodesDlg::CSelectKeyCodesDlg(CStayAwakeDlg* pCaller) - : CDialogEx(IDD_SELECT_KEYCODES_DIALOG, pCaller) - , m_pCaller(pCaller) +CSelectKeyCodesDlg::CSelectKeyCodesDlg(CWnd* pParent, StayAwakeCore& pAwakeCore) + : CDialogEx(IDD_SELECT_KEYCODES_DIALOG, pParent) + , m_AwakeCore(pAwakeCore) { - m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } CSelectKeyCodesDlg::~CSelectKeyCodesDlg() @@ -25,63 +22,89 @@ CSelectKeyCodesDlg::~CSelectKeyCodesDlg() void CSelectKeyCodesDlg::DoDataExchange(CDataExchange* pDX) { - CDialogEx::DoDataExchange(pDX); + CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CSelectKeyCodesDlg, CDialogEx) - ON_BN_CLICKED(IDOK, &CSelectKeyCodesDlg::OnOK) - ON_BN_CLICKED(IDC_KEY_SELECT_ALL_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectAllBtn) - ON_BN_CLICKED(IDC_KEY_SELECT_NONE_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn) + ON_BN_CLICKED(IDOK, &CSelectKeyCodesDlg::OnOK) + ON_BN_CLICKED(IDC_KEY_SELECT_ALL_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectAllBtn) + ON_BN_CLICKED(IDC_KEY_SELECT_NONE_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn) + ON_BN_CLICKED(IDC_KEY_SELECT_ALL_UNASSGND_BTN, &CSelectKeyCodesDlg::OnBnClickedSelectAllUnassignedKeys) + ON_BN_CLICKED(IDC_KEY_SELECT_ALL_EXT_FN_BTN, &CSelectKeyCodesDlg::OnBnClickedSelectAllExtFnKeys) + ON_BN_CLICKED(IDC_MOUSE_MOVE, &CSelectKeyCodesDlg::OnClickedMouseMove) END_MESSAGE_MAP() BOOL CSelectKeyCodesDlg::OnInitDialog() { - CDialogEx::OnInitDialog(); + CDialogEx::OnInitDialog(); + + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon - SetIcon(m_hIcon, TRUE); // Set big icon - SetIcon(m_hIcon, FALSE); // Set small icon + CheckAllBoxes(m_AwakeCore.GetSelectedKeyCodes()); - CheckAllBoxes(m_pCaller->GetSelectedKeyCodes()); + CheckDlgButton(IDC_MOUSE_MOVE_ZERO, + (m_AwakeCore.GetPreference(PREF_MOUSE_MOVE_ZERO, L"Y") == L"Y") ? BST_CHECKED : BST_UNCHECKED); + OnClickedMouseMove(); - return TRUE; // return TRUE unless you set the focus to a control + return TRUE; // return TRUE unless you set the focus to a control } void CSelectKeyCodesDlg::OnOK() { - wstring sSelectedKeyCodes{}; + wstring sSelectedKeyCodes{}; + + for (int i{}; i < LEN_ROSTER_KEYCODES; i++) + sSelectedKeyCodes += (IsDlgButtonChecked(IDC_KEY_SCROLL_LOCK + i)) ? L"1" : L"0"; - for (int i{}; i < LEN_ROSTER_KEYCODES; i++) - sSelectedKeyCodes += (IsDlgButtonChecked(IDC_KEY_SCROLL_LOCK + i)) ? L"1" : L"0"; + if (sSelectedKeyCodes == wstring(LEN_ROSTER_KEYCODES, L'0')) + { + MessageBox(L"Please select at least one Key Code", SELECT_KEYCODES_DIALOG_TITLE, MB_ICONEXCLAMATION); + return; + } - if (sSelectedKeyCodes == wstring(LEN_ROSTER_KEYCODES, L'0')) - { - MessageBox(L"Please select at least one Key Code", L"Select multiple Key Codes", MB_ICONEXCLAMATION); - return; - } + if (!m_AwakeCore.SaveSelectedKeyCodes(sSelectedKeyCodes)) + { + MessageBox(L"Unable to save to the StayAwake.ini file.", SELECT_KEYCODES_DIALOG_TITLE, MB_ICONEXCLAMATION); + return; + } - if (!m_pCaller->SaveSelectedKeyCodes(sSelectedKeyCodes)) - { - MessageBox(L"Unable to save to the StayAwake.ini file.", L"Select multiple Key Codes", MB_ICONEXCLAMATION); - return; - } + m_AwakeCore.SetPreference(PREF_MOUSE_MOVE_ZERO, IsDlgButtonChecked(IDC_MOUSE_MOVE_ZERO) ? L"Y" : L"N"); - CDialogEx::OnOK(); + CDialogEx::OnOK(); } -void CSelectKeyCodesDlg::CheckAllBoxes(const wstring& sSelectedKeyCodes) +void CSelectKeyCodesDlg::CheckAllBoxes(const wstring& sSelectedKeyCodes, int start, int endNext) { - for (int i{}; i < LEN_ROSTER_KEYCODES; i++) - CheckDlgButton(IDC_KEY_SCROLL_LOCK + i, sSelectedKeyCodes.at(i) == L'1'); + for (int i{ start }; i < endNext; i++) + CheckDlgButton(i, sSelectedKeyCodes.at(i % IDC_KEY_SCROLL_LOCK) == L'1'); } void CSelectKeyCodesDlg::OnClickedKeySelectAllBtn() { - CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1')); + CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1')); + OnClickedMouseMove(); } void CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn() { - CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'0')); + CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'0')); + OnClickedMouseMove(); +} + +void CSelectKeyCodesDlg::OnBnClickedSelectAllUnassignedKeys() +{ + CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1'), IDC_KEY_UNASSIGNED_1, IDC_KEY_F13); +} + +void CSelectKeyCodesDlg::OnBnClickedSelectAllExtFnKeys() +{ + CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1'), IDC_KEY_F13, IDC_MOUSE_MOVE); +} + +void CSelectKeyCodesDlg::OnClickedMouseMove() +{ + GetDlgItem(IDC_MOUSE_MOVE_ZERO)->EnableWindow(IsDlgButtonChecked(IDC_MOUSE_MOVE)); } diff --git a/StayAwake/SelectKeyCodesDlg.h b/StayAwake/SelectKeyCodesDlg.h index 2a23998..1909ed3 100644 --- a/StayAwake/SelectKeyCodesDlg.h +++ b/StayAwake/SelectKeyCodesDlg.h @@ -3,12 +3,14 @@ #include "Utils.h" #include "StayAwakeDlg.h" +constexpr auto SELECT_KEYCODES_DIALOG_TITLE = L"Select multiple simulation options"; + class CSelectKeyCodesDlg : public CDialogEx { DECLARE_DYNAMIC(CSelectKeyCodesDlg) public: - explicit CSelectKeyCodesDlg(CStayAwakeDlg* pCaller); + explicit CSelectKeyCodesDlg(CWnd* pParent, StayAwakeCore& pAwakeCore); virtual ~CSelectKeyCodesDlg(); // Dialog Data @@ -17,7 +19,7 @@ class CSelectKeyCodesDlg : public CDialogEx #endif protected: - CStayAwakeDlg* m_pCaller; + StayAwakeCore m_AwakeCore{}; HICON m_hIcon; virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support @@ -30,5 +32,9 @@ class CSelectKeyCodesDlg : public CDialogEx DECLARE_MESSAGE_MAP() private: - void CheckAllBoxes(const wstring& sSelectedKeyCodes); + void CheckAllBoxes(const wstring& sSelectedKeyCodes, int start = IDC_KEY_SCROLL_LOCK, int endNext = IDC_KEY_SCROLL_LOCK + LEN_ROSTER_KEYCODES); +public: + afx_msg void OnBnClickedSelectAllUnassignedKeys(); + afx_msg void OnBnClickedSelectAllExtFnKeys(); + afx_msg void OnClickedMouseMove(); }; diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index 26ccd5e..1964a84 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -1,6 +1,95 @@ #include "pch.h" + +#include "resource.h" #include "StayAwakeCore.h" +void StayAwakeCore::SetConfigFilePath(LPTSTR iniFilePath) +{ + wcscpy_s(sIniFilePath, MAX_PATH, iniFilePath); +} + +wstring StayAwakeCore::GetSelectedKeyCodes() +{ + wstring sKeyCodes{ GetPreference(PREF_SELECTED_KEYCODES, L"N/A") }; + + if (sKeyCodes == L"N/A") { + // Migrate single legacy key code to 25 bits + UINT nLegacyKeyCode = GetPrivateProfileInt(PREF_DEFAULTS, PREF_LEGACY_KEYCODE, 99, sIniFilePath); + + if (nLegacyKeyCode == 99) + sKeyCodes = DEF_SELECTED_KEYCODES; + else + { + sKeyCodes = wstring(LEN_ROSTER_KEYCODES, L'0'); + sKeyCodes.replace(nLegacyKeyCode % LEN_ROSTER_KEYCODES, 1, L"1"); + } + + WritePrivateProfileString(PREF_DEFAULTS, PREF_LEGACY_KEYCODE, nullptr, sIniFilePath); + SaveSelectedKeyCodes(sKeyCodes); + } + else if (sKeyCodes.length() == (IDC_KEY_F13 - IDC_KEY_SCROLL_LOCK)) + { + // Migrate 12-bit legacy key codes to 25 bits + sKeyCodes += wstring(LEN_ROSTER_KEYCODES - sKeyCodes.length(), L'1'); + SaveSelectedKeyCodes(sKeyCodes); + } + else if (!CheckSelectedKeyCodes(sKeyCodes)) + { + sKeyCodes = DEF_SELECTED_KEYCODES; + SaveSelectedKeyCodes(sKeyCodes); + } + + return sKeyCodes; +} + +bool StayAwakeCore::CheckSelectedKeyCodes(wstring sKeyCodes) +{ + return (sKeyCodes.length() == LEN_ROSTER_KEYCODES && + sKeyCodes != wstring(LEN_ROSTER_KEYCODES, L'0') && + sKeyCodes.find_first_not_of(L"01") == std::string::npos); +} + +bool StayAwakeCore::SaveSelectedKeyCodes(wstring sKeyCodes) +{ + return CheckSelectedKeyCodes(sKeyCodes) && SetPreference(PREF_SELECTED_KEYCODES, sKeyCodes); +} + +void StayAwakeCore::InitIntervals(UINT& minSeconds, UINT& maxSeconds) const +{ + int nIntervalLegacy{}, nIntervalMin{}, nIntervalMax{}; + + nIntervalLegacy = GetPrivateProfileInt(PREF_DEFAULTS, PREF_INTERVAL_LEGACY, DEF_PERIOD, sIniFilePath); + if (nIntervalLegacy < MIN_PERIOD || nIntervalLegacy > MAX_PERIOD) + nIntervalLegacy = DEF_PERIOD; + + nIntervalMin = GetPrivateProfileInt(PREF_DEFAULTS, PREF_INTERVAL_MINIMUM, nIntervalLegacy, sIniFilePath); + if (nIntervalMin < MIN_PERIOD || nIntervalMin > MAX_PERIOD) + nIntervalMin = DEF_PERIOD; + + nIntervalMax = GetPrivateProfileInt(PREF_DEFAULTS, PREF_INTERVAL_MAXIMUM, nIntervalLegacy, sIniFilePath); + if (nIntervalMax < MIN_PERIOD || nIntervalMax > MAX_PERIOD) + nIntervalMax = DEF_PERIOD; + + minSeconds = (nIntervalMin <= nIntervalMax) ? nIntervalMin : nIntervalMax; + maxSeconds = (nIntervalMin >= nIntervalMax) ? nIntervalMin : nIntervalMax; +} + +wstring StayAwakeCore::GetPreference(wstring key, wstring defaultVal) const +{ + const int bufSize{ MAX_PATH }; + wstring ftBuf(bufSize, '\0'); + + GetPrivateProfileString(PREF_DEFAULTS, key.c_str(), defaultVal.c_str(), ftBuf.data(), bufSize, sIniFilePath); + + return wstring{ ftBuf.c_str() }; +} + +bool StayAwakeCore::SetPreference(wstring key, wstring setVal) const +{ + return WritePrivateProfileString(PREF_DEFAULTS, key.c_str(), setVal.c_str(), sIniFilePath); +} + + void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) { switch (inputCode) @@ -36,6 +125,7 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) PressOneKey(VK_UNASSIGNED_10, false); break; + case 12: case 13: case 14: case 15: @@ -47,14 +137,21 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) case 21: case 22: case 23: - case 24: - inputName = L"F" + to_wstring(inputCode); - PressOneKey(static_cast(VK_F13 + inputCode - 13), false); + inputName = L"F" + to_wstring(inputCode + 1); + PressOneKey(static_cast(VK_F13 + inputCode - 12), false); break; - case 25: - inputName = L"Invisible Mouse Move"; - MouseMove(); + case 24: + if (GetPreference(PREF_MOUSE_MOVE_ZERO, L"Y") == L"Y") + { + inputName = L"Zero Mouse Move"; + MouseMoveZero(); + } + else + { + inputName = L"Tiny Mouse Move"; + MouseMoveTiny(); + } break; @@ -162,7 +259,7 @@ void StayAwakeCore::PressThreeKeys(BYTE vkFirst, BYTE vkSecond, BYTE vkThird, bo SendInput(6, input, sizeof(INPUT)); } -void StayAwakeCore::MouseMove() +void StayAwakeCore::MouseMoveTiny() { INPUT input[2] = {}; diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index ef310f9..862db01 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -6,8 +6,13 @@ #include #include +#pragma comment(lib, "Ole32.lib") #pragma comment(lib, "Wtsapi32.lib") +using std::string; +using std::wstring; +using std::to_wstring; + constexpr auto VK_UNASSIGNED_01 = 0x97; constexpr auto VK_UNASSIGNED_10 = 0xE8; @@ -22,18 +27,15 @@ constexpr auto PREF_MULTI_INSTANCE = L"MultipleInstancesAllowed"; constexpr auto PREF_INTERVAL_LEGACY = L"TimerIntervalInSeconds"; constexpr auto PREF_INTERVAL_MINIMUM = L"MinimumIntervalInSeconds"; constexpr auto PREF_INTERVAL_MAXIMUM = L"MaximumIntervalInSeconds"; +constexpr auto PREF_MOUSE_MOVE_ZERO = L"MouseMoveZeroPixels"; constexpr auto MIN_PERIOD{ 10 }; constexpr auto MAX_PERIOD{ 9990 }; constexpr auto DEF_PERIOD{ 240 }; -constexpr auto LEN_ROSTER_KEYCODES{ 12 }; -constexpr auto DEF_SELECTED_KEYCODES = L"001111111111"; - +constexpr auto LEN_ROSTER_KEYCODES{ 25 }; +const auto DEF_SELECTED_KEYCODES = L"00" + wstring(LEN_ROSTER_KEYCODES - 2, L'1'); -using std::string; -using std::wstring; -using std::to_wstring; class StayAwakeCore @@ -42,16 +44,26 @@ class StayAwakeCore StayAwakeCore() {}; ~StayAwakeCore() {}; + void SetConfigFilePath(LPTSTR iniFilePath); + + wstring GetSelectedKeyCodes(); + bool CheckSelectedKeyCodes(wstring sKeyCodes); + bool SaveSelectedKeyCodes(wstring sKeyCodes); + void InitIntervals(UINT& minSeconds, UINT& maxSeconds) const; + wstring GetPreference(wstring key, wstring defaultVal) const; + bool SetPreference(wstring key, wstring setVal) const; + void SimulateInput(int inputCode, wstring& inputName); - static ULONGLONG GetIdleTimeMilliseconds(); + ULONGLONG GetIdleTimeMilliseconds(); private: - static void PressOneKey(BYTE vk, bool extended = false); - static void PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended = false); - static void PressThreeKeys(BYTE vkFirst, BYTE vkSecond, BYTE vkThird, bool extended = false); + wchar_t sIniFilePath[MAX_PATH]{}; - static void MouseMove(); - static void MouseMoveZero(); + void PressOneKey(BYTE vk, bool extended = false); + void PressTwoKeys(BYTE vkFirst, BYTE vkSecond, bool extended = false); + void PressThreeKeys(BYTE vkFirst, BYTE vkSecond, BYTE vkThird, bool extended = false); + void MouseMoveTiny(); + void MouseMoveZero(); bool GetMuteState(); }; diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 4347b54..4e48cc1 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -26,51 +26,6 @@ void CStayAwakeDlg::DoDataExchange(CDataExchange* pDX) CDialogEx::DoDataExchange(pDX); } -wstring CStayAwakeDlg::GetSelectedKeyCodes() -{ - const int bufSize{ LEN_ROSTER_KEYCODES + 1 }; - wchar_t sBuf[bufSize]{}; - - GetPrivateProfileString(PREF_DEFAULTS, PREF_SELECTED_KEYCODES, L"N/A", sBuf, bufSize, m_IniFilePath); - - wstring sKeyCodes{ sBuf }; - - if (sKeyCodes == L"N/A") { - UINT nLegacyKeyCode = GetPrivateProfileInt(PREF_DEFAULTS, PREF_LEGACY_KEYCODE, 99, m_IniFilePath); - - if (nLegacyKeyCode == 99) - sKeyCodes = DEF_SELECTED_KEYCODES; - else - { - sKeyCodes = wstring(LEN_ROSTER_KEYCODES, L'0'); - sKeyCodes.replace(nLegacyKeyCode % LEN_ROSTER_KEYCODES, 1, L"1"); - } - - WritePrivateProfileString(PREF_DEFAULTS, PREF_LEGACY_KEYCODE, nullptr, m_IniFilePath); - SaveSelectedKeyCodes(sKeyCodes); - } - else if (!CheckSelectedKeyCodes(sKeyCodes)) - { - sKeyCodes = DEF_SELECTED_KEYCODES; - SaveSelectedKeyCodes(sKeyCodes); - } - - return sKeyCodes; -} - -bool CStayAwakeDlg::CheckSelectedKeyCodes(wstring sKeyCodes) -{ - return (sKeyCodes.length() == LEN_ROSTER_KEYCODES && - sKeyCodes != wstring(LEN_ROSTER_KEYCODES, L'0') && - sKeyCodes.find_first_not_of(L"01") == std::string::npos); -} - -bool CStayAwakeDlg::SaveSelectedKeyCodes(wstring sKeyCodes) -{ - return CheckSelectedKeyCodes(sKeyCodes) && - WritePrivateProfileString(PREF_DEFAULTS, PREF_SELECTED_KEYCODES, sKeyCodes.c_str(), m_IniFilePath); -} - BEGIN_MESSAGE_MAP(CStayAwakeDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_MESSAGE(WM_POST_OPEN, &CStayAwakeDlg::OnPostOpen) @@ -127,7 +82,8 @@ BOOL CStayAwakeDlg::OnInitDialog() SetIcon(m_hIcon, FALSE); // Set small icon InitConfigFilePath(); - InitIntervals(); + + m_AwakeCore.InitIntervals(m_IntervalMinSeconds, m_IntervalMaxSeconds); SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MIN, m_IntervalMinSeconds, FALSE); SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, m_IntervalMaxSeconds, FALSE); @@ -163,7 +119,7 @@ afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) { WTSRegisterSessionNotification(m_hWnd, NOTIFY_FOR_THIS_SESSION); - if (GetPreference(PREF_MULTI_INSTANCE, L"N") != L"Y" && Utils::getProcessRunCount(L"StayAwake.exe") > 1) + if (m_AwakeCore.GetPreference(PREF_MULTI_INSTANCE, L"N") != L"Y" && Utils::getProcessRunCount(L"StayAwake.exe") > 1) { ::PostMessage(HWND_BROADCAST, theApp.WM_SHOWFIRSTINSTANCE, 0, 0); @@ -176,7 +132,7 @@ afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) else InitAwakes(); - BOOL bMinimized = (GetPreference(PREF_START_MINIMIZED, L"N") == L"Y"); + BOOL bMinimized = (m_AwakeCore.GetPreference(PREF_START_MINIMIZED, L"N") == L"Y"); CheckDlgButton(IDC_START_MINIMIZED, bMinimized ? BST_CHECKED : BST_UNCHECKED); if (bMinimized) MinimizeToTray(); @@ -247,7 +203,7 @@ void CStayAwakeDlg::OnKillfocusIntervalMax() void CStayAwakeDlg::OnSetupKeyCodesRosterClicked() { - CSelectKeyCodesDlg dlgSelectKeyCodes(this); + CSelectKeyCodesDlg dlgSelectKeyCodes(this, m_AwakeCore); if (dlgSelectKeyCodes.DoModal() == IDOK) { @@ -265,41 +221,23 @@ void CStayAwakeDlg::OnTimer(UINT_PTR nIDEvent) void CStayAwakeDlg::InitConfigFilePath() { - DWORD size = GetModuleFileNameW(nullptr, m_IniFilePath, MAX_PATH); - - if (size > 0 && size < MAX_PATH && - SUCCEEDED(PathCchRemoveFileSpec(m_IniFilePath, MAX_PATH)) && - SUCCEEDED(PathCchAppend(m_IniFilePath, MAX_PATH, PREF_INI_FILE))) - return; - - wcscpy_s(m_IniFilePath, MAX_PATH, L".\\"); // last resort: working directory - wcscat_s(m_IniFilePath, MAX_PATH, PREF_INI_FILE); -} - - -void CStayAwakeDlg::InitIntervals() -{ - int nIntervalLegacy{}, nIntervalMin{}, nIntervalMax{}; + wchar_t sIniFilePath[MAX_PATH]{}; + DWORD size = GetModuleFileNameW(nullptr, sIniFilePath, MAX_PATH); - nIntervalLegacy = GetPrivateProfileInt(PREF_DEFAULTS, PREF_INTERVAL_LEGACY, DEF_PERIOD, m_IniFilePath); - if (nIntervalLegacy < MIN_PERIOD || nIntervalLegacy > MAX_PERIOD) - nIntervalLegacy = DEF_PERIOD; - - nIntervalMin = GetPrivateProfileInt(PREF_DEFAULTS, PREF_INTERVAL_MINIMUM, nIntervalLegacy, m_IniFilePath); - if (nIntervalMin < MIN_PERIOD || nIntervalMin > MAX_PERIOD) - nIntervalMin = DEF_PERIOD; - - nIntervalMax = GetPrivateProfileInt(PREF_DEFAULTS, PREF_INTERVAL_MAXIMUM, nIntervalLegacy, m_IniFilePath); - if (nIntervalMax < MIN_PERIOD || nIntervalMax > MAX_PERIOD) - nIntervalMax = DEF_PERIOD; + if (size <= 0 || size > MAX_PATH || + FAILED(PathCchRemoveFileSpec(sIniFilePath, MAX_PATH)) || + FAILED(PathCchAppend(sIniFilePath, MAX_PATH, PREF_INI_FILE))) + { + wcscpy_s(sIniFilePath, MAX_PATH, L".\\"); // last resort: working directory + wcscat_s(sIniFilePath, MAX_PATH, PREF_INI_FILE); + } - m_IntervalMinSeconds = (nIntervalMin <= nIntervalMax) ? nIntervalMin : nIntervalMax; - m_IntervalMaxSeconds = (nIntervalMin >= nIntervalMax) ? nIntervalMin : nIntervalMax; + m_AwakeCore.SetConfigFilePath(sIniFilePath); } void CStayAwakeDlg::InitRosterKeyCodes() { - wstring sSelectedKeyCodes{ GetSelectedKeyCodes() }; + wstring sSelectedKeyCodes{ m_AwakeCore.GetSelectedKeyCodes() }; m_RosterLength = 0; @@ -316,7 +254,7 @@ void CStayAwakeDlg::InitAwakes() SimulateAwakeKeyPress(); SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, BTN_TEXT_PAUSE); - WritePrivateProfileString(PREF_DEFAULTS, PREF_AWAKE_PAUSED, L"N", m_IniFilePath); + m_AwakeCore.SetPreference(PREF_AWAKE_PAUSED, L"N"); } void CStayAwakeDlg::InitTrayIcon() @@ -431,8 +369,8 @@ void CStayAwakeDlg::OnSetInterval() SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, m_IntervalMaxSeconds, FALSE); } - WritePrivateProfileString(PREF_DEFAULTS, PREF_INTERVAL_MINIMUM, to_wstring(m_IntervalMinSeconds).c_str(), m_IniFilePath); - WritePrivateProfileString(PREF_DEFAULTS, PREF_INTERVAL_MAXIMUM, to_wstring(m_IntervalMaxSeconds).c_str(), m_IniFilePath); + m_AwakeCore.SetPreference(PREF_INTERVAL_MINIMUM, to_wstring(m_IntervalMinSeconds)); + m_AwakeCore.SetPreference(PREF_INTERVAL_MAXIMUM, to_wstring(m_IntervalMaxSeconds)); InitAwakes(); } @@ -455,24 +393,14 @@ void CStayAwakeDlg::OnPauseResume() KillTimer(m_TimerID); SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, BTN_TEXT_RESUME); - WritePrivateProfileString(PREF_DEFAULTS, PREF_AWAKE_PAUSED, L"Y", m_IniFilePath); + m_AwakeCore.SetPreference(PREF_AWAKE_PAUSED, L"Y"); ShowPausedInfo(false); } } -wstring CStayAwakeDlg::GetPreference(wstring key, wstring defaultVal) -{ - const int bufSize{ MAX_PATH }; - wstring ftBuf(bufSize, '\0'); - - GetPrivateProfileString(PREF_DEFAULTS, key.c_str(), defaultVal.c_str(), ftBuf.data(), bufSize, m_IniFilePath); - - return wstring{ ftBuf.c_str() }; -} - bool CStayAwakeDlg::IsTimerPaused() { - return (GetPreference(PREF_AWAKE_PAUSED, L"N") == L"Y"); + return (m_AwakeCore.GetPreference(PREF_AWAKE_PAUSED, L"N") == L"Y"); } void CStayAwakeDlg::ShowPausedInfo(bool both) @@ -485,8 +413,7 @@ void CStayAwakeDlg::ShowPausedInfo(bool both) void CStayAwakeDlg::OnStartMinimized() { - WritePrivateProfileString(PREF_DEFAULTS, PREF_START_MINIMIZED, - (IsDlgButtonChecked(IDC_START_MINIMIZED) == BST_CHECKED) ? L"Y" : L"N", m_IniFilePath); + m_AwakeCore.SetPreference(PREF_START_MINIMIZED, (IsDlgButtonChecked(IDC_START_MINIMIZED) == BST_CHECKED) ? L"Y" : L"N"); } void CStayAwakeDlg::OnSessionChange(UINT nSessionState, UINT nId) diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index 10a69c2..10a18b2 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -3,6 +3,7 @@ #include "StayAwakeCore.h" #include "Utils.h" + #define WM_POST_OPEN (WM_APP + 1) #define WM_TRAY_NOTIFY (WM_APP + 2) #define WM_RESTORE_DIALOG theApp.WM_SHOWFIRSTINSTANCE @@ -30,11 +31,6 @@ class CStayAwakeDlg : public CDialogEx virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support -public: - wstring GetSelectedKeyCodes(); - bool CheckSelectedKeyCodes(wstring sKeyCodes); - bool SaveSelectedKeyCodes(wstring sKeyCodes); - protected: HICON m_hIcon; @@ -43,7 +39,6 @@ class CStayAwakeDlg : public CDialogEx DECLARE_MESSAGE_MAP() private: - wchar_t m_IniFilePath[MAX_PATH]{}; bool m_bMinimized{}, m_bSystemLocked{}; UINT_PTR m_TimerID{42}; @@ -53,7 +48,7 @@ class CStayAwakeDlg : public CDialogEx UINT m_IntervalMinSeconds{}; UINT m_IntervalMaxSeconds{}; - StayAwakeCore m_AwakeCore; + StayAwakeCore m_AwakeCore{}; NOTIFYICONDATA m_TrayData{}; CMenu m_menu; @@ -77,7 +72,6 @@ class CStayAwakeDlg : public CDialogEx afx_msg void OnSessionChange(UINT nSessionState, UINT nId); void InitConfigFilePath(); - void InitIntervals(); void InitRosterKeyCodes(); void InitAwakes(); void InitTrayIcon(); @@ -87,6 +81,5 @@ class CStayAwakeDlg : public CDialogEx void SimulateAwakeKeyPress(); void ShowPausedInfo(bool both); - wstring GetPreference(wstring key, wstring defaultVal); bool IsTimerPaused(); }; From 846c1484af0e5c1f6fa1cc07bcb405fbd0e03ddb Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:35:22 -0400 Subject: [PATCH 11/22] Split One-pixel Jiggle & Zero-pixel Mouse Move as distinct options --- StayAwake/SelectKeyCodesDlg.cpp | 23 +++++------------------ StayAwake/SelectKeyCodesDlg.h | 6 ++---- StayAwake/StayAwake.rc | 30 +++++++++++++++--------------- StayAwake/StayAwakeCore.cpp | 16 ++++++---------- StayAwake/StayAwakeCore.h | 3 +-- 5 files changed, 29 insertions(+), 49 deletions(-) diff --git a/StayAwake/SelectKeyCodesDlg.cpp b/StayAwake/SelectKeyCodesDlg.cpp index 11992b0..ac1efce 100644 --- a/StayAwake/SelectKeyCodesDlg.cpp +++ b/StayAwake/SelectKeyCodesDlg.cpp @@ -3,7 +3,7 @@ #include "afxdialogex.h" #include "SelectKeyCodesDlg.h" -static_assert(IDC_MOUSE_MOVE - IDC_KEY_SCROLL_LOCK + 1 == LEN_ROSTER_KEYCODES, "Roster checkbox IDs must be consecutive"); +static_assert(IDC_MOUSE_MOVE_ZERO - IDC_KEY_SCROLL_LOCK + 1 == LEN_ROSTER_KEYCODES, "Roster checkbox IDs must be consecutive"); // CSelectKeyCodesDlg dialog @@ -30,9 +30,8 @@ BEGIN_MESSAGE_MAP(CSelectKeyCodesDlg, CDialogEx) ON_BN_CLICKED(IDOK, &CSelectKeyCodesDlg::OnOK) ON_BN_CLICKED(IDC_KEY_SELECT_ALL_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectAllBtn) ON_BN_CLICKED(IDC_KEY_SELECT_NONE_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn) - ON_BN_CLICKED(IDC_KEY_SELECT_ALL_UNASSGND_BTN, &CSelectKeyCodesDlg::OnBnClickedSelectAllUnassignedKeys) - ON_BN_CLICKED(IDC_KEY_SELECT_ALL_EXT_FN_BTN, &CSelectKeyCodesDlg::OnBnClickedSelectAllExtFnKeys) - ON_BN_CLICKED(IDC_MOUSE_MOVE, &CSelectKeyCodesDlg::OnClickedMouseMove) + ON_BN_CLICKED(IDC_KEY_SELECT_ALL_UNASSGND_BTN, &CSelectKeyCodesDlg::OnClickedSelectAllUnassignedKeys) + ON_BN_CLICKED(IDC_KEY_SELECT_ALL_EXT_FN_BTN, &CSelectKeyCodesDlg::OnClickedSelectAllExtFnKeys) END_MESSAGE_MAP() @@ -45,10 +44,6 @@ BOOL CSelectKeyCodesDlg::OnInitDialog() CheckAllBoxes(m_AwakeCore.GetSelectedKeyCodes()); - CheckDlgButton(IDC_MOUSE_MOVE_ZERO, - (m_AwakeCore.GetPreference(PREF_MOUSE_MOVE_ZERO, L"Y") == L"Y") ? BST_CHECKED : BST_UNCHECKED); - OnClickedMouseMove(); - return TRUE; // return TRUE unless you set the focus to a control } @@ -71,8 +66,6 @@ void CSelectKeyCodesDlg::OnOK() return; } - m_AwakeCore.SetPreference(PREF_MOUSE_MOVE_ZERO, IsDlgButtonChecked(IDC_MOUSE_MOVE_ZERO) ? L"Y" : L"N"); - CDialogEx::OnOK(); } @@ -85,26 +78,20 @@ void CSelectKeyCodesDlg::CheckAllBoxes(const wstring& sSelectedKeyCodes, int sta void CSelectKeyCodesDlg::OnClickedKeySelectAllBtn() { CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1')); - OnClickedMouseMove(); } void CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn() { CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'0')); - OnClickedMouseMove(); } -void CSelectKeyCodesDlg::OnBnClickedSelectAllUnassignedKeys() +void CSelectKeyCodesDlg::OnClickedSelectAllUnassignedKeys() { CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1'), IDC_KEY_UNASSIGNED_1, IDC_KEY_F13); } -void CSelectKeyCodesDlg::OnBnClickedSelectAllExtFnKeys() +void CSelectKeyCodesDlg::OnClickedSelectAllExtFnKeys() { CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1'), IDC_KEY_F13, IDC_MOUSE_MOVE); } -void CSelectKeyCodesDlg::OnClickedMouseMove() -{ - GetDlgItem(IDC_MOUSE_MOVE_ZERO)->EnableWindow(IsDlgButtonChecked(IDC_MOUSE_MOVE)); -} diff --git a/StayAwake/SelectKeyCodesDlg.h b/StayAwake/SelectKeyCodesDlg.h index 1909ed3..4423745 100644 --- a/StayAwake/SelectKeyCodesDlg.h +++ b/StayAwake/SelectKeyCodesDlg.h @@ -27,14 +27,12 @@ class CSelectKeyCodesDlg : public CDialogEx virtual BOOL OnInitDialog(); afx_msg void OnClickedKeySelectAllBtn(); afx_msg void OnClickedKeySelectNoneBtn(); + afx_msg void OnClickedSelectAllUnassignedKeys(); + afx_msg void OnClickedSelectAllExtFnKeys(); afx_msg void OnOK(); DECLARE_MESSAGE_MAP() private: void CheckAllBoxes(const wstring& sSelectedKeyCodes, int start = IDC_KEY_SCROLL_LOCK, int endNext = IDC_KEY_SCROLL_LOCK + LEN_ROSTER_KEYCODES); -public: - afx_msg void OnBnClickedSelectAllUnassignedKeys(); - afx_msg void OnBnClickedSelectAllExtFnKeys(); - afx_msg void OnClickedMouseMove(); }; diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index e3dbf19..f5b7991 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -123,23 +123,23 @@ BEGIN CONTROL "Unassigned Key #&8",IDC_KEY_UNASSIGNED_8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,150,90,10 CONTROL "Unassigned Key #&9",IDC_KEY_UNASSIGNED_9,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,165,90,10 CONTROL "Unassigned Key #1&0",IDC_KEY_UNASSIGNED_10,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,180,90,10 - CONTROL "Extended FN Key: F13",IDC_KEY_F13,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,15,90,10 - CONTROL "Extended FN Key: F14",IDC_KEY_F14,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,30,90,10 - CONTROL "Extended FN Key: F15",IDC_KEY_F15,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,45,90,10 - CONTROL "Extended FN Key: F16",IDC_KEY_F16,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,60,90,10 - CONTROL "Extended FN Key: F17",IDC_KEY_F17,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,75,90,10 - CONTROL "Extended FN Key: F18",IDC_KEY_F18,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,90,90,10 - CONTROL "Extended FN Key: F19",IDC_KEY_F19,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,105,90,10 - CONTROL "Extended FN Key: F20",IDC_KEY_F20,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,120,90,10 - CONTROL "Extended FN Key: F21",IDC_KEY_F21,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,135,90,10 - CONTROL "Extended FN Key: F22",IDC_KEY_F22,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,150,90,10 - CONTROL "Extended FN Key: F23",IDC_KEY_F23,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,165,90,10 - CONTROL "Extended FN Key: F24",IDC_KEY_F24,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,180,90,10 - CONTROL "Invisible &Mouse Move",IDC_MOUSE_MOVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,15,90,10 - CONTROL "&Zero-pixel move",IDC_MOUSE_MOVE_ZERO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,245,25,70,10 + CONTROL "Extended FN Key: F13",IDC_KEY_F13,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,15,90,10 + CONTROL "Extended FN Key: F14",IDC_KEY_F14,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,30,90,10 + CONTROL "Extended FN Key: F15",IDC_KEY_F15,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,45,90,10 + CONTROL "Extended FN Key: F16",IDC_KEY_F16,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,60,90,10 + CONTROL "Extended FN Key: F17",IDC_KEY_F17,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,75,90,10 + CONTROL "Extended FN Key: F18",IDC_KEY_F18,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,90,90,10 + CONTROL "Extended FN Key: F19",IDC_KEY_F19,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,105,90,10 + CONTROL "Extended FN Key: F20",IDC_KEY_F20,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,120,90,10 + CONTROL "Extended FN Key: F21",IDC_KEY_F21,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,135,90,10 + CONTROL "Extended FN Key: F22",IDC_KEY_F22,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,150,90,10 + CONTROL "Extended FN Key: F23",IDC_KEY_F23,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,165,90,10 + CONTROL "Extended FN Key: F24",IDC_KEY_F24,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,180,90,10 + CONTROL "One-pixel Mouse &Jiggle",IDC_MOUSE_MOVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,15,90,10 + CONTROL "Zero-pixel Mouse &Move",IDC_MOUSE_MOVE_ZERO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,30,90,10 PUSHBUTTON "Select &None",IDC_KEY_SELECT_NONE_BTN,225,55,85,14 PUSHBUTTON "Select All &Unassigned", IDC_KEY_SELECT_ALL_UNASSGND_BTN,225,80,85,14 - PUSHBUTTON "Select All Ext. FN Keys", IDC_KEY_SELECT_ALL_EXT_FN_BTN,225,105,85,14 + PUSHBUTTON "Select All Ext. &FN Keys", IDC_KEY_SELECT_ALL_EXT_FN_BTN,225,105,85,14 PUSHBUTTON "Select &All",IDC_KEY_SELECT_ALL_BTN,225,130,85,14 DEFPUSHBUTTON "A&pply",IDOK,225,155,85,14 PUSHBUTTON "&Cancel",IDCANCEL,225,180,85,14 diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index 1964a84..c05acc9 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -142,18 +142,14 @@ void StayAwakeCore::SimulateInput(int inputCode, wstring& inputName) break; case 24: - if (GetPreference(PREF_MOUSE_MOVE_ZERO, L"Y") == L"Y") - { - inputName = L"Zero Mouse Move"; - MouseMoveZero(); - } - else - { - inputName = L"Tiny Mouse Move"; - MouseMoveTiny(); - } + inputName = L"One-pixel Mouse Jiggle"; + MouseMoveTiny(); break; + case 25: + inputName = L"Zero-pixel Mouse Move"; + MouseMoveZero(); + break; default: inputName = L"Invalid Awake Key"; diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index 862db01..408d450 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -27,13 +27,12 @@ constexpr auto PREF_MULTI_INSTANCE = L"MultipleInstancesAllowed"; constexpr auto PREF_INTERVAL_LEGACY = L"TimerIntervalInSeconds"; constexpr auto PREF_INTERVAL_MINIMUM = L"MinimumIntervalInSeconds"; constexpr auto PREF_INTERVAL_MAXIMUM = L"MaximumIntervalInSeconds"; -constexpr auto PREF_MOUSE_MOVE_ZERO = L"MouseMoveZeroPixels"; constexpr auto MIN_PERIOD{ 10 }; constexpr auto MAX_PERIOD{ 9990 }; constexpr auto DEF_PERIOD{ 240 }; -constexpr auto LEN_ROSTER_KEYCODES{ 25 }; +constexpr auto LEN_ROSTER_KEYCODES{ 26 }; const auto DEF_SELECTED_KEYCODES = L"00" + wstring(LEN_ROSTER_KEYCODES - 2, L'1'); From c89efaa979f2ede7dda565e75d83daa6683a45ad Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:39:30 -0400 Subject: [PATCH 12/22] Update About dialog --- StayAwake/StayAwake.h | 2 +- StayAwake/StayAwake.rc | 17 +++++++++-------- StayAwake/StayAwakeAboutDlg.cpp | 4 ++-- StayAwake/resource.h | 1 + 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/StayAwake/StayAwake.h b/StayAwake/StayAwake.h index 7b5e178..c27ea52 100644 --- a/StayAwake/StayAwake.h +++ b/StayAwake/StayAwake.h @@ -6,7 +6,7 @@ #include "resource.h" -constexpr auto LAST_CODE_UPDATE_TIME = "Sep 21 2026 - 15:45:46"; +constexpr auto LAST_CODE_UPDATE_TIME = L"Sep 21 2026 - 15:45:46"; class CStayAwakeApp : public CWinApp diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index f5b7991..51198d3 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -69,18 +69,19 @@ END // Dialog // -IDD_ABOUTBOX DIALOGEX 0, 0, 191, 104 +IDD_ABOUTBOX DIALOGEX 0, 0, 210, 120 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About StayAwake" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN - ICON IDR_MAINFRAME,IDC_STATIC,12,14,21,20 - CTEXT "StayAwake",IDC_ABOUT_NAME,39,14,111,8,SS_NOPREFIX - CTEXT "Copyright (C) Shridhar Kumar, 2026",IDC_ABOUT_ATTRIBUTION,18,54,154,8 - DEFPUSHBUTTON "OK",IDOK,70,81,50,14,WS_GROUP - CTEXT "Version 1.0.0.1",IDC_ABOUT_VERSION,39,26,111,8 - CTEXT "Last code update:",IDC_ABOUT_BUILD_TIME,19,38,151,8 - CONTROL "GitHub Link",IDC_ABOUT_PROD_URL,"SysLink",WS_TABSTOP,77,66,37,8 + ICON IDR_MAINFRAME,IDC_STATIC,35,14,21,20 + CTEXT "StayAwake",IDC_ABOUT_NAME,55,14,100,8,SS_NOPREFIX + CTEXT "Version 1.0.0.1",IDC_ABOUT_VERSION,55,26,100,8 + CTEXT "Last code update:",IDC_ABOUT_BUILD_TIME,10,38,190,8 + CTEXT "Copyright (C) Shridhar Kumar, 2026",IDC_ABOUT_ATTRIBUTION,10,54,190,8 + CONTROL "GitHub Link",IDC_ABOUT_PROD_URL,"SysLink",WS_TABSTOP,86,70,37,8 + CTEXT "If you like StayAwake, please star this repo in GitHub.",IDC_ABOUT_PROD_REQ,10,82,190,8 + DEFPUSHBUTTON "OK",IDOK,80,100,50,14,WS_GROUP END IDD_STAYAWAKE_DIALOG DIALOGEX 0, 0, 205, 144 diff --git a/StayAwake/StayAwakeAboutDlg.cpp b/StayAwake/StayAwakeAboutDlg.cpp index fb1a5ac..50c6094 100644 --- a/StayAwake/StayAwakeAboutDlg.cpp +++ b/StayAwake/StayAwakeAboutDlg.cpp @@ -37,7 +37,7 @@ BOOL CAboutDlg::OnInitDialog() SetDlgItemText(IDC_ABOUT_NAME, Utils::getVersionInfo(theApp.m_hInstance, L"FileDescription").c_str()); SetDlgItemText(IDC_ABOUT_VERSION, (L"Version: " + Utils::getVersionInfo(theApp.m_hInstance, L"FileVersion") + buildBit).c_str()); - SetDlgItemTextA(m_hWnd, IDC_ABOUT_BUILD_TIME, (string{ "Last code update: " } + LAST_CODE_UPDATE_TIME).c_str()); + SetDlgItemText(IDC_ABOUT_BUILD_TIME, (wstring{ L"Last code update: " } + LAST_CODE_UPDATE_TIME).c_str()); SetDlgItemText(IDC_ABOUT_ATTRIBUTION, Utils::getVersionInfo(theApp.m_hInstance, L"LegalCopyright").c_str()); return TRUE; @@ -52,5 +52,5 @@ afx_msg void CAboutDlg::OnProdUrlClick(NMHDR* pNotifyStruct, LRESULT* result) afx_msg void CAboutDlg::OnProdUrlReturn(NMHDR* pNotifyStruct, LRESULT* result) { - ShellExecute(NULL, L"open", Utils::getVersionInfo(theApp.m_hInstance, L"CompanyName").c_str(), NULL, NULL, SW_SHOW); + OnProdUrlClick(pNotifyStruct, result); } diff --git a/StayAwake/resource.h b/StayAwake/resource.h index 5b80db5..59df107 100644 --- a/StayAwake/resource.h +++ b/StayAwake/resource.h @@ -62,6 +62,7 @@ #define IDC_ABOUT_BUILD_TIME 1203 #define IDC_ABOUT_ATTRIBUTION 1204 #define IDC_ABOUT_PROD_URL 1205 +#define IDC_ABOUT_PROD_REQ 1206 #define IDM_EXIT 32771 #define IDM_RESTORE 32772 From 870913c0999d0838f09e60824b88181b704757c2 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:02:06 -0400 Subject: [PATCH 13/22] Optimize builds favoring size and use LTCG --- StayAwake/StayAwake.vcxproj | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/StayAwake/StayAwake.vcxproj b/StayAwake/StayAwake.vcxproj index 23c26c9..4393f47 100644 --- a/StayAwake/StayAwake.vcxproj +++ b/StayAwake/StayAwake.vcxproj @@ -150,16 +150,18 @@ stdc17 true true - Speed + Size true - false + true + OnlyExplicitInline + MinSpace Windows true true /BREPRO %(AdditionalOptions) - Default + UseLinkTimeCodeGeneration false @@ -184,18 +186,20 @@ pch.h true true - Speed + Size true stdcpp20 stdc17 - false + true + OnlyExplicitInline + MinSpace Windows true true /BREPRO - Default + UseLinkTimeCodeGeneration false From 5d47de716ec2b118265e53c224ceaa2ab9c0b974 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:10:07 -0400 Subject: [PATCH 14/22] Add VersionUpdate class for 'Check for Updates' feature Add functions to: 1. Fetch repo's latest release JSON data from GitHub 2. Parse latest release version info from that JSON data --- StayAwake/StayAwake.vcxproj | 2 + StayAwake/StayAwake.vcxproj.filters | 6 ++ StayAwake/VersionUpdate.cpp | 101 ++++++++++++++++++++++++++++ StayAwake/VersionUpdate.h | 21 ++++++ 4 files changed, 130 insertions(+) create mode 100644 StayAwake/VersionUpdate.cpp create mode 100644 StayAwake/VersionUpdate.h diff --git a/StayAwake/StayAwake.vcxproj b/StayAwake/StayAwake.vcxproj index 4393f47..b0ce24e 100644 --- a/StayAwake/StayAwake.vcxproj +++ b/StayAwake/StayAwake.vcxproj @@ -223,6 +223,7 @@ + @@ -237,6 +238,7 @@ Create + diff --git a/StayAwake/StayAwake.vcxproj.filters b/StayAwake/StayAwake.vcxproj.filters index 856a3d4..7fa4b7b 100644 --- a/StayAwake/StayAwake.vcxproj.filters +++ b/StayAwake/StayAwake.vcxproj.filters @@ -42,6 +42,9 @@ Header Files + + Header Files + @@ -65,6 +68,9 @@ Source Files + + Source Files + diff --git a/StayAwake/VersionUpdate.cpp b/StayAwake/VersionUpdate.cpp new file mode 100644 index 0000000..201c12e --- /dev/null +++ b/StayAwake/VersionUpdate.cpp @@ -0,0 +1,101 @@ +#include "pch.h" + +#include "VersionUpdate.h" +#include + +#pragma comment(lib, "winhttp.lib") + +wstring VersionUpdate::DownloadUrl(const wstring& host, const wstring& path) +{ + wstring response{}; + + HINTERNET hSession = + WinHttpOpen(L"StayAwake Update Checker/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (!hSession) return response; + + HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0); + + if (!hConnect) + { + WinHttpCloseHandle(hSession); + return response; + } + + HINTERNET hRequest = + WinHttpOpenRequest(hConnect, L"GET", path.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + + if (!hRequest) + { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return response; + } + + // GitHub likes having a User-Agent. + BOOL bSuccess = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + + if (bSuccess) + bSuccess = WinHttpReceiveResponse(hRequest, nullptr); + + if (bSuccess) + { + DWORD dwSize = 0; + + do + { + dwSize = 0; + + if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) + break; + + if (dwSize == 0) + break; + + string buffer(dwSize, '\0'); + + DWORD dwDownloaded = 0; + + if (!WinHttpReadData(hRequest, buffer.data(), dwSize, &dwDownloaded)) + break; + + response.append(wstring(buffer.begin(), buffer.begin() + dwDownloaded)); + } while (dwSize > 0); + } + + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + + return response; +} + +wstring VersionUpdate::ExtractTagNameValue(const wstring& json) +{ + const wchar_t* tagStart = wcsstr(json.c_str(), GITHUB_API_TAGNAME); + if (!tagStart) return L""; + + const wchar_t* valStart = wcschr(tagStart + wcslen(GITHUB_API_TAGNAME), L'"'); + if (!valStart) return L""; + ++valStart; // Move past the first quote + + const wchar_t* valEnd = wcschr(valStart, L'"'); + if (!valEnd) return L""; + + return wstring (valStart, valEnd - valStart); +} + +wstring VersionUpdate::GetVersion() +{ + auto json = DownloadUrl(GITHUB_API_HOST, GITHUB_APP_PATH); + if (json.empty()) return L""; + + wstring tag{ ExtractTagNameValue(json) }; + if (!tag.empty() && tag[0] == L'v') + tag.erase(0, 1); // Remove leading 'v' + return tag; +} diff --git a/StayAwake/VersionUpdate.h b/StayAwake/VersionUpdate.h new file mode 100644 index 0000000..aa70b85 --- /dev/null +++ b/StayAwake/VersionUpdate.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +using std::string; +using std::wstring; + +constexpr auto GITHUB_API_HOST = L"api.github.com"; +constexpr auto GITHUB_APP_PATH = L"/repos/shriprem/StayAwake/releases/latest"; +constexpr auto GITHUB_API_TAGNAME = L"\"tag_name\""; + +class VersionUpdate +{ +public: + wstring GetVersion(); + +private: + wstring DownloadUrl(const wstring& host, const wstring& path); + wstring ExtractTagNameValue(const wstring& json); +}; + From 725f1be30a033850d841030b21d9c91652ac3cc5 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 4 Oct 2026 12:24:49 -0400 Subject: [PATCH 15/22] StayAwake main dialog: Rename resource IDs and dialog events Renaming to better reflect the purpose for controls and events --- StayAwake/StayAwake.rc | 25 ++++++------ StayAwake/StayAwakeCore.cpp | 2 +- StayAwake/StayAwakeDlg.cpp | 80 ++++++++++++++++++------------------- StayAwake/StayAwakeDlg.h | 8 ++-- StayAwake/resource.h | 24 +++++------ 5 files changed, 69 insertions(+), 70 deletions(-) diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index 51198d3..96f839d 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -90,21 +90,20 @@ EXSTYLE WS_EX_APPWINDOW CAPTION "StayAwake" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN - PUSHBUTTON "Select multiple &options for random simulation",IDC_STAYAWAKE_KEYS_ROSTER_BTN,13,12,179,14 + PUSHBUTTON "Select multiple &options for random simulation",IDC_SELECT_INPUT_OPTIONS_BTN,13,12,179,14 RTEXT "&Minimum Awake Seconds:",IDC_STATIC,1,35,100,8 - EDITTEXT IDC_STAYAWAKE_INTERVAL_MIN,102,32,27,13,ES_NUMBER + EDITTEXT IDC_INTERVAL_MIN_EDIT,102,32,27,13,ES_NUMBER RTEXT "Ma&ximum Awake Seconds:",IDC_STATIC,1,55,100,8 - EDITTEXT IDC_STAYAWAKE_INTERVAL_MAX,102,52,27,13,ES_NUMBER - PUSHBUTTON "Set &Timer",IDC_STAYAWAKE_SET_INTERVAL_BTN,132,32,60,34 - RTEXT "",IDC_STAYAWAKE_LAST_EVENT,1,74,143,8 - RTEXT "",IDC_STAYAWAKE_LAST_KEYCODE,1,82,143,8 - RTEXT "",IDC_STAYAWAKE_NEXT_EVENT,1,93,143,8 - PUSHBUTTON "&Pause",IDC_STAYAWAKE_PAUSE_RESUME_BTN,16,110,50,14 - PUSHBUTTON "Minimi&ze",IDC_MINIMIZE,74,110,50,14 - PUSHBUTTON "&Exit",IDC_EXIT,132,110,50,14 - CONTROL "&Launch application in minimized state",IDC_START_MINIMIZED, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,38,128,128,10 - PUSHBUTTON "...",IDC_ABOUT_BUTTON,188,126,15,13,BS_BITMAP + EDITTEXT IDC_INTERVAL_MAX_EDIT,102,52,27,13,ES_NUMBER + PUSHBUTTON "Set &Timer",IDC_SET_TIMER_BTN,132,32,60,34 + RTEXT "",IDC_LAST_EVENT_TIME_INFO,1,74,143,8 + RTEXT "",IDC_LAST_EVENT_INPUT_INFO,1,82,143,8 + RTEXT "",IDC_NEXT_EVENT_TIME_INFO,1,93,143,8 + PUSHBUTTON "&Pause",IDC_PAUSE_RESUME_BTN,16,110,50,14 + PUSHBUTTON "Minimi&ze",IDC_MINIMIZE_BTN,74,110,50,14 + PUSHBUTTON "&Exit",IDC_EXIT_BTN,132,110,50,14 + CONTROL "&Start minimized",IDC_START_MINIMIZED_CBX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,128,70,10 + PUSHBUTTON "...",IDC_ABOUT_BTN,188,126,15,13,BS_BITMAP END IDD_SELECT_KEYCODES_DIALOG DIALOGEX 0, 0, 330, 205 diff --git a/StayAwake/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp index c05acc9..7d2d15f 100644 --- a/StayAwake/StayAwakeCore.cpp +++ b/StayAwake/StayAwakeCore.cpp @@ -46,7 +46,7 @@ bool StayAwakeCore::CheckSelectedKeyCodes(wstring sKeyCodes) { return (sKeyCodes.length() == LEN_ROSTER_KEYCODES && sKeyCodes != wstring(LEN_ROSTER_KEYCODES, L'0') && - sKeyCodes.find_first_not_of(L"01") == std::string::npos); + sKeyCodes.find_first_not_of(L"01") == string::npos); } bool StayAwakeCore::SaveSelectedKeyCodes(wstring sKeyCodes) diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 4e48cc1..686c64f 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -37,15 +37,15 @@ BEGIN_MESSAGE_MAP(CStayAwakeDlg, CDialogEx) ON_COMMAND(IDM_EXIT, &CStayAwakeDlg::OnExit) ON_WM_DESTROY() ON_WM_TIMER() - ON_BN_CLICKED(IDC_MINIMIZE, &CStayAwakeDlg::OnMinimize) - ON_BN_CLICKED(IDC_EXIT, &CStayAwakeDlg::OnExit) - ON_BN_CLICKED(IDC_STAYAWAKE_KEYS_ROSTER_BTN, &CStayAwakeDlg::OnSetupKeyCodesRosterClicked) - ON_BN_CLICKED(IDC_STAYAWAKE_SET_INTERVAL_BTN, &CStayAwakeDlg::OnSetInterval) - ON_EN_KILLFOCUS(IDC_STAYAWAKE_INTERVAL_MIN, &CStayAwakeDlg::OnKillfocusIntervalMin) - ON_EN_KILLFOCUS(IDC_STAYAWAKE_INTERVAL_MAX, &CStayAwakeDlg::OnKillfocusIntervalMax) - ON_BN_CLICKED(IDC_ABOUT_BUTTON, &CStayAwakeDlg::OnClickedAboutButton) - ON_BN_CLICKED(IDC_STAYAWAKE_PAUSE_RESUME_BTN, &CStayAwakeDlg::OnPauseResume) - ON_BN_CLICKED(IDC_START_MINIMIZED, &CStayAwakeDlg::OnStartMinimized) + ON_BN_CLICKED(IDC_MINIMIZE_BTN, &CStayAwakeDlg::OnMinimize) + ON_BN_CLICKED(IDC_EXIT_BTN, &CStayAwakeDlg::OnExit) + ON_BN_CLICKED(IDC_SELECT_INPUT_OPTIONS_BTN, &CStayAwakeDlg::OnSelectInputOptionsClicked) + ON_BN_CLICKED(IDC_SET_TIMER_BTN, &CStayAwakeDlg::OnSetTimerClicked) + ON_EN_KILLFOCUS(IDC_INTERVAL_MIN_EDIT, &CStayAwakeDlg::OnKillfocusIntervalMin) + ON_EN_KILLFOCUS(IDC_INTERVAL_MAX_EDIT, &CStayAwakeDlg::OnKillfocusIntervalMax) + ON_BN_CLICKED(IDC_ABOUT_BTN, &CStayAwakeDlg::OnClickedAboutButton) + ON_BN_CLICKED(IDC_PAUSE_RESUME_BTN, &CStayAwakeDlg::OnPauseResumeClicked) + ON_BN_CLICKED(IDC_START_MINIMIZED_CBX, &CStayAwakeDlg::OnStartMinimized) ON_WM_WTSSESSION_CHANGE() END_MESSAGE_MAP() @@ -84,16 +84,16 @@ BOOL CStayAwakeDlg::OnInitDialog() InitConfigFilePath(); m_AwakeCore.InitIntervals(m_IntervalMinSeconds, m_IntervalMaxSeconds); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MIN, m_IntervalMinSeconds, FALSE); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, m_IntervalMaxSeconds, FALSE); + SetDlgItemInt(IDC_INTERVAL_MIN_EDIT, m_IntervalMinSeconds, FALSE); + SetDlgItemInt(IDC_INTERVAL_MAX_EDIT, m_IntervalMaxSeconds, FALSE); - Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_STAYAWAKE_INTERVAL_MIN, L"", INTERVAL_TOOLTIP, 3, TRUE); - Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_STAYAWAKE_INTERVAL_MAX, L"", INTERVAL_TOOLTIP, 3, TRUE); + Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_INTERVAL_MIN_EDIT, L"", INTERVAL_TOOLTIP, 3, TRUE); + Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_INTERVAL_MAX_EDIT, L"", INTERVAL_TOOLTIP, 3, TRUE); - SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, IsTimerPaused() ? BTN_TEXT_RESUME : BTN_TEXT_PAUSE); + SetDlgItemText(IDC_PAUSE_RESUME_BTN, IsTimerPaused() ? BTN_TEXT_RESUME : BTN_TEXT_PAUSE); - Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_ABOUT_BUTTON, L"", L"About StayAwake", 3, TRUE); - Utils::loadBitmap(theApp.m_hInstance, m_hWnd, IDC_ABOUT_BUTTON, IDB_ABOUT_BITMAP); + Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_ABOUT_BTN, L"", L"About StayAwake", 3, TRUE); + Utils::loadBitmap(theApp.m_hInstance, m_hWnd, IDC_ABOUT_BTN, IDB_ABOUT_BITMAP); InitTrayIcon(); PostMessage(WM_POST_OPEN, 0, 0); @@ -133,7 +133,7 @@ afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) InitAwakes(); BOOL bMinimized = (m_AwakeCore.GetPreference(PREF_START_MINIMIZED, L"N") == L"Y"); - CheckDlgButton(IDC_START_MINIMIZED, bMinimized ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(IDC_START_MINIMIZED_CBX, bMinimized ? BST_CHECKED : BST_UNCHECKED); if (bMinimized) MinimizeToTray(); return 0; @@ -171,12 +171,12 @@ void CStayAwakeDlg::OnKillfocusIntervalMin() { int nInterval{}; - nInterval = GetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MIN, nullptr, FALSE); + nInterval = GetDlgItemInt(IDC_INTERVAL_MIN_EDIT, nullptr, FALSE); if (nInterval < MIN_PERIOD || nInterval > MAX_PERIOD) { - Utils::showEditBalloonTip(GetDlgItem(IDC_STAYAWAKE_INTERVAL_MIN)->m_hWnd, INTERVAL_WARN_TITLE, INTERVAL_WARNING.c_str()); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MIN, m_IntervalMinSeconds, FALSE); + Utils::showEditBalloonTip(GetDlgItem(IDC_INTERVAL_MIN_EDIT)->m_hWnd, INTERVAL_WARN_TITLE, INTERVAL_WARNING.c_str()); + SetDlgItemInt(IDC_INTERVAL_MIN_EDIT, m_IntervalMinSeconds, FALSE); return; } @@ -188,12 +188,12 @@ void CStayAwakeDlg::OnKillfocusIntervalMax() { int nInterval{}; - nInterval = GetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, nullptr, FALSE); + nInterval = GetDlgItemInt(IDC_INTERVAL_MAX_EDIT, nullptr, FALSE); if (nInterval < MIN_PERIOD || nInterval > MAX_PERIOD) { - Utils::showEditBalloonTip(GetDlgItem(IDC_STAYAWAKE_INTERVAL_MAX)->m_hWnd, INTERVAL_WARN_TITLE, INTERVAL_WARNING.c_str()); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, m_IntervalMaxSeconds, FALSE); + Utils::showEditBalloonTip(GetDlgItem(IDC_INTERVAL_MAX_EDIT)->m_hWnd, INTERVAL_WARN_TITLE, INTERVAL_WARNING.c_str()); + SetDlgItemInt(IDC_INTERVAL_MAX_EDIT, m_IntervalMaxSeconds, FALSE); return; } @@ -201,7 +201,7 @@ void CStayAwakeDlg::OnKillfocusIntervalMax() } -void CStayAwakeDlg::OnSetupKeyCodesRosterClicked() +void CStayAwakeDlg::OnSelectInputOptionsClicked() { CSelectKeyCodesDlg dlgSelectKeyCodes(this, m_AwakeCore); @@ -253,7 +253,7 @@ void CStayAwakeDlg::InitAwakes() InitRosterKeyCodes(); SimulateAwakeKeyPress(); - SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, BTN_TEXT_PAUSE); + SetDlgItemText(IDC_PAUSE_RESUME_BTN, BTN_TEXT_PAUSE); m_AwakeCore.SetPreference(PREF_AWAKE_PAUSED, L"N"); } @@ -276,8 +276,8 @@ void CStayAwakeDlg::InitTrayIcon() void CStayAwakeDlg::MinimizeToTray() { if (m_bMinimized) return; - SendDlgItemMessage(IDC_STAYAWAKE_INTERVAL_MIN, EM_HIDEBALLOONTIP, 0, 0); - SendDlgItemMessage(IDC_STAYAWAKE_INTERVAL_MAX, EM_HIDEBALLOONTIP, 0, 0); + SendDlgItemMessage(IDC_INTERVAL_MIN_EDIT, EM_HIDEBALLOONTIP, 0, 0); + SendDlgItemMessage(IDC_INTERVAL_MAX_EDIT, EM_HIDEBALLOONTIP, 0, 0); if (!Shell_NotifyIcon(NIM_ADD, &m_TrayData)) MessageBox(L"Unable to Display Tray Icon", L"Error!"); @@ -320,7 +320,7 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() if (m_bSystemLocked) { - SetDlgItemText(IDC_STAYAWAKE_NEXT_EVENT, L"PAUSED since Windows is LOCKED"); + SetDlgItemText(IDC_NEXT_EVENT_TIME_INFO, L"PAUSED since Windows is LOCKED"); return; } @@ -331,14 +331,14 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() SYSTEMTIME lastTime{}; GetLocalTime(&lastTime); - SetDlgItemText(IDC_STAYAWAKE_LAST_EVENT, Utils::formatSystemTime(lastTime, L"Last StayAwake event").c_str()); + SetDlgItemText(IDC_LAST_EVENT_TIME_INFO, Utils::formatSystemTime(lastTime, L"Last StayAwake event").c_str()); #ifdef DEBUG_DISPLAY_IDLE_TICKS Sleep((rand() % 20) + 1); // simulate a small delay to get a more accurate idle time ULONGLONG idleTime = m_AwakeCore.GetIdleTimeMilliseconds(); - SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L":" + to_wstring(idleTime) + L" ms]").c_str()); + SetDlgItemText(IDC_LAST_EVENT_INPUT_INFO, (L"[" + sAwakeKeyCode + L":" + to_wstring(idleTime) + L" ms]").c_str()); #else - SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L"]").c_str()); + SetDlgItemText(IDC_LAST_EVENT_INPUT_INFO, (L"[" + sAwakeKeyCode + L"]").c_str()); #endif UINT nTimerSeconds{ m_IntervalMinSeconds }; @@ -350,11 +350,11 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() SYSTEMTIME nextTime{}; GetSystemTime(&nextTime); Utils::addSecondsToTime(nextTime, nTimerSeconds); - SetDlgItemText(IDC_STAYAWAKE_NEXT_EVENT, Utils::formatSystemTime(nextTime, L"Next StayAwake event").c_str()); + SetDlgItemText(IDC_NEXT_EVENT_TIME_INFO, Utils::formatSystemTime(nextTime, L"Next StayAwake event").c_str()); } -void CStayAwakeDlg::OnSetInterval() +void CStayAwakeDlg::OnSetTimerClicked() { OnKillfocusIntervalMin(); OnKillfocusIntervalMax(); @@ -365,8 +365,8 @@ void CStayAwakeDlg::OnSetInterval() m_IntervalMinSeconds = m_IntervalMaxSeconds; m_IntervalMaxSeconds = nTemp; - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MIN, m_IntervalMinSeconds, FALSE); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, m_IntervalMaxSeconds, FALSE); + SetDlgItemInt(IDC_INTERVAL_MIN_EDIT, m_IntervalMinSeconds, FALSE); + SetDlgItemInt(IDC_INTERVAL_MAX_EDIT, m_IntervalMaxSeconds, FALSE); } m_AwakeCore.SetPreference(PREF_INTERVAL_MINIMUM, to_wstring(m_IntervalMinSeconds)); @@ -382,7 +382,7 @@ void CStayAwakeDlg::OnClickedAboutButton() dlgAbout.DoModal(); } -void CStayAwakeDlg::OnPauseResume() +void CStayAwakeDlg::OnPauseResumeClicked() { if (IsTimerPaused()) { @@ -392,7 +392,7 @@ void CStayAwakeDlg::OnPauseResume() { KillTimer(m_TimerID); - SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, BTN_TEXT_RESUME); + SetDlgItemText(IDC_PAUSE_RESUME_BTN, BTN_TEXT_RESUME); m_AwakeCore.SetPreference(PREF_AWAKE_PAUSED, L"Y"); ShowPausedInfo(false); } @@ -406,14 +406,14 @@ bool CStayAwakeDlg::IsTimerPaused() void CStayAwakeDlg::ShowPausedInfo(bool both) { if (both) - SetDlgItemText(IDC_STAYAWAKE_LAST_EVENT, L"Last StayAwake event: PAUSED"); + SetDlgItemText(IDC_LAST_EVENT_TIME_INFO, L"Last StayAwake event: PAUSED"); - SetDlgItemText(IDC_STAYAWAKE_NEXT_EVENT, L"Next StayAwake event: PAUSED"); + SetDlgItemText(IDC_NEXT_EVENT_TIME_INFO, L"Next StayAwake event: PAUSED"); } void CStayAwakeDlg::OnStartMinimized() { - m_AwakeCore.SetPreference(PREF_START_MINIMIZED, (IsDlgButtonChecked(IDC_START_MINIMIZED) == BST_CHECKED) ? L"Y" : L"N"); + m_AwakeCore.SetPreference(PREF_START_MINIMIZED, (IsDlgButtonChecked(IDC_START_MINIMIZED_CBX) == BST_CHECKED) ? L"Y" : L"N"); } void CStayAwakeDlg::OnSessionChange(UINT nSessionState, UINT nId) diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index 10a18b2..af9489c 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -8,7 +8,7 @@ #define WM_TRAY_NOTIFY (WM_APP + 2) #define WM_RESTORE_DIALOG theApp.WM_SHOWFIRSTINSTANCE -#define DEBUG_DISPLAY_IDLE_TICKS +//#define DEBUG_DISPLAY_IDLE_TICKS constexpr auto BTN_TEXT_PAUSE = L"&Pause"; constexpr auto BTN_TEXT_RESUME = L"&Resume"; @@ -55,12 +55,12 @@ class CStayAwakeDlg : public CDialogEx afx_msg LRESULT OnPostOpen(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnTrayNotify(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnRestoreDialog(WPARAM wParam, LPARAM lParam); - afx_msg void OnSetupKeyCodesRosterClicked(); + afx_msg void OnSelectInputOptionsClicked(); afx_msg void OnTimer(UINT_PTR nIDEvent); afx_msg void OnKillfocusIntervalMin(); afx_msg void OnKillfocusIntervalMax(); - afx_msg void OnSetInterval(); - afx_msg void OnPauseResume(); + afx_msg void OnSetTimerClicked(); + afx_msg void OnPauseResumeClicked(); afx_msg void OnMinimize() { MinimizeToTray(); }; afx_msg void OnClickedAboutButton(); afx_msg void OnEnter() { /* Ignore ENTER key press */ }; diff --git a/StayAwake/resource.h b/StayAwake/resource.h index 59df107..fb38d5e 100644 --- a/StayAwake/resource.h +++ b/StayAwake/resource.h @@ -12,18 +12,18 @@ #define IDR_MAINFRAME 133 #define IDB_ABOUT_BITMAP 134 -#define IDC_STAYAWAKE_KEYS_ROSTER_BTN 1001 -#define IDC_STAYAWAKE_INTERVAL_MIN 1002 -#define IDC_STAYAWAKE_INTERVAL_MAX 1003 -#define IDC_STAYAWAKE_SET_INTERVAL_BTN 1004 -#define IDC_STAYAWAKE_LAST_EVENT 1005 -#define IDC_STAYAWAKE_NEXT_EVENT 1006 -#define IDC_STAYAWAKE_LAST_KEYCODE 1007 -#define IDC_STAYAWAKE_PAUSE_RESUME_BTN 1011 -#define IDC_MINIMIZE 1012 -#define IDC_EXIT 1013 -#define IDC_START_MINIMIZED 1014 -#define IDC_ABOUT_BUTTON 1015 +#define IDC_SELECT_INPUT_OPTIONS_BTN 1001 +#define IDC_INTERVAL_MIN_EDIT 1002 +#define IDC_INTERVAL_MAX_EDIT 1003 +#define IDC_SET_TIMER_BTN 1004 +#define IDC_LAST_EVENT_TIME_INFO 1005 +#define IDC_LAST_EVENT_INPUT_INFO 1006 +#define IDC_NEXT_EVENT_TIME_INFO 1007 +#define IDC_PAUSE_RESUME_BTN 1011 +#define IDC_MINIMIZE_BTN 1012 +#define IDC_EXIT_BTN 1013 +#define IDC_START_MINIMIZED_CBX 1014 +#define IDC_ABOUT_BTN 1015 #define IDC_KEY_SCROLL_LOCK 1101 #define IDC_KEY_VOLUME_UP_DOWN 1102 From 68a46ec1030b67356eb4b9f1c2eb3020457f9b05 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:32:03 -0400 Subject: [PATCH 16/22] Add 'Update Check' button --- StayAwake/StayAwake.rc | 15 ++++++++------- StayAwake/StayAwakeDlg.cpp | 9 ++++++++- StayAwake/StayAwakeDlg.h | 2 ++ StayAwake/resource.h | 3 ++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index 96f839d..3d16906 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -84,7 +84,7 @@ BEGIN DEFPUSHBUTTON "OK",IDOK,80,100,50,14,WS_GROUP END -IDD_STAYAWAKE_DIALOG DIALOGEX 0, 0, 205, 144 +IDD_STAYAWAKE_DIALOG DIALOGEX 0, 0, 205, 160 STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "StayAwake" @@ -99,11 +99,12 @@ BEGIN RTEXT "",IDC_LAST_EVENT_TIME_INFO,1,74,143,8 RTEXT "",IDC_LAST_EVENT_INPUT_INFO,1,82,143,8 RTEXT "",IDC_NEXT_EVENT_TIME_INFO,1,93,143,8 - PUSHBUTTON "&Pause",IDC_PAUSE_RESUME_BTN,16,110,50,14 - PUSHBUTTON "Minimi&ze",IDC_MINIMIZE_BTN,74,110,50,14 - PUSHBUTTON "&Exit",IDC_EXIT_BTN,132,110,50,14 - CONTROL "&Start minimized",IDC_START_MINIMIZED_CBX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,128,70,10 - PUSHBUTTON "...",IDC_ABOUT_BTN,188,126,15,13,BS_BITMAP + PUSHBUTTON "&Pause",IDC_PAUSE_RESUME_BTN,10,110,55,14 + PUSHBUTTON "Minimi&ze",IDC_MINIMIZE_BTN,75,110,55,14 + PUSHBUTTON "&Exit",IDC_EXIT_BTN,140,110,55,14 + CONTROL "&Start minimized",IDC_START_MINIMIZED_CBX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,77,125,70,10 + PUSHBUTTON "&Update Check",IDC_UPDATE_CHECK_BTN,75,144,55,14 + PUSHBUTTON "...",IDC_ABOUT_BTN,188,144,15,13,BS_BITMAP END IDD_SELECT_KEYCODES_DIALOG DIALOGEX 0, 0, 330, 205 @@ -203,7 +204,7 @@ BEGIN IDD_STAYAWAKE_DIALOG, DIALOG BEGIN RIGHTMARGIN, 203 - BOTTOMMARGIN, 142 + BOTTOMMARGIN, 158 END IDD_SELECT_KEYCODES_DIALOG, DIALOG diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 686c64f..ec792de 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -1,10 +1,12 @@ #include "pch.h" #include "framework.h" +#include "afxdialogex.h" + #include "StayAwake.h" #include "StayAwakeDlg.h" #include "SelectKeyCodesDlg.h" #include "StayAwakeAboutDlg.h" -#include "afxdialogex.h" +#include "VersionUpdate.h" #include "Utils.h" #include @@ -47,6 +49,7 @@ BEGIN_MESSAGE_MAP(CStayAwakeDlg, CDialogEx) ON_BN_CLICKED(IDC_PAUSE_RESUME_BTN, &CStayAwakeDlg::OnPauseResumeClicked) ON_BN_CLICKED(IDC_START_MINIMIZED_CBX, &CStayAwakeDlg::OnStartMinimized) ON_WM_WTSSESSION_CHANGE() + ON_BN_CLICKED(IDC_UPDATE_CHECK_BTN, &CStayAwakeDlg::OnUpdateCheckClicked) END_MESSAGE_MAP() @@ -431,3 +434,7 @@ void CStayAwakeDlg::OnSessionChange(UINT nSessionState, UINT nId) CDialogEx::OnSessionChange(nSessionState, nId); } + +void CStayAwakeDlg::OnUpdateCheckClicked() +{ +} diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index af9489c..4fd4760 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -82,4 +82,6 @@ class CStayAwakeDlg : public CDialogEx void ShowPausedInfo(bool both); bool IsTimerPaused(); +public: + afx_msg void OnUpdateCheckClicked(); }; diff --git a/StayAwake/resource.h b/StayAwake/resource.h index fb38d5e..416a530 100644 --- a/StayAwake/resource.h +++ b/StayAwake/resource.h @@ -23,7 +23,8 @@ #define IDC_MINIMIZE_BTN 1012 #define IDC_EXIT_BTN 1013 #define IDC_START_MINIMIZED_CBX 1014 -#define IDC_ABOUT_BTN 1015 +#define IDC_UPDATE_CHECK_BTN 1015 +#define IDC_ABOUT_BTN 1016 #define IDC_KEY_SCROLL_LOCK 1101 #define IDC_KEY_VOLUME_UP_DOWN 1102 From ed69537764858c444b4878fe2ae15fbd033fdc7d Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:40:52 -0400 Subject: [PATCH 17/22] Add control IDs & layout resource for Update Check dialog --- StayAwake/StayAwake.rc | 59 ++++++++++++++++++++++++++++++------------ StayAwake/resource.h | 19 +++++++++----- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index 3d16906..115ba37 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -21,6 +21,7 @@ #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// @@ -28,12 +29,12 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // TEXTINCLUDE // -1 TEXTINCLUDE +1 TEXTINCLUDE BEGIN "resource.h\0" END -2 TEXTINCLUDE +2 TEXTINCLUDE BEGIN "#ifndef APSTUDIO_INVOKED\r\n" "#include ""targetver.h""\r\n" @@ -43,7 +44,7 @@ BEGIN "\0" END -3 TEXTINCLUDE +3 TEXTINCLUDE BEGIN "#define _AFX_NO_SPLITTER_RESOURCES\r\n" "#define _AFX_NO_OLE_RESOURCES\r\n" @@ -103,8 +104,8 @@ BEGIN PUSHBUTTON "Minimi&ze",IDC_MINIMIZE_BTN,75,110,55,14 PUSHBUTTON "&Exit",IDC_EXIT_BTN,140,110,55,14 CONTROL "&Start minimized",IDC_START_MINIMIZED_CBX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,77,125,70,10 - PUSHBUTTON "&Update Check",IDC_UPDATE_CHECK_BTN,75,144,55,14 - PUSHBUTTON "...",IDC_ABOUT_BTN,188,144,15,13,BS_BITMAP + PUSHBUTTON "&Update Check",IDC_UPDATE_CHECK_BTN,75,142,55,14 + PUSHBUTTON "...",IDC_ABOUT_BTN,188,142,15,13,BS_BITMAP END IDD_SELECT_KEYCODES_DIALOG DIALOGEX 0, 0, 330, 205 @@ -112,17 +113,17 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSM CAPTION "Select multiple simulation options" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN - CONTROL "&Scroll Lock cycling",IDC_KEY_SCROLL_LOCK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,15,90,10 + CONTROL "&Scroll Lock cycling",IDC_KEY_SCROLL_LOCK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,15,90,10 CONTROL "&Volume Down && Up",IDC_KEY_VOLUME_UP_DOWN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,30,90,10 - CONTROL "Unassigned Key #&1",IDC_KEY_UNASSIGNED_1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,45,90,10 - CONTROL "Unassigned Key #&2",IDC_KEY_UNASSIGNED_2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,60,90,10 - CONTROL "Unassigned Key #&3",IDC_KEY_UNASSIGNED_3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,75,90,10 - CONTROL "Unassigned Key #&4",IDC_KEY_UNASSIGNED_4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,90,90,10 - CONTROL "Unassigned Key #&5",IDC_KEY_UNASSIGNED_5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,105,90,10 - CONTROL "Unassigned Key #&6",IDC_KEY_UNASSIGNED_6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,120,90,10 - CONTROL "Unassigned Key #&7",IDC_KEY_UNASSIGNED_7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,135,90,10 - CONTROL "Unassigned Key #&8",IDC_KEY_UNASSIGNED_8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,150,90,10 - CONTROL "Unassigned Key #&9",IDC_KEY_UNASSIGNED_9,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,165,90,10 + CONTROL "Unassigned Key #&1",IDC_KEY_UNASSIGNED_1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,45,90,10 + CONTROL "Unassigned Key #&2",IDC_KEY_UNASSIGNED_2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,60,90,10 + CONTROL "Unassigned Key #&3",IDC_KEY_UNASSIGNED_3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,75,90,10 + CONTROL "Unassigned Key #&4",IDC_KEY_UNASSIGNED_4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,90,90,10 + CONTROL "Unassigned Key #&5",IDC_KEY_UNASSIGNED_5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,105,90,10 + CONTROL "Unassigned Key #&6",IDC_KEY_UNASSIGNED_6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,120,90,10 + CONTROL "Unassigned Key #&7",IDC_KEY_UNASSIGNED_7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,135,90,10 + CONTROL "Unassigned Key #&8",IDC_KEY_UNASSIGNED_8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,150,90,10 + CONTROL "Unassigned Key #&9",IDC_KEY_UNASSIGNED_9,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,165,90,10 CONTROL "Unassigned Key #1&0",IDC_KEY_UNASSIGNED_10,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,180,90,10 CONTROL "Extended FN Key: F13",IDC_KEY_F13,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,15,90,10 CONTROL "Extended FN Key: F14",IDC_KEY_F14,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,30,90,10 @@ -139,13 +140,29 @@ BEGIN CONTROL "One-pixel Mouse &Jiggle",IDC_MOUSE_MOVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,15,90,10 CONTROL "Zero-pixel Mouse &Move",IDC_MOUSE_MOVE_ZERO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,225,30,90,10 PUSHBUTTON "Select &None",IDC_KEY_SELECT_NONE_BTN,225,55,85,14 - PUSHBUTTON "Select All &Unassigned", IDC_KEY_SELECT_ALL_UNASSGND_BTN,225,80,85,14 - PUSHBUTTON "Select All Ext. &FN Keys", IDC_KEY_SELECT_ALL_EXT_FN_BTN,225,105,85,14 + PUSHBUTTON "Select All &Unassigned",IDC_KEY_SELECT_ALL_UNASSGND_BTN,225,80,85,14 + PUSHBUTTON "Select All Ext. &FN Keys",IDC_KEY_SELECT_ALL_EXT_FN_BTN,225,105,85,14 PUSHBUTTON "Select &All",IDC_KEY_SELECT_ALL_BTN,225,130,85,14 DEFPUSHBUTTON "A&pply",IDOK,225,155,85,14 PUSHBUTTON "&Cancel",IDCANCEL,225,180,85,14 END +IDD_UPDATE_CHECK_DIALOG DIALOGEX 0, 0, 170, 115 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Check for Updates" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + RTEXT "Installed Version:",IDC_STATIC,5,12,80,8 + LTEXT "",IDC_INSTALLED_VERSION_INFO,86,12,60,8 + RTEXT "Latest Version:",IDC_STATIC,5,27,80,8 + LTEXT "",IDC_LATEST_VERSION_INFO,86,27,60,8 + CTEXT "",IDC_VERSION_CHECK_STATUS,5,42,160,8 + CONTROL "Stay Awake - Latest Release",IDC_LATEST_RELEASE_URL,"SysLink",WS_TABSTOP,38,57,94,8 + CONTROL "Check for &updates when StayAwake starts",IDC_START_VERSION_CHECK_CBX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,77,150,10 + PUSHBUTTON "&Close",IDCANCEL,60,92,50,14 +END + + ///////////////////////////////////////////////////////////////////////////// // // Version @@ -209,6 +226,14 @@ BEGIN IDD_SELECT_KEYCODES_DIALOG, DIALOG BEGIN + RIGHTMARGIN, 329 + BOTTOMMARGIN, 204 + END + + IDD_UPDATE_CHECK_DIALOG, DIALOG + BEGIN + RIGHTMARGIN, 169 + BOTTOMMARGIN, 114 END END #endif // APSTUDIO_INVOKED diff --git a/StayAwake/resource.h b/StayAwake/resource.h index 416a530..85881bc 100644 --- a/StayAwake/resource.h +++ b/StayAwake/resource.h @@ -7,6 +7,7 @@ #define IDS_ABOUTBOX 101 #define IDD_STAYAWAKE_DIALOG 102 #define IDD_SELECT_KEYCODES_DIALOG 103 +#define IDD_UPDATE_CHECK_DIALOG 104 #define IDR_MENU1 130 #define IDI_ICON1 133 #define IDR_MAINFRAME 133 @@ -58,12 +59,18 @@ #define IDC_KEY_SELECT_ALL_EXT_FN_BTN 1153 #define IDC_KEY_SELECT_ALL_BTN 1154 -#define IDC_ABOUT_NAME 1201 -#define IDC_ABOUT_VERSION 1202 -#define IDC_ABOUT_BUILD_TIME 1203 -#define IDC_ABOUT_ATTRIBUTION 1204 -#define IDC_ABOUT_PROD_URL 1205 -#define IDC_ABOUT_PROD_REQ 1206 +#define IDC_INSTALLED_VERSION_INFO 1201 +#define IDC_LATEST_VERSION_INFO 1202 +#define IDC_VERSION_CHECK_STATUS 1203 +#define IDC_LATEST_RELEASE_URL 1204 +#define IDC_START_VERSION_CHECK_CBX 1205 + +#define IDC_ABOUT_NAME 1301 +#define IDC_ABOUT_VERSION 1302 +#define IDC_ABOUT_BUILD_TIME 1303 +#define IDC_ABOUT_ATTRIBUTION 1304 +#define IDC_ABOUT_PROD_URL 1305 +#define IDC_ABOUT_PROD_REQ 1306 #define IDM_EXIT 32771 #define IDM_RESTORE 32772 From 8c468b3a247d64d6a20fadf15bb98ceb3fa71157 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:42:44 -0400 Subject: [PATCH 18/22] Implement functionality for Update Check dialog --- StayAwake/SelectKeyCodesDlg.h | 1 - StayAwake/StayAwake.vcxproj | 2 + StayAwake/StayAwake.vcxproj.filters | 6 +++ StayAwake/StayAwakeAboutDlg.cpp | 3 +- StayAwake/StayAwakeCore.h | 1 + StayAwake/StayAwakeDlg.cpp | 7 ++- StayAwake/UpdateCheckDlg.cpp | 76 +++++++++++++++++++++++++++++ StayAwake/UpdateCheckDlg.h | 34 +++++++++++++ 8 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 StayAwake/UpdateCheckDlg.cpp create mode 100644 StayAwake/UpdateCheckDlg.h diff --git a/StayAwake/SelectKeyCodesDlg.h b/StayAwake/SelectKeyCodesDlg.h index 4423745..65442e5 100644 --- a/StayAwake/SelectKeyCodesDlg.h +++ b/StayAwake/SelectKeyCodesDlg.h @@ -33,6 +33,5 @@ class CSelectKeyCodesDlg : public CDialogEx DECLARE_MESSAGE_MAP() -private: void CheckAllBoxes(const wstring& sSelectedKeyCodes, int start = IDC_KEY_SCROLL_LOCK, int endNext = IDC_KEY_SCROLL_LOCK + LEN_ROSTER_KEYCODES); }; diff --git a/StayAwake/StayAwake.vcxproj b/StayAwake/StayAwake.vcxproj index b0ce24e..cc452eb 100644 --- a/StayAwake/StayAwake.vcxproj +++ b/StayAwake/StayAwake.vcxproj @@ -222,6 +222,7 @@ + @@ -237,6 +238,7 @@ Create Create + diff --git a/StayAwake/StayAwake.vcxproj.filters b/StayAwake/StayAwake.vcxproj.filters index 7fa4b7b..d7fb28f 100644 --- a/StayAwake/StayAwake.vcxproj.filters +++ b/StayAwake/StayAwake.vcxproj.filters @@ -45,6 +45,9 @@ Header Files + + Header Files + @@ -71,6 +74,9 @@ Source Files + + Source Files + diff --git a/StayAwake/StayAwakeAboutDlg.cpp b/StayAwake/StayAwakeAboutDlg.cpp index 50c6094..c326c4e 100644 --- a/StayAwake/StayAwakeAboutDlg.cpp +++ b/StayAwake/StayAwakeAboutDlg.cpp @@ -46,7 +46,8 @@ BOOL CAboutDlg::OnInitDialog() afx_msg void CAboutDlg::OnProdUrlClick(NMHDR* pNotifyStruct, LRESULT* result) { - ShellExecute(NULL, L"open", Utils::getVersionInfo(theApp.m_hInstance, L"CompanyName").c_str(), NULL, NULL, SW_SHOW); + ShellExecute(nullptr, L"open", Utils::getVersionInfo(theApp.m_hInstance, L"CompanyName").c_str(), nullptr, nullptr, SW_SHOW); + *result = 0; } diff --git a/StayAwake/StayAwakeCore.h b/StayAwake/StayAwakeCore.h index 408d450..4d74024 100644 --- a/StayAwake/StayAwakeCore.h +++ b/StayAwake/StayAwakeCore.h @@ -27,6 +27,7 @@ constexpr auto PREF_MULTI_INSTANCE = L"MultipleInstancesAllowed"; constexpr auto PREF_INTERVAL_LEGACY = L"TimerIntervalInSeconds"; constexpr auto PREF_INTERVAL_MINIMUM = L"MinimumIntervalInSeconds"; constexpr auto PREF_INTERVAL_MAXIMUM = L"MaximumIntervalInSeconds"; +constexpr auto PREF_START_UPDATE_CHECK = L"StartCheckUpdates"; constexpr auto MIN_PERIOD{ 10 }; constexpr auto MAX_PERIOD{ 9990 }; diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index ec792de..4cf6354 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -6,7 +6,7 @@ #include "StayAwakeDlg.h" #include "SelectKeyCodesDlg.h" #include "StayAwakeAboutDlg.h" -#include "VersionUpdate.h" +#include "UpdateCheckDlg.h" #include "Utils.h" #include @@ -135,6 +135,9 @@ afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) else InitAwakes(); + if (m_AwakeCore.GetPreference(PREF_START_UPDATE_CHECK, L"N") == L"Y") + OnUpdateCheckClicked(); + BOOL bMinimized = (m_AwakeCore.GetPreference(PREF_START_MINIMIZED, L"N") == L"Y"); CheckDlgButton(IDC_START_MINIMIZED_CBX, bMinimized ? BST_CHECKED : BST_UNCHECKED); if (bMinimized) MinimizeToTray(); @@ -437,4 +440,6 @@ void CStayAwakeDlg::OnSessionChange(UINT nSessionState, UINT nId) void CStayAwakeDlg::OnUpdateCheckClicked() { + CUpdateCheckDlg dlgUpdateCheck(this, m_AwakeCore); + dlgUpdateCheck.DoModal(); } diff --git a/StayAwake/UpdateCheckDlg.cpp b/StayAwake/UpdateCheckDlg.cpp new file mode 100644 index 0000000..819844a --- /dev/null +++ b/StayAwake/UpdateCheckDlg.cpp @@ -0,0 +1,76 @@ +#include "pch.h" + +#include "afxdialogex.h" +#include "StayAwake.h" +#include "UpdateCheckDlg.h" +#include "Utils.h" +#include "VersionUpdate.h" + + +// CUpdateCheckDlg dialog + +IMPLEMENT_DYNAMIC(CUpdateCheckDlg, CDialogEx) + +CUpdateCheckDlg::CUpdateCheckDlg(CWnd* pParent, StayAwakeCore& pAwakeCore) + : CDialogEx(IDD_UPDATE_CHECK_DIALOG, pParent) + , m_AwakeCore(pAwakeCore) +{ + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); +} + +CUpdateCheckDlg::~CUpdateCheckDlg() +{ +} + +void CUpdateCheckDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialogEx::DoDataExchange(pDX); +} + + +BEGIN_MESSAGE_MAP(CUpdateCheckDlg, CDialogEx) + ON_NOTIFY(NM_CLICK, IDC_LATEST_RELEASE_URL, &CUpdateCheckDlg::OnClickLatestReleaseUrl) + ON_BN_CLICKED(IDC_START_VERSION_CHECK_CBX, &CUpdateCheckDlg::OnClickedStartVersionCheck) + ON_NOTIFY(NM_RETURN, IDC_LATEST_RELEASE_URL, &CUpdateCheckDlg::OnReturnLatestReleaseUrl) +END_MESSAGE_MAP() + + +// CUpdateCheckDlg message handlers + +BOOL CUpdateCheckDlg::OnInitDialog() +{ + CDialogEx::OnInitDialog(); + + wstring currentVersionInfo{ Utils::getVersionInfo(theApp.m_hInstance, L"FileVersion") }; + + VersionUpdate versionUpdate; + wstring latestVersionInfo{ versionUpdate.GetVersion() }; + + SetDlgItemText(IDC_INSTALLED_VERSION_INFO, currentVersionInfo.c_str()); + SetDlgItemText(IDC_LATEST_VERSION_INFO, latestVersionInfo.c_str()); + SetDlgItemText(IDC_VERSION_CHECK_STATUS, + (latestVersionInfo == currentVersionInfo) ? + L"This is the latest available version." : + L"A newer version is available. Click the link below."); + + BOOL bStartUpdateCheck = (m_AwakeCore.GetPreference(PREF_START_UPDATE_CHECK, L"N") == L"Y"); + CheckDlgButton(IDC_START_VERSION_CHECK_CBX, bStartUpdateCheck ? BST_CHECKED : BST_UNCHECKED); + + return TRUE; // return TRUE unless you set the focus to a control +} + +void CUpdateCheckDlg::OnClickLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult) +{ + ShellExecute(nullptr, L"open", L"https://github.com/shriprem/StayAwake/releases/latest", nullptr, nullptr, SW_SHOW); + *pResult = 0; +} + +void CUpdateCheckDlg::OnReturnLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult) +{ + OnClickLatestReleaseUrl(pNMHDR, pResult); +} + +void CUpdateCheckDlg::OnClickedStartVersionCheck() +{ + m_AwakeCore.SetPreference(PREF_START_UPDATE_CHECK, (IsDlgButtonChecked(IDC_START_VERSION_CHECK_CBX) == BST_CHECKED) ? L"Y" : L"N"); +} diff --git a/StayAwake/UpdateCheckDlg.h b/StayAwake/UpdateCheckDlg.h new file mode 100644 index 0000000..428d7bc --- /dev/null +++ b/StayAwake/UpdateCheckDlg.h @@ -0,0 +1,34 @@ +#pragma once + +#include "afxdialogex.h" +#include "StayAwakeDlg.h" + + +// CUpdateCheckDlg dialog + +class CUpdateCheckDlg : public CDialogEx +{ + DECLARE_DYNAMIC(CUpdateCheckDlg) + +public: + explicit CUpdateCheckDlg(CWnd* pParent, StayAwakeCore& pAwakeCore); + virtual ~CUpdateCheckDlg(); + +// Dialog Data +#ifdef AFX_DESIGN_TIME + enum { IDD = IDD_UPDATE_CHECK_DIALOG }; +#endif + +protected: + StayAwakeCore m_AwakeCore{}; + HICON m_hIcon; + + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + virtual BOOL OnInitDialog(); + afx_msg void OnClickLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnClickedStartVersionCheck(); + afx_msg void OnReturnLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult); + + DECLARE_MESSAGE_MAP() +}; From d504cb104b21d9d9e8741f2f84566234a5e2f49a Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:17:59 -0400 Subject: [PATCH 19/22] Split InputOptions.md from README.md --- InputOptions.md | 25 +++++++++++++++++++++++++ README.md | 31 ++----------------------------- 2 files changed, 27 insertions(+), 29 deletions(-) create mode 100644 InputOptions.md diff --git a/InputOptions.md b/InputOptions.md new file mode 100644 index 0000000..064c434 --- /dev/null +++ b/InputOptions.md @@ -0,0 +1,25 @@ +## Input Simulation Options + +![StayAwake UI](images/StayAwakeKeySelections.png) + +StayAwake can randomly pick any of the 12 key codes during each simulation. In this popup, users can specify _at least_ one OR a select few OR all of the 12 key codes to be included in the simulation roster. + +#### Scroll Lock cycling +Scroll Lock toggling will work just fine for most users. However, some users using Remote Desktop alongside have reported spurious keystrokes being transmitted between the connected Windows devices. Users in such situations should explore other key simulation options. + +#### Volume Up & Down +With this option, StayAwake will simulate a _Volume Down_ key press, immediately followed by a _Volume Up_ key press, thereby resulting in no net change in volume level. + +A minor benefit with this option is the visual feedback. Each StayAwake key simulation event will cause the Windows Volume Slider bar to display for a few seconds above the Windows Taskbar. + +However, if a user has muted the audio, then this option will unmute it. Users wishing to avoid this side effect should explore other key simulation options. + +#### Unassigned Key (1 to 10) +With these options, StayWake will simulate key press of an unassigned keycode. + +These unassigned keycodes have been obtained from this Microsoft article on [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). + +Microsoft may start utilizing any of these unassigned codes in the future -- but not all of them in one go. Hence all 10 of the currently unassigned codes have been included in the [Select multiple Key Codes](#select-multiple-key-codes) popup so that users can select other still available unassigned keycodes. + +#### Optional Tip + You can verify StayAwake in action by *temporarily* enabling only the [Volume Up & Down](#volume-up--down) key code and setting both *Minimum Awake Seconds* and *Maximum Awake Seconds* fields to 10 seconds. diff --git a/README.md b/README.md index 0dabb5b..c0e7410 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ StayAwake executable is now digitally signed by the kind folks at [OSSign](https ![StayAwake UI](images/StayAwakeApp.png) #### Select multiple Key Codes for random simulation -StayAwake can randomly pick any of the 12 key codes during each simulation. Users can specify _at least_ one OR a select few OR all of the 12 key codes to be included in the simulation roster. See the [Select multiple Key Codes](#select-multiple-key-codes) section below for more info. +StayAwake can randomly pick any of the 12 key codes during each simulation. Users can specify _at least_ one OR a select few OR all of the 12 key codes to be included in the simulation roster. See the [Select multiple Key Codes](https://github.com/shriprem/StayAwake/blob/Version2.0/InputOptions.md) section below for more info. #### Minimum Awake Seconds This field allows you to specify the minimum interval between key simulations. The initial default value for this is 240 seconds (_i.e._, 4 minutes). However, you can change this to any value between 10 and 9990 seconds. Your new value will be saved and used the next time you run StayAwake. @@ -67,33 +67,6 @@ Check the _Launch application in minimized state_ box to have StayAwake automati When the _Start Minimized_ option is enabled, StayAwake will launch and minimize immediately only on the first launch attempt. If StayAwake is still running in a minimized state, trying to launch the application again will restore the StayAwake dialog from the minimized state. This is by design when `MultipleInstancesAllowed=N`. See under: [Configuration File Details](#configuration-file-details) -### Select multiple Key Codes - -![StayAwake UI](images/StayAwakeKeySelections.png) - -StayAwake can randomly pick any of the 12 key codes during each simulation. In this popup, users can specify _at least_ one OR a select few OR all of the 12 key codes to be included in the simulation roster. - -#### Scroll Lock cycling -Scroll Lock toggling will work just fine for most users. However, some users using Remote Desktop alongside have reported spurious keystrokes being transmitted between the connected Windows devices. Users in such situations should explore other key simulation options. - -#### Volume Up & Down -With this option, StayAwake will simulate a _Volume Down_ key press, immediately followed by a _Volume Up_ key press, thereby resulting in no net change in volume level. - -A minor benefit with this option is the visual feedback. Each StayAwake key simulation event will cause the Windows Volume Slider bar to display for a few seconds above the Windows Taskbar. - -However, if a user has muted the audio, then this option will unmute it. Users wishing to avoid this side effect should explore other key simulation options. - -#### Unassigned Key (1 to 10) -With these options, StayWake will simulate key press of an unassigned keycode. - -These unassigned keycodes have been obtained from this Microsoft article on [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). - -Microsoft may start utilizing any of these unassigned codes in the future -- but not all of them in one go. Hence all 10 of the currently unassigned codes have been included in the [Select multiple Key Codes](#select-multiple-key-codes) popup so that users can select other still available unassigned keycodes. - -#### Optional Tip - You can verify StayAwake in action by *temporarily* enabling only the [Volume Up & Down](#volume-up--down) key code and setting both *Minimum Awake Seconds* and *Maximum Awake Seconds* fields to 10 seconds. - - ### System Tray Icon ![StayAwake UI](images/SystemTray.png) @@ -107,7 +80,7 @@ When you minimize StayAwake, it will be represented by the System Tray icon show ## Configuration File Details StayAwake saves its configuration in a file named `StayAwake.ini`. This file is co-located in the `StayAwake.exe` application file folder. These key-value pairs are stored in this file: -1. `SelectedKeyCodes`: This key stores a string of 12 characters -- each character indicating if a key code in the [Select multiple Key Codes](#select-multiple-key-codes) popup is selected or not with either a `1` or `0`, respectively. +1. `SelectedKeyCodes`: This key stores a string of 12 characters -- each character indicating if a key code in the [Select multiple Key Codes](https://github.com/shriprem/StayAwake/blob/Version2.0/InputOptions.md) popup is selected or not with either a `1` or `0`, respectively. 2. `MinimumIntervalInSeconds`: This key stores the minimum number of seconds between each StayAwake Key simulation as specified in the [Minimum Awake Seconds](#minimum-awake-seconds) field. From bf7275386946796affac782ff99f118c15894ab2 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:40:29 -0400 Subject: [PATCH 20/22] Add link to InputOptions.md in SelectKeyCodes dialog --- StayAwake/SelectKeyCodesDlg.cpp | 9 +++++++++ StayAwake/SelectKeyCodesDlg.h | 1 + StayAwake/StayAwake.rc | 8 +++++--- StayAwake/StayAwake.vcxproj | 2 ++ StayAwake/StayAwake.vcxproj.filters | 6 ++++++ StayAwake/res/information.bmp | Bin 0 -> 1078 bytes StayAwake/resource.h | 3 +++ 7 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 StayAwake/res/information.bmp diff --git a/StayAwake/SelectKeyCodesDlg.cpp b/StayAwake/SelectKeyCodesDlg.cpp index ac1efce..59fdd89 100644 --- a/StayAwake/SelectKeyCodesDlg.cpp +++ b/StayAwake/SelectKeyCodesDlg.cpp @@ -32,6 +32,7 @@ BEGIN_MESSAGE_MAP(CSelectKeyCodesDlg, CDialogEx) ON_BN_CLICKED(IDC_KEY_SELECT_NONE_BTN, &CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn) ON_BN_CLICKED(IDC_KEY_SELECT_ALL_UNASSGND_BTN, &CSelectKeyCodesDlg::OnClickedSelectAllUnassignedKeys) ON_BN_CLICKED(IDC_KEY_SELECT_ALL_EXT_FN_BTN, &CSelectKeyCodesDlg::OnClickedSelectAllExtFnKeys) + ON_BN_CLICKED(IDC_INPUT_OPTIONS_INFO_BTN, &CSelectKeyCodesDlg::OnClickedInputOptionsInfoBtn) END_MESSAGE_MAP() @@ -42,6 +43,9 @@ BOOL CSelectKeyCodesDlg::OnInitDialog() SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon + Utils::addTooltip(theApp.m_hInstance, m_hWnd, IDC_INPUT_OPTIONS_INFO_BTN, L"", L"View Readme Online", 3, TRUE); + Utils::loadBitmap(theApp.m_hInstance, m_hWnd, IDC_INPUT_OPTIONS_INFO_BTN, IDB_INFO_BITMAP); + CheckAllBoxes(m_AwakeCore.GetSelectedKeyCodes()); return TRUE; // return TRUE unless you set the focus to a control @@ -95,3 +99,8 @@ void CSelectKeyCodesDlg::OnClickedSelectAllExtFnKeys() CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1'), IDC_KEY_F13, IDC_MOUSE_MOVE); } + +void CSelectKeyCodesDlg::OnClickedInputOptionsInfoBtn() +{ + ShellExecute(nullptr, L"open", L"https://github.com/shriprem/StayAwake/blob/Version2.0/InputOptions.md", nullptr, nullptr, SW_SHOW); +} diff --git a/StayAwake/SelectKeyCodesDlg.h b/StayAwake/SelectKeyCodesDlg.h index 65442e5..896c125 100644 --- a/StayAwake/SelectKeyCodesDlg.h +++ b/StayAwake/SelectKeyCodesDlg.h @@ -29,6 +29,7 @@ class CSelectKeyCodesDlg : public CDialogEx afx_msg void OnClickedKeySelectNoneBtn(); afx_msg void OnClickedSelectAllUnassignedKeys(); afx_msg void OnClickedSelectAllExtFnKeys(); + afx_msg void OnClickedInputOptionsInfoBtn(); afx_msg void OnOK(); DECLARE_MESSAGE_MAP() diff --git a/StayAwake/StayAwake.rc b/StayAwake/StayAwake.rc index 115ba37..b5472a5 100644 --- a/StayAwake/StayAwake.rc +++ b/StayAwake/StayAwake.rc @@ -29,12 +29,12 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // TEXTINCLUDE // -1 TEXTINCLUDE +1 TEXTINCLUDE BEGIN "resource.h\0" END -2 TEXTINCLUDE +2 TEXTINCLUDE BEGIN "#ifndef APSTUDIO_INVOKED\r\n" "#include ""targetver.h""\r\n" @@ -44,7 +44,7 @@ BEGIN "\0" END -3 TEXTINCLUDE +3 TEXTINCLUDE BEGIN "#define _AFX_NO_SPLITTER_RESOURCES\r\n" "#define _AFX_NO_OLE_RESOURCES\r\n" @@ -145,6 +145,7 @@ BEGIN PUSHBUTTON "Select &All",IDC_KEY_SELECT_ALL_BTN,225,130,85,14 DEFPUSHBUTTON "A&pply",IDOK,225,155,85,14 PUSHBUTTON "&Cancel",IDCANCEL,225,180,85,14 + PUSHBUTTON "...",IDC_INPUT_OPTIONS_INFO_BTN,312,2,15,13,BS_BITMAP END IDD_UPDATE_CHECK_DIALOG DIALOGEX 0, 0, 170, 115 @@ -286,6 +287,7 @@ IDR_MAINFRAME ICON "res\\StayAwake.ico" // IDB_ABOUT_BITMAP BITMAP "res\\about.bmp" +IDB_INFO_BITMAP BITMAP "res\\information.bmp" ///////////////////////////////////////////////////////////////////////////// diff --git a/StayAwake/StayAwake.vcxproj b/StayAwake/StayAwake.vcxproj index cc452eb..9598b0a 100644 --- a/StayAwake/StayAwake.vcxproj +++ b/StayAwake/StayAwake.vcxproj @@ -216,6 +216,7 @@ + @@ -250,6 +251,7 @@ + diff --git a/StayAwake/StayAwake.vcxproj.filters b/StayAwake/StayAwake.vcxproj.filters index d7fb28f..d0f8204 100644 --- a/StayAwake/StayAwake.vcxproj.filters +++ b/StayAwake/StayAwake.vcxproj.filters @@ -48,6 +48,9 @@ Header Files + + Header Files + @@ -95,5 +98,8 @@ Resource Files + + Resource Files + \ No newline at end of file diff --git a/StayAwake/res/information.bmp b/StayAwake/res/information.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0fe3f416a252b650c9950c340cd9d0025ebc440f GIT binary patch literal 1078 zcmbu3T}YE*6vyYgDUk|YWKDxu%rDkt8gmTGkJg84kw|_uyU?wD1%ZRLBxDk4f#0-hl}!FI_o8iO9mDHyT2)oV8$NKaEI+@*DGR9H(HuM zy=YYNiM~ci^fka-cMSzIve2vKN2TkhaW~t&zJ)6u*|yFZGN}0@|@_f8#lLdHg|JbICxAAf!$lbj`cvquz^sdhQ|{eF4_GwWl0+bT<d*qxLPW~ z=kw7Un6bz3bSTp3F#m2D=L!$%Q(ntnm@zhiyS)a+4wQq}>!mlIYRkZ(EB_0br_f>k z{R(b0JkYr^cf1)G?@bzbq%DUB`=szhR{;G5NZFqX&1hU6!2+q1LS~d=|9r z4#h586a2MPDgLTG6&toB0+z-Q&TxnAuT@JojAy`X!r5BR|MdO Date: Mon, 5 Oct 2026 16:11:55 -0400 Subject: [PATCH 21/22] Start Auto Update Check: Show dialog only if newer version found --- StayAwake/StayAwakeAboutDlg.cpp | 2 +- StayAwake/StayAwakeDlg.cpp | 7 ++++++- StayAwake/UpdateCheckDlg.cpp | 27 ++++++++++++++++++--------- StayAwake/UpdateCheckDlg.h | 26 ++++++++++++++------------ StayAwake/Utils.cpp | 4 ++++ StayAwake/Utils.h | 1 + 6 files changed, 44 insertions(+), 23 deletions(-) diff --git a/StayAwake/StayAwakeAboutDlg.cpp b/StayAwake/StayAwakeAboutDlg.cpp index c326c4e..ae910b8 100644 --- a/StayAwake/StayAwakeAboutDlg.cpp +++ b/StayAwake/StayAwakeAboutDlg.cpp @@ -36,7 +36,7 @@ BOOL CAboutDlg::OnInitDialog() #endif // _WIN64 SetDlgItemText(IDC_ABOUT_NAME, Utils::getVersionInfo(theApp.m_hInstance, L"FileDescription").c_str()); - SetDlgItemText(IDC_ABOUT_VERSION, (L"Version: " + Utils::getVersionInfo(theApp.m_hInstance, L"FileVersion") + buildBit).c_str()); + SetDlgItemText(IDC_ABOUT_VERSION, (L"Version: " + Utils::getAppVersion(theApp.m_hInstance) + buildBit).c_str()); SetDlgItemText(IDC_ABOUT_BUILD_TIME, (wstring{ L"Last code update: " } + LAST_CODE_UPDATE_TIME).c_str()); SetDlgItemText(IDC_ABOUT_ATTRIBUTION, Utils::getVersionInfo(theApp.m_hInstance, L"LegalCopyright").c_str()); diff --git a/StayAwake/StayAwakeDlg.cpp b/StayAwake/StayAwakeDlg.cpp index 4cf6354..cc74899 100644 --- a/StayAwake/StayAwakeDlg.cpp +++ b/StayAwake/StayAwakeDlg.cpp @@ -136,7 +136,12 @@ afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) InitAwakes(); if (m_AwakeCore.GetPreference(PREF_START_UPDATE_CHECK, L"N") == L"Y") - OnUpdateCheckClicked(); + { + wstring currentVersion{}, latestVersion{}; + bool bNewer = CUpdateCheckDlg::IsNewerVersionAvailable(currentVersion, latestVersion); + + if (bNewer) OnUpdateCheckClicked(); + } BOOL bMinimized = (m_AwakeCore.GetPreference(PREF_START_MINIMIZED, L"N") == L"Y"); CheckDlgButton(IDC_START_MINIMIZED_CBX, bMinimized ? BST_CHECKED : BST_UNCHECKED); diff --git a/StayAwake/UpdateCheckDlg.cpp b/StayAwake/UpdateCheckDlg.cpp index 819844a..06bf3bc 100644 --- a/StayAwake/UpdateCheckDlg.cpp +++ b/StayAwake/UpdateCheckDlg.cpp @@ -22,6 +22,19 @@ CUpdateCheckDlg::~CUpdateCheckDlg() { } +bool CUpdateCheckDlg::IsNewerVersionAvailable(wstring& currentVersion, wstring& latestVersion) +{ + currentVersion = Utils::getAppVersion(theApp.m_hInstance); + + VersionUpdate versionUpdate; + latestVersion = versionUpdate.GetVersion(); + + if (latestVersion.empty() || currentVersion.empty()) + return false; + + return (latestVersion != currentVersion); +} + void CUpdateCheckDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); @@ -41,17 +54,13 @@ BOOL CUpdateCheckDlg::OnInitDialog() { CDialogEx::OnInitDialog(); - wstring currentVersionInfo{ Utils::getVersionInfo(theApp.m_hInstance, L"FileVersion") }; - - VersionUpdate versionUpdate; - wstring latestVersionInfo{ versionUpdate.GetVersion() }; + wstring currentVersion{}, latestVersion{}; + bool bNewer = IsNewerVersionAvailable(currentVersion, latestVersion); - SetDlgItemText(IDC_INSTALLED_VERSION_INFO, currentVersionInfo.c_str()); - SetDlgItemText(IDC_LATEST_VERSION_INFO, latestVersionInfo.c_str()); + SetDlgItemText(IDC_INSTALLED_VERSION_INFO, currentVersion.c_str()); + SetDlgItemText(IDC_LATEST_VERSION_INFO, latestVersion.c_str()); SetDlgItemText(IDC_VERSION_CHECK_STATUS, - (latestVersionInfo == currentVersionInfo) ? - L"This is the latest available version." : - L"A newer version is available. Click the link below."); + bNewer ? L"A newer version is available. Click the link below." : L"This is the latest available version."); BOOL bStartUpdateCheck = (m_AwakeCore.GetPreference(PREF_START_UPDATE_CHECK, L"N") == L"Y"); CheckDlgButton(IDC_START_VERSION_CHECK_CBX, bStartUpdateCheck ? BST_CHECKED : BST_UNCHECKED); diff --git a/StayAwake/UpdateCheckDlg.h b/StayAwake/UpdateCheckDlg.h index 428d7bc..5a23049 100644 --- a/StayAwake/UpdateCheckDlg.h +++ b/StayAwake/UpdateCheckDlg.h @@ -8,27 +8,29 @@ class CUpdateCheckDlg : public CDialogEx { - DECLARE_DYNAMIC(CUpdateCheckDlg) + DECLARE_DYNAMIC(CUpdateCheckDlg) public: - explicit CUpdateCheckDlg(CWnd* pParent, StayAwakeCore& pAwakeCore); - virtual ~CUpdateCheckDlg(); + explicit CUpdateCheckDlg(CWnd* pParent, StayAwakeCore& pAwakeCore); + virtual ~CUpdateCheckDlg(); + + static bool IsNewerVersionAvailable(wstring& currentVersion, wstring& latestVersion); // Dialog Data #ifdef AFX_DESIGN_TIME - enum { IDD = IDD_UPDATE_CHECK_DIALOG }; + enum { IDD = IDD_UPDATE_CHECK_DIALOG }; #endif protected: - StayAwakeCore m_AwakeCore{}; - HICON m_hIcon; + StayAwakeCore m_AwakeCore{}; + HICON m_hIcon; - virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support - virtual BOOL OnInitDialog(); - afx_msg void OnClickLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult); - afx_msg void OnClickedStartVersionCheck(); - afx_msg void OnReturnLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult); + virtual BOOL OnInitDialog(); + afx_msg void OnClickLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnClickedStartVersionCheck(); + afx_msg void OnReturnLatestReleaseUrl(NMHDR* pNMHDR, LRESULT* pResult); - DECLARE_MESSAGE_MAP() + DECLARE_MESSAGE_MAP() }; diff --git a/StayAwake/Utils.cpp b/StayAwake/Utils.cpp index a48ef2f..05f8ffa 100644 --- a/StayAwake/Utils.cpp +++ b/StayAwake/Utils.cpp @@ -145,3 +145,7 @@ wstring Utils::getVersionInfo(HINSTANCE hApp, LPCWSTR key) { return sVersionInfo; } + +wstring Utils::getAppVersion(HINSTANCE hApp) { + return getVersionInfo(hApp, L"FileVersion"); +} diff --git a/StayAwake/Utils.h b/StayAwake/Utils.h index e09f425..e4dff46 100644 --- a/StayAwake/Utils.h +++ b/StayAwake/Utils.h @@ -23,4 +23,5 @@ namespace Utils { int getProcessRunCount(wstring sBaseName); wstring getVersionInfo(HINSTANCE hApp, LPCWSTR key); + wstring getAppVersion(HINSTANCE hApp); } From 53e01369dad3e6fd6d38a15876b00a1104978722 Mon Sep 17 00:00:00 2001 From: Shridhar Kumar <45252729+shriprem@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:12:54 -0400 Subject: [PATCH 22/22] Updating images and documentation for v1.5.0.0 Release --- InputOptions.md | 44 +++++++++++++++++++---- README.md | 57 ++++++++++++++++++++++++------ VersionHistory.md | 18 ++++++++++ images/CheckForUpdates.png | Bin 0 -> 4352 bytes images/StayAwakeApp.png | Bin 7491 -> 7720 bytes images/StayAwakeInputOptions.png | Bin 0 -> 16224 bytes images/StayAwakeKeySelections.png | Bin 6199 -> 0 bytes images/StayAwakePaused.png | Bin 7548 -> 7763 bytes images/StayAwakePausedLaunch.png | Bin 6942 -> 7170 bytes 9 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 images/CheckForUpdates.png create mode 100644 images/StayAwakeInputOptions.png delete mode 100644 images/StayAwakeKeySelections.png diff --git a/InputOptions.md b/InputOptions.md index 064c434..a5642ab 100644 --- a/InputOptions.md +++ b/InputOptions.md @@ -1,8 +1,12 @@ ## Input Simulation Options -![StayAwake UI](images/StayAwakeKeySelections.png) +![Input Options UI](images/StayAwakeInputOptions.png) -StayAwake can randomly pick any of the 12 key codes during each simulation. In this popup, users can specify _at least_ one OR a select few OR all of the 12 key codes to be included in the simulation roster. +StayAwake can randomly pick any of the 26 inputs (24 key codes & 2 mouse moves) during each simulation. In this popup, users can specify _at least_ one OR a select few OR all of the 26 inputs to be included in the simulation roster. + +StayAwake simulates events using the `SendInput` API instead of the deprecated `keybd_event` API. `SendInput` API allows batching of multiple key or mouse events in a single atomic call to the Windows OS, thus yielding jitter-free key or mouse simulations. + +As can be noted from the screenshot, [Zero-pixel Mouse Move](#zero-pixel-mouse-move) and [One-pixel Mouse Jiggle](#one-pixel-mouse-jiggle) are my two preferred input options. #### Scroll Lock cycling Scroll Lock toggling will work just fine for most users. However, some users using Remote Desktop alongside have reported spurious keystrokes being transmitted between the connected Windows devices. Users in such situations should explore other key simulation options. @@ -10,16 +14,42 @@ Scroll Lock toggling will work just fine for most users. However, some users usi #### Volume Up & Down With this option, StayAwake will simulate a _Volume Down_ key press, immediately followed by a _Volume Up_ key press, thereby resulting in no net change in volume level. -A minor benefit with this option is the visual feedback. Each StayAwake key simulation event will cause the Windows Volume Slider bar to display for a few seconds above the Windows Taskbar. +Volume Up & Down simulation will preserve the muted sound state. Prior to each Volume Up & Down simulation event, StayAwake will detect if the sound is in a muted state or not. If the sound is found muted, Volume Mute key will also be added after the Volume Down & Up keys in the `SendInput` API call. Since all these key events are sent in a single atomic API call, any sound jitter that may still occur will be the briefest possible. + +A minor benefit with this option is the visual feedback. Each StayAwake key simulation event will cause the Windows Volume Slider flyout/toast OSD (on-screen display) to show for a few seconds. While this input option is useful for debugging or verification of StayAwake functionality, it will get too distracting over long term. -However, if a user has muted the audio, then this option will unmute it. Users wishing to avoid this side effect should explore other key simulation options. +**Tip**: You can verify StayAwake in action by *temporarily* enabling only the *Volume Up & Down* key code and setting both *Minimum Awake Seconds* and *Maximum Awake Seconds* fields to 10. #### Unassigned Key (1 to 10) With these options, StayWake will simulate key press of an unassigned keycode. These unassigned keycodes have been obtained from this Microsoft article on [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). -Microsoft may start utilizing any of these unassigned codes in the future -- but not all of them in one go. Hence all 10 of the currently unassigned codes have been included in the [Select multiple Key Codes](#select-multiple-key-codes) popup so that users can select other still available unassigned keycodes. +Microsoft may start utilizing any of these unassigned codes in the future -- but not all of them in one go. Hence all 10 of the currently unassigned codes have been included in the [Input Simulation Options](#input-simulation-options) popup so that users can select other still available unassigned keycodes. + +See also: [Google Sheets clears active cell content on unassigned Windows virtual-key events](https://github.com/shriprem/StayAwake/discussions/21) + +#### Extended Function Keys (F13 to F24) +These 12 options are safe to use since most applications do not bind F13–F24 keys to any action. However, there are some rare exceptions such as: +* Some DAWs (digital audio workstations) +* Some macro software +* Some enterprise kiosk systems +* Some accessibility tools + +See also: [Google Sheets clears active cell content on unassigned Windows virtual-key events](https://github.com/shriprem/StayAwake/discussions/21) + +#### One-pixel Mouse Jiggle +With this option, StayAwake will simulate a one-pixel mouse jiggle. The mouse pointer will move one pixel diagonally right & down, and then back to its original position. This one-pixel jiggle is jitter-free since the two mouse moves are batched into a single atomic `SendInput` API call. So, the mouse pointer will not seem to move at all with this option. + +#### Zero-pixel Mouse Move +With this option, StayAwake will simulate a zero-pixel mouse move. The mouse pointer will not move at all. At least on the few Windows 11 devices that I have tested, this option was found to be remarkably effective. If this option does not work on your Windows device, use the [One-pixel Mouse Jiggle](#one-pixel-mouse-jiggle) option instead. + + +#### Buttons +* _Select None_ button will deselect all 26 input options. +* _Select All Unassigned_ button will select all 10 Unassigned Key options. +* _Select All Ext. FN Keys_ button will select all 12 Extended Function Key options. +* _Select All_ button will select all 26 input options. +* _Apply_ will save the selected input options and close the popup. +* _Cancel_ will close the popup without saving any changes. -#### Optional Tip - You can verify StayAwake in action by *temporarily* enabling only the [Volume Up & Down](#volume-up--down) key code and setting both *Minimum Awake Seconds* and *Maximum Awake Seconds* fields to 10 seconds. diff --git a/README.md b/README.md index c0e7410..69323e5 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,37 @@       +![StayAwake UI](images/StayAwakeApp.png)   ![Input Options UI](images/StayAwakeInputOptions.png) -[Current Version: 1.3.0.0](https://github.com/shriprem/StayAwake/blob/main/VersionHistory.md) +[Current Version: 1.5.0.0](VersionHistory.md) StayAwake is a simple utility that enables you to maintain an _Active_ status on Microsoft Teams (and perhaps other messaging applications such as Slack and Zoom). StayAwake also prevents screen saver activation, screen blanking, and Windows session lockouts. +## Features at a glance +* Randomized simulation with mouse or key inputs. Roster with a choice of 24 no/low side-effect keys and 2 invisible mouse moves. +* Random interval simulation. Intervals within a specified range of seconds. +* Helpful status information with timestamps of Last and Next simulations. +* Minimization to System Tray. +* Start Minimized option. +* Manual Pause feature when needed. +* Automatic Pause while Windows in locked state. Reduces disk activity and CPU usage. Conserves battery power. +* Small, portable application. Runs with no installation or administrator privileges. +* Digitally signed application executable file. +* Update Check feature - Manual or automatic (opt-in). + If you are using Microsoft Teams, you may have noticed that Teams automatically sets your status to _Away_ after a certain period of inactivity. This can be frustrating if you are still at your desk but perhaps on a business phone call _OR_ outlining a plan on paper _OR_ consulting some reference material. -StayAwake can be minimized to the Windows System Tray, and will continue to run in the background until you click the _Exit_ button. StayAwake also has the option to automatically minimize to Windows System Tray upon launch. See: [Start Minimized](#start-minimized) +StayAwake simulates a randomly selected input from a specified roster of jitter-free mouse moves and no/low side-effect keys. Additionally, these simulations are done at random intervals within a specified range of seconds. This is a safe and non-intrusive way to keep your Microsoft Teams status _Active_. The combined randomness of inputs and time intervals eliminates predictable patterns, making StayAwake simulations seem organic. -StayAwake does its job by simulating key presses of unused or low side-effect buttons at specified time intervals. This is a safe and non-intrusive way to keep your Microsoft Teams status _Active_. +StayAwake can be minimized to the Windows System Tray, and will continue to run in the background until you click the _Exit_ button. StayAwake also has the option to automatically minimize to Windows System Tray upon launch. See: [Start Minimized](#start-minimized) StayAwake executable is now digitally signed by the kind folks at [OSSign](https://www.ossign.org). Thanks to the code signing, there is *NO* need for users to override the Windows Defender Smartscreen or [unblock the executable file](https://stackoverflow.com/questions/48946680/how-to-avoid-the-windows-defender-smartscreen-prevented-an-unrecognized-app-fro) to launch StayAwake. ## User Interface ![StayAwake UI](images/StayAwakeApp.png) -#### Select multiple Key Codes for random simulation -StayAwake can randomly pick any of the 12 key codes during each simulation. Users can specify _at least_ one OR a select few OR all of the 12 key codes to be included in the simulation roster. See the [Select multiple Key Codes](https://github.com/shriprem/StayAwake/blob/Version2.0/InputOptions.md) section below for more info. +#### Select multiple options for random simulation +StayAwake can randomly pick any of the 26 inputs (24 key codes & 2 mouse moves) during each simulation. Users can specify _at least_ one OR a select few OR all of the 26 inputs to be included in the simulation roster. See [Input Simulation Options](InputOptions.md) for more info. #### Minimum Awake Seconds This field allows you to specify the minimum interval between key simulations. The initial default value for this is 240 seconds (_i.e._, 4 minutes). However, you can change this to any value between 10 and 9990 seconds. Your new value will be saved and used the next time you run StayAwake. @@ -35,12 +48,14 @@ This field allows you to specify the minimum interval between key simulations. T #### Maximum Awake Seconds This field allows you to specify the maximum interval between key simulations. The initial default value for this is 240 seconds (_i.e._, 4 minutes). However, you can change this to any value between 10 and 9990 seconds. Your new value will be saved and used the next time you run StayAwake. +**Tip**: If Microsoft Teams is changing your status to _Away_ after 5 minutes (i.e., 300 seconds) of inactivity, consider setting the *Minimum Awake Seconds* to around 200 and the *Maximum Awake Seconds* to 310 or 320 or even 330 (i.e., a little over 300). This weighted range will cause your Teams status to briefly drift out of _Active_ status once in a while -- a more normal user behavior from your coworkers' perspective than a solid green status through an entire work day. + #### Set Timer button After changing the value for either *Minimum Awake Seconds* or *Maximum Awake Seconds*, click this button to apply the new setting. Clicking this button will also immediately invoke an Awake event. After every Awake event, a random number of seconds (between the specified minimum and maximum seconds) will be picked to run the next Awake event. -The clock times of the last and next Awake events will be displayed and continually refreshed on the StayAwake window. The randomly picked key code used for the last Awake will also be displayed below the last Awake time. +The clock times of the last and next Awake events will be displayed and continually refreshed on the StayAwake window. The randomly picked input option used for the last Awake will also be displayed below the last Awake time. #### Pause button Click this button to pause StayAwake Key simulations. @@ -55,6 +70,8 @@ As seen in the clip above, clicking the *Pause* button will also have these effe ![StayAwake UI](images/StayAwakePausedLaunch.png) +4. On a related note, if the Windows system has been manually locked, StayAwake will pause key and mouse simulations until the system gets unlocked again. This will help reduce disk activity and CPU usage (thus conserving battery power on laptops) while the system is in locked state. + #### Minimize button Click this button to minimize StayAwake to the Windows System Tray. See the [SystemTray Icon](#system-tray-icon) section below for more info. @@ -62,10 +79,22 @@ Click this button to minimize StayAwake to the Windows System Tray. See the [Sys Click this button to exit StayAwake. #### Start Minimized -Check the _Launch application in minimized state_ box to have StayAwake automatically minimize to System Tray upon launch. +Check the _Start minimized_ box to have StayAwake automatically minimize to System Tray upon launch. When the _Start Minimized_ option is enabled, StayAwake will launch and minimize immediately only on the first launch attempt. If StayAwake is still running in a minimized state, trying to launch the application again will restore the StayAwake dialog from the minimized state. This is by design when `MultipleInstancesAllowed=N`. See under: [Configuration File Details](#configuration-file-details) +#### Update Check button +Click this button to manually check if a newer version of StayAwake is available. When clicked, the _Check for Updates_ dialog will popup. + + +![StayAwake UI](images/CheckForUpdates.png) + +The _Check for Updates_ popup will display the Installed and Latest Version numbers. + +If a newer version is available, the popup will display a clickable link that redirects to the [StayAwake - Latest Release](https://github.com/shriprem/StayAwake/releases/latest) page. + +In this popup, you can also enable the _Check for updates when StayAwake starts_ option to have StayAwake automatically check for a newer version every time it is launched. This is an opt-in feature that is unchecked by default. + ### System Tray Icon ![StayAwake UI](images/SystemTray.png) @@ -80,7 +109,9 @@ When you minimize StayAwake, it will be represented by the System Tray icon show ## Configuration File Details StayAwake saves its configuration in a file named `StayAwake.ini`. This file is co-located in the `StayAwake.exe` application file folder. These key-value pairs are stored in this file: -1. `SelectedKeyCodes`: This key stores a string of 12 characters -- each character indicating if a key code in the [Select multiple Key Codes](https://github.com/shriprem/StayAwake/blob/Version2.0/InputOptions.md) popup is selected or not with either a `1` or `0`, respectively. +1. `SelectedKeyCodes`: This key stores a string of 26 characters -- each character indicating if a key code or mouse move in the [Input Simulation Options](InputOptions.md) popup is enabled or not with either a `1` or `0`, respectively. + + If an invalid value is detected for this key, the default value of `00111111111111111111111111` (i.e., enabling of all inputs except *Scroll Lock cycle* and *Volume Down & Up*) will be used instead. 2. `MinimumIntervalInSeconds`: This key stores the minimum number of seconds between each StayAwake Key simulation as specified in the [Minimum Awake Seconds](#minimum-awake-seconds) field. @@ -92,9 +123,11 @@ StayAwake saves its configuration in a file named `StayAwake.ini`. This file is 4. `AwakePaused`: This key stores a flag value (_Y_ or _N_) to indicate if StayAwake is in paused state. -5. `StartMinimized`: This key stores a flag value (_Y_ or _N_) to indicate if StayAwake will automatically minimize to System Tray upon launch. See: [Start Minimized](#start-minimized) +5. `StartMinimized`: This key stores a flag value (_Y_ or _N_) to indicate if StayAwake will automatically minimize to System Tray upon launch. See: [Start Minimized](#start-minimized) -6. `MultipleInstancesAllowed`: This key stores a flag value (_Y_ or _N_) to indicate whether multiple instances of StayAwake are allowed to run simultaneously. The default value is _N_ (i.e., multiple instances are not allowed). Any value that is not _Y_ will be interpreted as _N_. +6. `StartCheckUpdates`: This key stores a flag value (_Y_ or _N_) to indicate if StayAwake will automatically check for a newer version every time it is launched. See: [Update Check button](#update-check-button) + +7. `MultipleInstancesAllowed`: This key stores a flag value (_Y_ or _N_) to indicate whether multiple instances of StayAwake are allowed to run simultaneously. The default value is _N_ (i.e., multiple instances are not allowed). Any value that is not _Y_ will be interpreted as _N_. ## Alternatives @@ -116,5 +149,9 @@ However, if you are looking for a way to keep your Microsoft Teams status _Activ StayAwake is a portable application and does NOT require installation with administrator privileges. Simply download the latest release from the [Releases](https://github.com/shriprem/StayAwake/releases) page and unzip the contents to a folder of your choice. Run the `StayAwake.exe` application file to start the utility. +If you like StayAwake, please star this repo. Thank you for your appreciation. + +If you would like to support its development, you can buy me a coffee by clicking the button below. Thank you for your support! + Buy Me a Coffee at ko-fi.com diff --git a/VersionHistory.md b/VersionHistory.md index 760863d..e23ae07 100644 --- a/VersionHistory.md +++ b/VersionHistory.md @@ -1,5 +1,23 @@ ## Version History +### Version 1.5.0.0 [October 12, 2026] + +1. Two mouse simulation options (Zero-pixel Mouse Move and One-pixel Mouse Jiggle) have been added to the [Input Simulation Options](InputOptions.md) roster. + +2. 12 new simulation options for Extended Function Keys: F13 to F24 have been added to the [Input Simulation Options](InputOptions.md) roster. + +3. StayAwake will now simulate events using the `SendInput` API instead of the deprecated `keybd_event` API. `SendInput` API allows batching of multiple key or mouse events in a single atomic call to the Windows OS, thus yielding jitter-free key or mouse simulations. + +4. Volume Up & Down simulation will now preserve the muted sound state. Prior to each Volume Up & Down simulation event, StayAwake will detect if the sound is in a muted state or not. If the sound is found muted, Volume Mute key will also be added after the Volume Down & Up keys in the `SendInput` API call. + +5. If the Windows system has been manually locked, StayAwake will now suppress key and mouse simulations until the system gets unlocked again. This will help reduce disk activity and CPU usage (thus conserving battery power on laptops) while the system is in locked state. + +6. Update Check button has been added so that users can manually check if a newer version of StayAwake is available. Additionally, an opt-in feature is also being offered to automatically check for a newer version every time StayAwake is started. + +**Release:** [1.5.0.0 Release](https://github.com/shriprem/StayAwake/releases/tag/v1.5.0.0) + +--- + ### Version 1.3.0.0 [September 21, 2026] 1. StayAwake will use a random pick from a roster of user-specified key codes for each Awake event. See enhancement request: [#15](https://github.com/shriprem/StayAwake/issues/15). diff --git a/images/CheckForUpdates.png b/images/CheckForUpdates.png new file mode 100644 index 0000000000000000000000000000000000000000..55c17793982aa166437a8259061a57e958e79f1f GIT binary patch literal 4352 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 literal 0 HcmV?d00001 diff --git a/images/StayAwakeApp.png b/images/StayAwakeApp.png index 854f4850927374ad09427c71394570877646ddfe..4665694a1d5016985fbaa1b3fedba351104de0c4 100644 GIT binary patch literal 7720 zcmaiZ1yq#X*Do~!LpUIaG?F7VN_WoCH8cVPsFZ+6cY_Qif^;*0gmgC|(gM;A(k0z} z$M^f+?|biB_pZC1b=H1PJ?EVL{PsS-eNLE$nmiGZ3W$M$L8Pbv)x^NS6hJ3tJS=nx z`?v6SXo3G;!N3UvgZRhagc;94OpSp-JEI7d)^?xSP51dkwwB&44^bR9P7ieni@$Mt z(QC}E6XudJeY6T`7$-2Vk1Jlas3tn{uN-dCneiVe#|6&2$TKq8;SM zGJz05Q&lq|L6iUHpTSB30tHTr=~ff~f7f~XdC~hUq1(c^&c17vmY-i#w?yP%#>Q)BN7AU(+if?g z)zO5*QFDq^F+d)a*A66qv)Ja%zqPIL9@+dFPFV(#TlP3I?D zoyrMmH_HU^;Ai;)dH_V#Hg$2t;(SjF_x2jZ-gHcHzXst@(J< z$}z?6$;ZnLBK2SD<3xk^!woKoJmEk^Yfo=h*vZ*an}o<+SHjg*G%?LZbJ>2n-Rxfd z%E$Z+U(b%Ns3E)QqisX`5Wnl$QUf>LXurAXutR3BPYV25xh3BU$22|{Qxt2Yy?L}M z{pGI|iOL5Lj%D<~;Jnby)7NxDLQCt|HWMIJx2Knvdb+3I^;yPew!%O6C!@5u#NJm2 z9k}n3U%K>v$W>-+Ty=sMku*~Vg~oI!tmH@Gr-T@RT@#1-G3d5S7quo!t%nh2gI)i z0Zb~zcO|pYw;nC~d%_Ww(OBAS$t%lvFnCScy7e6fUth?m$=!PHDFru?v6$dN^rJ~4+NSLy zbXjHU%D>oDj1Oh=Y zX=zLp7%W8w005AvEDSgq85u)(U?3J0tqS^KU%Wu0XAD4TDY8I@{}CWi$lu(*1Rhf6 zZ|+|L2m17H?q7l(h-ztRS$Z!-2CR_+k!g8(i8?F+q1NJfgQcaVP8);ip$wZh9g9_a2jWm5X zg7gr>&2F(3)n8~|D;G${-s*nRz*>>D8D>o zVWqh4d*YJ;i_khQE+n;jC=^qUftf=p?>n@31{(Ony_qKAIBq+8A0#c_NNc` zM!O9{;ww@`td0Y~%;DZtFLKCrO?)r&BEdu&fllVHtM_d?&C~N#5u?uDJ$*jYT0m%k zRV#?LsbMAT3xcYQ!q^#Ze&=IqBM2aoB6EC(;w$Y*ezH=-Gw$ZM%|4ErcArWTYj#k%A+jufJBxB3K-meys$*NgpniM099Z^1M|)1!=`;Pu52DMEBHG z(oqdbGUHpoXIVapUc4G_e8@{ogf_j71UcpJ<40( zM%w}T^2-v8qdh{m%ua!ji>kA=rsAR9`eIG%h3T+-je$CpGTveFjcbkMx=KX8!o8GyR;X-m+2zZ+8(Ze`eWw|8H;NH}+o}ehLWY#A=a28V zwLG9?h-w^=9q$MGR6I*V+u}}K6y_%bdTJ*^!aGAuxVduP`HQyjm0YJSUK&J@x?)-b z*=v4bVwTLup+l?M?`&OvteeUsFP3qF-rMEID+=v6lho8}t;o`08@mt3O7Tf}m-vo6 z5N<^zmD@n-w7Wyaa=H$pX@v*{RCI+BS%fU^3~M^p1$5qV7N?dA%V0ODuG=PCXML4# zaA16-7kcE@fQP8&1M%O=x08@7n3Z@FJ;*=og--Qm3UpuF|L$0dQ&VjyXkU86BMS{- zk`~c(o{nY*+zbN{)ld-mTOPSMNoi$#xQ%V^jn(W+6aG}$Xjd-F49)wn%ldMy11)VxJcn%X8Io` z3VZE^6kKD+#a?cTFbUoYwJ@8JbpRVjQ+*R2g6uny{{d>v%`K)|&a_Yl6heW1e#4ev z*|9R!IXOAs?2WTnTwPq+Bfdw-^tvqn2y@w)5I52vCbe!jKpmhop5Y_Vro-Y7ZU%$? z>G-tZ08w2vn0gU5XgFy-+jT@M_%L;H|9ocfAq3bSVNu|}h{OB)99L78&p^BxjI-7g zL5d04^VFnY_sa8K$bk$scX$C$=_XY!>5FA+3qn*ih`eCkzuGxQi}%vRpxD`Cyj*&7!wqau*i*zLgaDyWLk=bYb2$W<@MafG1X&Vc%x-JlZE=6=F? ztU{>Vhk4FXMQNzML(Wxd8Q^kVlvLjw+V?~Hp0yPWSpFyG;%Q0-T2erB`i=@YgT=rdXF^X0S$ z`^owai>*@B{3!3*=MFQ`SD{)%k9>5F638z5#6Bu^8arehV^U3jiH48E4%Qy`_jy=- zQ;+LmkLb%i^|M^19 z01QJmG6UXkT0w+I_*J`m6LmB0#m>J?yqodn!2*SFI;sOrgGMTL48@LXJ=R+d;pWKu z`wx;8qFxdKV4Mcy|MBtvH&_1;h58q1`VWP|Lt3!uu|R_TtzvNN93KlanX*oiF?*N= zs3tZW1gWdpwlDPAh;k$@uqukk72bR2+vA}2WVPc-o_Ay%r#Js{L53HWT%{wv*h{O# z1jAznl&0Hcy?S<$?DJ3cvVQ`>mN?aXpRZPVd62!p}H+wN|hy>)=)5T}XX-q6G$|Uf9X;m3*iP%1CQBGlDLiW!V z>A}Frf~4QjzTGvIn(o)Ue1JvHVU48KkiIff4iZTWp?qHFp0+9b&vaeZDjXzhfc(upSjgTDcdFbKh}0TPs15RRb1D2W zp)3dE%1VN`Bv3FiSAjD;kW1An{aZ98K=Wm+zdSaZ88h`H2>hythLSw{IcR|8VJ!uO zfGA`6+;Xe-GVS5r90XwbRf^AVmF#?wzjJ?RZiolHM{avFq^#s&z!_2?P+=)M`xuIM z(TS)xr^f4tnIDDgN*be0Tp|>(-HzMrx%}jVw-bAPcLtcw&bQAkYvXc{;M@Me2Xz=h3iNW z+qkLe=fFnKPx=1c#N}`0?7ztAsQ!CS%&pFz2^dJMVKIic9%3&W8JOo8Gfy02uDG4v z@m2NaDpxpFjQ|y5YEMjq=6SKVIAIR@n$84&=~3xsW4)pNW2Ek~RA2WzEM8o~!Hwkq z9q2sPyMWQM3qnIU1FVT0m?0`Q*~03G0tNA&`3)Te>XCq(x4g<$XrTESwgZ)2WWVPH#U@}9T<&R|eKiyo-vLDse;RNG1`z(5}h#OP~I0oU54TU$kf- zYLT-J?J0@kah9_V-@R^aHk$SukY22n7q2XtZeUm? z9|;=GXk=o+_3>HR&7h(?&Ff$LZZR49QbVfbzwIBo?}!v;+_BGc>}Xxqa+1_Ua;X1w zJ+pX*^iiUOThLN{090sOGi;w;CtIIHBg#0URuE7vK?;!Sw8)QwJ)wFg(?6oEs;_2j zJBWjcyZd9I)!d(^-pzBnR6I;yUlfs~@Jc$xsfAP4Aumg4Dptp=O6B?Jkj&nkRrV60 zYROOEPL*htvlFpU!dtAxfP@#;jDxNjACa9h)uU6TY9xbG>#4K*6=U538Bua~h6_c- z$8(ee;{l}}G#g!yu;!~(d!2gp*qg1s4P`ckv(X7l8E{^JQ6RRtg1Do#*n*@5?R((7(lASaREO zzpHiO=iixmPV8UcrJ5G0o}=I8y!@f&VoJ>?&T?_2A(YS^8zoikOp@N3ttvORVaC+h zzG2ftfhM)+P)zRR^2<=ZJzgP*zh2v2hiV%Y5rtVkC{J(At*rbJlnK$@w?PX2qA>mR zdXRf^cBoU-Qy0RSY>$QZV2TVPzPsF_5*|=$Z`*jH0%0ZJ%-4k4?YGy2B)XBl2WWP5%h@Unu{*gp1CoAfzXzEH4ZTiaR zMkmX{`$8teC1z${LsU4vM(R?+m$U^WkuMWFLTV=P^o5F~q{gC@&fVZ^=8u{F(@?@o%_T_8ZDNp}YZ0QHuT3=U zOhZp^vcYMiPYA$oXUs=6Ae1QV+EPUsU_}SR?aau?IF8R&V3twC78UO|hNO}p*WqN1 zgoK3Jc&O^URm}3yHDkZ+v2QEU^mM#mJf_NjBT1U%J`#?P{W8fPdkXgrx^tEiFsOUjii|hLhwWzQ!_ANT8 zFJuiXq12LhPe3l1sP9th0P;|Q!q1NnQul98aQ&^Gx{0UzX?lw~7YI1!x^BnqJ%==@ zd@Y@@$?#ONv8F688Q*I)q!uJb>awH8*t=MA#_2IofVop9`47IQU)woKe<8e7y|!5F zw#V8>^K0y8ogr7FnZzR|jwNmjiupP60R@I6f~5+=Xpebh*&P>^A}ey3&O zpxPR(%pE=|zqadnX`q|p_;^rvw9Tq%VZh`pK6L&M%)mS{2}7@=HU6gPienHm^PnJI z^WO0xaI8cedw9&%$8NWMx(c_fK~B^`=DxxF^ewF#&{MimvE52s8+=mRL;n0=l*k+~Ml|;ssd7 z{D)Fw)|1gxHADN87DE%yeEz)PqM6vtt*+C%{*4`FwBs_l#a8f$)v6;DH=uwTR1>DR z_JrRdPp<7#oT+SqSxIqXgqTv9^>t2!69bnCcV!y_2O8hxd$I`vG!i`COti~a8GdWy z!V^j~Di`_dmhF%-HA_km13DjIW%f?(+dK@W115WQzA16kldTN7^_<|my$ePodSif^ z`BlF9L1<}!1wiZ z07XSqUyJUvbxtjG#;J95?u~U$-|CEW9{+h+A4IUGST}2H*a8IASx=#P)qBdGd%a5z}bg+EZdUtp?+XCEYzMz zTmtG3@x8_%sAYBW?2qUKuA$VYS*R&#MuyEq2!hzPg9fxJaJ#`~9Gq`C7?GXlCkAx- zr^Ui4eMyGi!t8AcxmSv)12?8L_sCvH3l8hjmbJCDf&QY1=WE3F_3ADqud1{~IQayk}=)O~uLcaW&2=@3N`2;>ahTQe2> z&$VZR#&@lAR4`b0#Pqp7J>HRk{a5U7+5l$PHl@jtzS3`qaI1clzy Zx16MhXx*-TL0|o1D8kgBr81_0{|gFDj6(nb literal 7491 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

qF2PZ0X z&7(vinj28vrtPh*%gygj)&zwrBFWi|mLrN70IrJ56lC_a`mRpGQ>l&Y^79W`&*URX zuO#qH>?l}eJnHL5rl+;;Q$~Je=tzEwt;rSk!k*u2kT>er5_n``s;a8!B6r=$ zuHZ~ck+*^V9A5Rz2{1m`9)FK&rdQT&-){~>D}!5l>!eI$K+`}SsUv7mnJ4$IDM7|~ z{#lGcyttB>l}a13pd-`6GOo7$a?%iqkh%!fdS1qxUPG6!&WEna^<}K}HIy{avk$Z$ zc?-Cogd#dm_SmA9r%H}=Cq`R@-db~ zC%Xdbgg`Z z2m3CKM;ioc-V$)5iFs{$RBC|0*OF~$A`-^4&* zIuT3!4Pgx*avOTT`aFU_ogJ%50`g^<2FQ>z6A;i2uNEqEDi4c;U2We7qeIWU#f(=@--NpAqGlI!VRYQWvaSoMEqLILLY*54G6MaEX1sc{d6y(-88uy14IKQ!{%@OdHC5shh5znF{;Ok+J=yC2B5TVH;g!?rN(;ZAG=)K&6fBaNeG2 z?VsTgHaYL@iSbT-`#K?6XsKuR6Jjnlg!vIwel5Z~X7zmY;{4u|4fahd`Lyav$!N~?3B6|(1&}U=#7Z<$ z+ymO}OO>(IxwWueQ6<_>FS}I)vvAtfBl&i?W*fL(ZV7#7nhZ`!)TS#s`YAu}sWjqO zZGh(2jCEa@bvx=8X5;KVr#y_{jcb^Zd5gkoOWfkYti8V4PoUT+sJr z-|-xRGSgBchLC(W%0&!a#dXvyb=bt+w^55)zjJsrT($XDcJ=16s=698Ww>*(RkbnP z!-DBFlr#Ck8AAt?3Rs0)7LZ*rtQ1Y=B4W6Qx~j!fD6_imTjE7uL1GvM%cO#9kTYeB zId^{oWSJfa>Zg*QQhAMHG|gy)hOC$QXokwZvFtU1Oo{pCU_5HAK{HR2Mfu!bjCW8D z(~)5`;;5P~M%ReFSoO|Wc?CIinGn$Daq#hyWuG0>NzD!7g#fC}EF@6zj`0Q{YRq~CbTGN@5 zM0%Z@O*q}gq3#fcZ|2_|h7H>1)>T!PJ!F_5BPrJR=g{sv5VB=-sy)ojqnB!hK<*GO zTZ!T9c(01h*xb>n+l_f2euQ~!B1t5kN(#~gwM2W_*Y{F?DmJZI^fAIP4xhf7ydAEKs8(D<{J*G9=-WlpF&!qPj- zrLz;-US~21L({4jgRA-}e>AgPvQl!>eBT>-6!wjV2xEF>;eHRGo*b7pE2m3m1uFjOmVZSN%4% z*e;dhK40wW_EwCCg?@0(`x#`|>*oA|z;*TU`<6hDWH1V4u4V{Lk{P0#$pAWhQzAsD zxuD|-$Xt>HhLhf}^^d-_brtE19>4!|WQS`%`9cAI_C+#S)9^sOu2UBEJ;O~;%|lWI z7(@cTVQF!n`*~V9-1w?ey-D8u`~Z`qr4~9tNP2Y6bsK+AQ?k?Crfyuf7SX3ii`e`W zy5Y>W>td|f}6xN+8KoiXkQS_4)6Q^dzUDZ6_{tILJSkh-uI|yr!&1e zJ0U);@>#1Uzb0ZJAP7_<{1i0^!C^XLid+o5_fZ3JBVQNtnCW@J$!qL&DvpNAQ)r2j H*{A;j_w_mB diff --git a/images/StayAwakePausedLaunch.png b/images/StayAwakePausedLaunch.png index 2568358e82d2865a7211c136102f2885e9777240..138989bb9825ca6aa14dc88d66393a8ef186197e 100644 GIT binary patch delta 6909 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