diff --git a/InputOptions.md b/InputOptions.md new file mode 100644 index 0000000..a5642ab --- /dev/null +++ b/InputOptions.md @@ -0,0 +1,55 @@ +## Input Simulation Options + +![Input Options UI](images/StayAwakeInputOptions.png) + +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. + +#### 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. + +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. + +**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 [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. + diff --git a/README.md b/README.md index 0dabb5b..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](#select-multiple-key-codes) 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,36 +79,21 @@ 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. -### 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. +![StayAwake UI](images/CheckForUpdates.png) -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. +The _Check for Updates_ popup will display the Installed and Latest Version numbers. -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. +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. -#### 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. +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 @@ -107,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](#select-multiple-key-codes) 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. @@ -119,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 @@ -143,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/StayAwake/SelectKeyCodesDlg.cpp b/StayAwake/SelectKeyCodesDlg.cpp index 5958d04..59fdd89 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_ZERO - 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,85 @@ 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::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() 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 + 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_pCaller->GetSelectedKeyCodes()); + CheckAllBoxes(m_AwakeCore.GetSelectedKeyCodes()); - 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", L"Select multiple Key Codes", MB_ICONEXCLAMATION); - return; - } + 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 (!m_pCaller->SaveSelectedKeyCodes(sSelectedKeyCodes)) - { - MessageBox(L"Unable to save to the StayAwake.ini file.", 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; + } - 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')); } void CSelectKeyCodesDlg::OnClickedKeySelectNoneBtn() { - CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'0')); + CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'0')); +} + +void CSelectKeyCodesDlg::OnClickedSelectAllUnassignedKeys() +{ + CheckAllBoxes(wstring(LEN_ROSTER_KEYCODES, L'1'), IDC_KEY_UNASSIGNED_1, IDC_KEY_F13); +} + +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 2a23998..896c125 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 @@ -25,10 +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 OnClickedInputOptionsInfoBtn(); afx_msg void OnOK(); 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); }; 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 7201241..b5472a5 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 ///////////////////////////////////////////////////////////////////////////// @@ -69,46 +70,47 @@ 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 +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" 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_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,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,142,55,14 + PUSHBUTTON "...",IDC_ABOUT_BTN,188,142,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 @@ -123,12 +125,45 @@ 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 - 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,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",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 +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 @@ -187,11 +222,19 @@ BEGIN IDD_STAYAWAKE_DIALOG, DIALOG BEGIN RIGHTMARGIN, 203 - BOTTOMMARGIN, 142 + BOTTOMMARGIN, 158 END 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 @@ -244,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 c9cac35..9598b0a 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 @@ -214,16 +218,20 @@ + + + + Create @@ -231,7 +239,9 @@ Create Create + + @@ -241,6 +251,7 @@ + diff --git a/StayAwake/StayAwake.vcxproj.filters b/StayAwake/StayAwake.vcxproj.filters index 20383b7..d0f8204 100644 --- a/StayAwake/StayAwake.vcxproj.filters +++ b/StayAwake/StayAwake.vcxproj.filters @@ -15,9 +15,6 @@ - - Header Files - Header Files @@ -42,6 +39,18 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -62,6 +71,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -80,5 +98,8 @@ Resource Files + + Resource Files + \ No newline at end of file diff --git a/StayAwake/StayAwakeAboutDlg.cpp b/StayAwake/StayAwakeAboutDlg.cpp index fb1a5ac..ae910b8 100644 --- a/StayAwake/StayAwakeAboutDlg.cpp +++ b/StayAwake/StayAwakeAboutDlg.cpp @@ -36,8 +36,8 @@ 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()); - SetDlgItemTextA(m_hWnd, IDC_ABOUT_BUILD_TIME, (string{ "Last code update: " } + LAST_CODE_UPDATE_TIME).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()); return TRUE; @@ -46,11 +46,12 @@ 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; } 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/StayAwakeCore.cpp b/StayAwake/StayAwakeCore.cpp new file mode 100644 index 0000000..7d2d15f --- /dev/null +++ b/StayAwake/StayAwakeCore.cpp @@ -0,0 +1,325 @@ +#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") == 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) + { + case 0: + inputName = L"Scroll Lock cycling"; + PressTwoKeys(VK_SCROLL, VK_SCROLL, false); + break; + + case 1: + inputName = L"Volume Down && Up"; + 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: + case 3: + case 4: + case 5: + case 6: + case 7: + 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"; + PressOneKey(VK_UNASSIGNED_10, false); + break; + + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + inputName = L"F" + to_wstring(inputCode + 1); + PressOneKey(static_cast(VK_F13 + inputCode - 12), false); + break; + + case 24: + inputName = L"One-pixel Mouse Jiggle"; + MouseMoveTiny(); + break; + + case 25: + inputName = L"Zero-pixel Mouse Move"; + MouseMoveZero(); + break; + + default: + inputName = L"Invalid Awake Key"; + break; + } +} + +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] = {}; + + 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)); +} + +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::MouseMoveTiny() +{ + 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)); +} + +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 new file mode 100644 index 0000000..4d74024 --- /dev/null +++ b/StayAwake/StayAwakeCore.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#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; + +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 PREF_START_UPDATE_CHECK = L"StartCheckUpdates"; + +constexpr auto MIN_PERIOD{ 10 }; +constexpr auto MAX_PERIOD{ 9990 }; +constexpr auto DEF_PERIOD{ 240 }; + +constexpr auto LEN_ROSTER_KEYCODES{ 26 }; +const auto DEF_SELECTED_KEYCODES = L"00" + wstring(LEN_ROSTER_KEYCODES - 2, L'1'); + + + +class StayAwakeCore +{ +public: + 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); + ULONGLONG GetIdleTimeMilliseconds(); + +private: + wchar_t sIniFilePath[MAX_PATH]{}; + + 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 55ff507..cc74899 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 "UpdateCheckDlg.h" #include "Utils.h" #include @@ -26,51 +28,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) @@ -82,15 +39,17 @@ 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() + ON_BN_CLICKED(IDC_UPDATE_CHECK_BTN, &CStayAwakeDlg::OnUpdateCheckClicked) END_MESSAGE_MAP() @@ -126,17 +85,18 @@ BOOL CStayAwakeDlg::OnInitDialog() SetIcon(m_hIcon, FALSE); // Set small icon InitConfigFilePath(); - InitIntervals(); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MIN, m_IntervalMinSeconds, FALSE); - SetDlgItemInt(IDC_STAYAWAKE_INTERVAL_MAX, 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); + m_AwakeCore.InitIntervals(m_IntervalMinSeconds, m_IntervalMaxSeconds); + SetDlgItemInt(IDC_INTERVAL_MIN_EDIT, m_IntervalMinSeconds, FALSE); + SetDlgItemInt(IDC_INTERVAL_MAX_EDIT, m_IntervalMaxSeconds, FALSE); - SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, IsTimerPaused() ? BTN_TEXT_RESUME : BTN_TEXT_PAUSE); + 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); - 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); + SetDlgItemText(IDC_PAUSE_RESUME_BTN, IsTimerPaused() ? BTN_TEXT_RESUME : BTN_TEXT_PAUSE); + + 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); @@ -160,7 +120,9 @@ void CStayAwakeDlg::OnSysCommand(UINT nID, LPARAM lParam) afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) { - if (GetPreference(PREF_MULTI_INSTANCE, L"N") != L"Y" && Utils::getProcessRunCount(L"StayAwake.exe") > 1) + WTSRegisterSessionNotification(m_hWnd, NOTIFY_FOR_THIS_SESSION); + + 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); @@ -173,8 +135,16 @@ afx_msg LRESULT CStayAwakeDlg::OnPostOpen(WPARAM wParam, LPARAM lParam) else InitAwakes(); - BOOL bMinimized = (GetPreference(PREF_START_MINIMIZED, L"N") == L"Y"); - CheckDlgButton(IDC_START_MINIMIZED, bMinimized ? BST_CHECKED : BST_UNCHECKED); + if (m_AwakeCore.GetPreference(PREF_START_UPDATE_CHECK, L"N") == L"Y") + { + 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); if (bMinimized) MinimizeToTray(); return 0; @@ -212,12 +182,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; } @@ -229,12 +199,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; } @@ -242,9 +212,9 @@ void CStayAwakeDlg::OnKillfocusIntervalMax() } -void CStayAwakeDlg::OnSetupKeyCodesRosterClicked() +void CStayAwakeDlg::OnSelectInputOptionsClicked() { - CSelectKeyCodesDlg dlgSelectKeyCodes(this); + CSelectKeyCodesDlg dlgSelectKeyCodes(this, m_AwakeCore); if (dlgSelectKeyCodes.DoModal() == IDOK) { @@ -262,41 +232,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; + wchar_t sIniFilePath[MAX_PATH]{}; + DWORD size = GetModuleFileNameW(nullptr, sIniFilePath, MAX_PATH); - 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{}; - - 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; @@ -312,8 +264,8 @@ void CStayAwakeDlg::InitAwakes() InitRosterKeyCodes(); SimulateAwakeKeyPress(); - SetDlgItemText(IDC_STAYAWAKE_PAUSE_RESUME_BTN, BTN_TEXT_PAUSE); - WritePrivateProfileString(PREF_DEFAULTS, PREF_AWAKE_PAUSED, L"N", m_IniFilePath); + SetDlgItemText(IDC_PAUSE_RESUME_BTN, BTN_TEXT_PAUSE); + m_AwakeCore.SetPreference(PREF_AWAKE_PAUSED, L"N"); } void CStayAwakeDlg::InitTrayIcon() @@ -335,8 +287,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!"); @@ -368,6 +320,7 @@ void CStayAwakeDlg::OnDestroy() if (m_bMinimized) Shell_NotifyIcon(NIM_DELETE, &m_TrayData); m_menu.DestroyMenu(); + WTSUnRegisterSessionNotification(m_hWnd); CDialogEx::OnDestroy(); } @@ -376,57 +329,28 @@ void CStayAwakeDlg::SimulateAwakeKeyPress() { if (!m_RosterLength) InitRosterKeyCodes(); - UINT nAwakeKeyCode{ m_RosterKeyCodes[rand() % m_RosterLength] }; - wstring sAwakeKeyCode{}; - - switch (nAwakeKeyCode) + if (m_bSystemLocked) { - 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; + SetDlgItemText(IDC_NEXT_EVENT_TIME_INFO, L"PAUSED since Windows is LOCKED"); + return; } - 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; + UINT nAwakeKeyCode{ m_RosterKeyCodes[rand() % m_RosterLength] }; + wstring sAwakeKeyCode{}; - 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; - } + m_AwakeCore.SimulateInput(nAwakeKeyCode, sAwakeKeyCode); SYSTEMTIME lastTime{}; GetLocalTime(&lastTime); - SetDlgItemText(IDC_STAYAWAKE_LAST_EVENT, Utils::formatSystemTime(lastTime, L"Last StayAwake event").c_str()); - SetDlgItemText(IDC_STAYAWAKE_LAST_KEYCODE, (L"[" + sAwakeKeyCode + L"]").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_LAST_EVENT_INPUT_INFO, (L"[" + sAwakeKeyCode + L":" + to_wstring(idleTime) + L" ms]").c_str()); +#else + SetDlgItemText(IDC_LAST_EVENT_INPUT_INFO, (L"[" + sAwakeKeyCode + L"]").c_str()); +#endif UINT nTimerSeconds{ m_IntervalMinSeconds }; if (m_IntervalMinSeconds != m_IntervalMaxSeconds) @@ -437,11 +361,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(); @@ -452,12 +376,12 @@ 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); } - 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(); } @@ -469,7 +393,7 @@ void CStayAwakeDlg::OnClickedAboutButton() dlgAbout.DoModal(); } -void CStayAwakeDlg::OnPauseResume() +void CStayAwakeDlg::OnPauseResumeClicked() { if (IsTimerPaused()) { @@ -479,37 +403,48 @@ 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); + SetDlgItemText(IDC_PAUSE_RESUME_BTN, BTN_TEXT_RESUME); + m_AwakeCore.SetPreference(PREF_AWAKE_PAUSED, L"Y"); ShowPausedInfo(false); } } -wstring CStayAwakeDlg::GetPreference(wstring key, wstring defaultVal) +bool CStayAwakeDlg::IsTimerPaused() { - const int bufSize{ MAX_PATH }; - wstring ftBuf(bufSize, '\0'); + return (m_AwakeCore.GetPreference(PREF_AWAKE_PAUSED, L"N") == L"Y"); +} - GetPrivateProfileString(PREF_DEFAULTS, key.c_str(), defaultVal.c_str(), ftBuf.data(), bufSize, m_IniFilePath); +void CStayAwakeDlg::ShowPausedInfo(bool both) +{ + if (both) + SetDlgItemText(IDC_LAST_EVENT_TIME_INFO, L"Last StayAwake event: PAUSED"); - return wstring{ ftBuf.c_str() }; + SetDlgItemText(IDC_NEXT_EVENT_TIME_INFO, L"Next StayAwake event: PAUSED"); } -bool CStayAwakeDlg::IsTimerPaused() +void CStayAwakeDlg::OnStartMinimized() { - return (GetPreference(PREF_AWAKE_PAUSED, L"N") == L"Y"); + m_AwakeCore.SetPreference(PREF_START_MINIMIZED, (IsDlgButtonChecked(IDC_START_MINIMIZED_CBX) == BST_CHECKED) ? L"Y" : L"N"); } -void CStayAwakeDlg::ShowPausedInfo(bool both) +void CStayAwakeDlg::OnSessionChange(UINT nSessionState, UINT nId) { - if (both) - SetDlgItemText(IDC_STAYAWAKE_LAST_EVENT, L"Last StayAwake event: PAUSED"); + switch (nSessionState) + { + case WTS_SESSION_LOCK: + m_bSystemLocked = true; + break; - SetDlgItemText(IDC_STAYAWAKE_NEXT_EVENT, L"Next StayAwake event: PAUSED"); + case WTS_SESSION_UNLOCK: + m_bSystemLocked = false; + break; + } + + CDialogEx::OnSessionChange(nSessionState, nId); } -void CStayAwakeDlg::OnStartMinimized() +void CStayAwakeDlg::OnUpdateCheckClicked() { - WritePrivateProfileString(PREF_DEFAULTS, PREF_START_MINIMIZED, - (IsDlgButtonChecked(IDC_START_MINIMIZED) == BST_CHECKED) ? L"Y" : L"N", m_IniFilePath); + CUpdateCheckDlg dlgUpdateCheck(this, m_AwakeCore); + dlgUpdateCheck.DoModal(); } diff --git a/StayAwake/StayAwakeDlg.h b/StayAwake/StayAwakeDlg.h index 7d453d5..4fd4760 100644 --- a/StayAwake/StayAwakeDlg.h +++ b/StayAwake/StayAwakeDlg.h @@ -1,35 +1,18 @@ #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"; +//#define DEBUG_DISPLAY_IDLE_TICKS 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; @@ -48,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; @@ -61,8 +39,7 @@ class CStayAwakeDlg : public CDialogEx DECLARE_MESSAGE_MAP() 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]{}; @@ -71,18 +48,19 @@ class CStayAwakeDlg : public CDialogEx UINT m_IntervalMinSeconds{}; UINT m_IntervalMaxSeconds{}; + StayAwakeCore m_AwakeCore{}; NOTIFYICONDATA m_TrayData{}; CMenu m_menu; 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 */ }; @@ -91,9 +69,9 @@ 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(); void InitRosterKeyCodes(); void InitAwakes(); void InitTrayIcon(); @@ -103,6 +81,7 @@ class CStayAwakeDlg : public CDialogEx void SimulateAwakeKeyPress(); void ShowPausedInfo(bool both); - wstring GetPreference(wstring key, wstring defaultVal); bool IsTimerPaused(); +public: + afx_msg void OnUpdateCheckClicked(); }; diff --git a/StayAwake/UpdateCheckDlg.cpp b/StayAwake/UpdateCheckDlg.cpp new file mode 100644 index 0000000..06bf3bc --- /dev/null +++ b/StayAwake/UpdateCheckDlg.cpp @@ -0,0 +1,85 @@ +#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() +{ +} + +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); +} + + +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 currentVersion{}, latestVersion{}; + bool bNewer = IsNewerVersionAvailable(currentVersion, latestVersion); + + SetDlgItemText(IDC_INSTALLED_VERSION_INFO, currentVersion.c_str()); + SetDlgItemText(IDC_LATEST_VERSION_INFO, latestVersion.c_str()); + SetDlgItemText(IDC_VERSION_CHECK_STATUS, + 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); + + 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..5a23049 --- /dev/null +++ b/StayAwake/UpdateCheckDlg.h @@ -0,0 +1,36 @@ +#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(); + + static bool IsNewerVersionAvailable(wstring& currentVersion, wstring& latestVersion); + +// 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() +}; 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); } 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); +}; + diff --git a/StayAwake/res/information.bmp b/StayAwake/res/information.bmp new file mode 100644 index 0000000..0fe3f41 Binary files /dev/null and b/StayAwake/res/information.bmp differ diff --git a/StayAwake/resource.h b/StayAwake/resource.h index cd1de7e..0779d26 100644 --- a/StayAwake/resource.h +++ b/StayAwake/resource.h @@ -7,44 +7,73 @@ #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 #define IDB_ABOUT_BITMAP 134 +#define IDB_INFO_BITMAP 135 -#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_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_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_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_UPDATE_CHECK_BTN 1015 +#define IDC_ABOUT_BTN 1016 + +#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_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_INPUT_OPTIONS_INFO_BTN 1155 + + +#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 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 0000000..55c1779 Binary files /dev/null and b/images/CheckForUpdates.png differ diff --git a/images/StayAwakeApp.png b/images/StayAwakeApp.png index 854f485..4665694 100644 Binary files a/images/StayAwakeApp.png and b/images/StayAwakeApp.png differ diff --git a/images/StayAwakeInputOptions.png b/images/StayAwakeInputOptions.png new file mode 100644 index 0000000..1f7c67e Binary files /dev/null and b/images/StayAwakeInputOptions.png differ diff --git a/images/StayAwakeKeySelections.png b/images/StayAwakeKeySelections.png deleted file mode 100644 index 89bcac5..0000000 Binary files a/images/StayAwakeKeySelections.png and /dev/null differ diff --git a/images/StayAwakePaused.png b/images/StayAwakePaused.png index dbaed5f..04fcaf4 100644 Binary files a/images/StayAwakePaused.png and b/images/StayAwakePaused.png differ diff --git a/images/StayAwakePausedLaunch.png b/images/StayAwakePausedLaunch.png index 2568358..138989b 100644 Binary files a/images/StayAwakePausedLaunch.png and b/images/StayAwakePausedLaunch.png differ