wakepy icon indicating copy to clipboard operation
wakepy copied to clipboard

Windows SetThreadExecutionState based prevent sleep also prevents automatic screen lock if screen saver with password is not enabled

Open fohrloop opened this issue 1 year ago • 5 comments

Background: State diagram

The current understanding of the Windows sleep & lock states diagram.

  • require sign-in: setting "Require sign-in when PC wakes up from sleep" is set
  • ScreenSaverIsSecure: The ScreenSaverIsSecure is set, either by (1) "On resume, display log-on screen" tickbox the Screen Saver Settings or (2) Enforcing ScreenSaverIsSecure by a Group Policy (GPO) (this may be checked with SystemParametersInfoW).
  • ScreenSaveTimeout reached: The screen saver timer reaches the set timeout, and the screen lock is enabled. (might also be "blank".). This can be prevented with ES_DISPLAY_REQUIRED (=keep.presenting mode).
  • suspend timer timeout: The suspend/sleep timer reaches the set timeout. This can be prevented with ES_SYSTEM_REQUIRED (=keep.running or keep.presenting mode)

image

Windows may lock the screen automatically in two occasions:

  1. When user resumes from the screen saver if the ScreenSaverIsSecure is set, either by (1) "On resume, display log-on screen" tickbox the Screen Saver Settings or (2) Enforcing ScreenSaverIsSecure by a Group Policy (GPO). (this may be checked with SystemParametersInfoW).
  2. When user returns from the suspend state to the working state, if the setting "Require sign-in when PC wakes up from sleep" is set

This has the implication that wakepy might prevent automatic screen lock, because if Sleep is inhibited, the route (2) to lock screen is not possible. If route (1) to lock screen is also removed, Windows will never lock screen automatically.This happens when user does not a screen saver with password enabled and there is no Group Policy enforcing automatic and secure screen lock.

To think about

Should wakepy take care that the screen lock is started in the keep.running mode? Or is it the responsibility of the user to have Screen Saver enabled on Windows (and perhaps, on some other systems)?

Possible solutions

Solution option A

  • Check first with SystemParametersInfoW if the system has Screen Saver with ScreenSaverIsSecure (=ask for password) enabled. If yes, do nothing. If not, continue.
  • Use the ScreenSaveTimeout default value (15 minutes) as timeout value.
  • Detect user idle time: https://stackoverflow.com/questions/911856/detecting-idle-time-using-python
  • When user idle time hits the timeout, turn screen lock on with https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-lockworkstation?redirectedfrom=MSDN

Solution option B

  • Add some argument, like allow_disable_autolock=False to the keep.running mode? In the returned mode, also tell if the automatic screen lock is disabled.

fohrloop avatar Jan 19 '24 20:01 fohrloop

Comments which are merely debugging details mode under <details> to reduce noise

Confirmation that screen is locked if Screen Saver with password is not enabled

Just confirmed this again with another test. Left computer SetThreadExecutionState with ES_CONTINUOUS and ES_SYSTEM_REQUIRED for 8 hours. The laptop screen was shut down quite fast (I think I had 1 minute setting). After coming back, I moved mouse a bit which put the screen on, and everything was like I left it (script has been running the whole time). No screenlock and no passwords asked. Not sure if the screen lock could be there for other versions of Windows (This is Windows 10 version 22H2 build 19045.3324), or with some settings enabled.

I also have this in my "Sign in Options" settings: image the only another selectable option is Never. This gives the impression that Windows normally only requires sign in (=has screen lock on) when it returns from sleep. Could be that if the system never suspends (enters a form of sleep), it will never be returning from sleep, and thus, never requiring sign in.

Screen Saver Settings

I have also this in my Lock Screen -> Screen Saver Settings: image I.e. I do not have screen saver enabled. I'm not certain if this is the default setting or not. After setting the "On resume, display log-on screen" to True

image

and repeating the test, the screen blanked in about a minute, and after a bit longer than minute, when moving mouse a bit, there is a screen lock asking for a password.


Tested with SetThreadExecutionState with ES_CONTINUOUS + ES_SYSTEM_REQUIRED + ES_DISPLAY_REQUIRED and with the screen saver on with the "On resume, display log-on screen" selected. Ran 15 + minutes. As expected the screen saver / blank never occurred and I did not have to log in with my password.

How the Screen Saver Settings can be checked?

How the Screen Saver Settings can be checked?

I will write down here notes about how to check the Screen Saver Settings. I will be using a

  • PC1: home laptop (Windows 10 Pro version 22H2 build 19045.332) and a
  • PC2: company laptop (with company group policies and restricted settings access, and "Windows/company default settings", Windows 10 Enterprise version 22H2 build 19045.3803).

SUMMARY

Use Option 4, SystemParametersInfoW, to get the screen saver settings. This seems to be the most reliable as it supports both, getting values saved with the Settings UI and values enforced by a group policy (GPO).

Option 1: Directly from Lock Screen -> Screen Saver Settings

  • From the Settings UI ("Windows + I") -> Lock Screen -> Screen Saver Settings, as shown in an above comment. There is "On resume, display log-on screen" checkbox (True/False) and "Wait X minutes" (integer) setting.
  • Alternatively, running
Start ms-settings:lockscreen

in cmd/powershell, and selecting Screen Saver Settings.

PC1

All works

PC2

This option could not be used with the company laptop.. The "Screen Saver Settings" could not be opened. This has been disabled with a group policy.

Option 2: Reg query

With these settings

image

PC1

I get:

PS C:\Users\niko> reg query "HKEY_CURRENT_USER\Control Panel\Desktop" /v ScreenSaveActive

HKEY_CURRENT_USER\Control Panel\Desktop
    ScreenSaveActive    REG_SZ    1

PS C:\Users\niko> reg query "HKEY_CURRENT_USER\Control Panel\Desktop" /v ScreenSaverIsSecure

HKEY_CURRENT_USER\Control Panel\Desktop
    ScreenSaverIsSecure    REG_SZ    1

PS C:\Users\niko> reg query "HKEY_CURRENT_USER\Control Panel\Desktop" /v ScreenSaveTimeout

HKEY_CURRENT_USER\Control Panel\Desktop
    ScreenSaveTimeout    REG_SZ    60

I checked also to change the settings. These are my observations

  • The "HKEY_CURRENT_USER\Control Panel\Desktop" /v ScreenSaveTimeout is the "Wait X minutes" / screen saver idle timer timeout value in seconds.
  • The "HKEY_CURRENT_USER\Control Panel\Desktop" /v ScreenSaverIsSecure is 1 if the "On resume, display log-on screen" is selected (True).
  • The ScreenSaveActive is still a mystery.

PC2

It says "ERROR: The system was unable to find the specified registry key or value." on ScreenSaveTimeout and ScreenSaverIsSecure. It shows

HKEY_CURRENT_USER\Control Panel\Desktop
    ScreenSaveActive    REG_SZ    1

with ScreenSaveActive. The reason that there are no registry entries for ScreenSaveTimeout and ScreenSaverIsSecure is that they're set with GPOs (group policies), and the path is then "HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\Control Panel\Desktop"

Option 3:

Running this in Powershell:

Get-ItemProperty -Path "HKCU:\Control Panel\Desktop"

will show all the properties of the "HKCU:\Control Panel\Desktop", and adding -Name <someproperty> will list only that (e.g. -Name ScreenSaveTimeOut).

PC1

I can see

LockScreenAutoLockActive   : 0
ScreenSaveTimeOut          : 60
ScreenSaverIsSecure        : 1

in the output. The LockScreenAutoLockActive seems to be (based on quick Googling) some old entry, not related to any user setting.

PC2

The ScreenSaveTimeout and ScreenSaverIsSecure were found in the another list (set by group policies):

Get-ItemProperty "HKCU:\Software\Policies\Microsoft\Windows\Control Panel\Desktop"

ScreenSaveActive           : 1
ScreenSaveTimeOut          : 900
ScreenSaverIsSecure        : 1

Option 4: Using the SystemParametersInfoA from User32.dll

The SystemParametersInfoW function could be used to get screen saver related settings.

  • SPI_GETSCREENSAVEACTIVE -> Enable Screen Saver
  • SPI_GETSCREENSAVESECURE -> Password protect the screen saver
  • SPI_GETSCREENSAVETIMEOUT -> Get the screen saver idle timer timeout value (seconds)

Some quick-and-dirty python code MWE

import ctypes

# Determines whether the screen saver requires a password to display the Windows desktop. 
SPI_GETSCREENSAVESECURE = 0x0076
# Determines whether screen saving is enabled
SPI_GETSCREENSAVEACTIVE = 0x0010
# Retrieves the screen saver time-out value, in seconds.
SPI_GETSCREENSAVETIMEOUT = 0x000E


# Same as ctypes.wintypes.BOOL(). 
# Got an error with Python 3.12.1 (from Microsoft Store) when trying to access wintypes, so not using that for bools.
retval = ctypes.c_long()
pvparam = ctypes.byref(retval)
result = ctypes.windll.user32.SystemParametersInfoW(SPI_GETSCREENSAVEACTIVE, 0, pvparam, 0)
print(retval.value)

result = ctypes.windll.user32.SystemParametersInfoW(SPI_GETSCREENSAVESECURE, 0, pvparam, 0)
print(retval.value)

result = ctypes.windll.user32.SystemParametersInfoW(SPI_GETSCREENSAVETIMEOUT, 0, pvparam, 0)
print(retval.value)

This prints on my PC1:

1
1
60

and PC2:

1
1
900

Therefore, the SystemParametersInfoW is an API which may be used to access both, the settings set by user in the Screen Saver Settings (saved in "HKCU:\Control Panel\Desktop"), and the values set by the group policies (in "HKCU:\Software\Policies\Microsoft\Windows\Control Panel\Desktop). This seems to be the simplest API.

What are the Windows default settings

What are the Windows default settings

the screen saver has been disabled by default on Windows for years now. Most Windows 8/10 users don't use screen savers anymore, because the lock screen behavior and display power saving features makes it moot.

  • The windows on the Virtualbox also does not have sleep settings available (other than for the screen): image

  • I did try to run wakepy 0.7.2 keep.running (SetThreadExecutionState with system flag) for 18 minutes. Virtualbox was on full screen mode. The underlying Ubuntu did lock the screen at some point. After logging in to Ubuntu, I saw the test python script on the Win10 WM still running. There was no log-in required for Windows. I'm not sure if this this test helps at all and if things are different when running on a VM.

Testing with Windows 10 Pro (VM on Virtualbox):

  • System: Windows 10 Pro, version 22H2, Build 19045.2965

The Lock Screen settings are not available if Windows is not Activated.

image

The Get-ItemProperty -Path "HKCU:\Control Panel\Desktop" did not return ScreenSaveTimeout or ScreenSaverIsSecure, so I'm guessing Screen Saver is not somehow available..?

Also the SystemParametersInfoW returns only zeroes:

>>> pvparam = ctypes.byref(retval)
>>> result = ctypes.windll.user32.SystemParametersInfoW(SPI_GETSCREENSAVEACTIVE, 0, pvparam, 0)
>>> print(retval.value)
0
>>>
>>> result = ctypes.windll.user32.SystemParametersInfoW(SPI_GETSCREENSAVESECURE, 0, pvparam, 0)
>>> print(retval.value)
0
>>>
>>> result = ctypes.windll.user32.SystemParametersInfoW(SPI_GETSCREENSAVETIMEOUT, 0, pvparam, 0)
>>> print(retval.value)
0

Testing with the company laptop PC2 (Win 10 Enterprise)

  • PC2: company laptop (with company policies and restricted settings access, and "Windows/company default settings", Windows 10 Enterprise version 22H2 build 19045.3803).
  • Ran wakepy 0,7.2 keep.running mode, and left the computer on with the manual test script
  • After 34 minutes the computer still did not sleep. The test script was still printing with 2 second interval (as expected, as wakepy had set the thread executionstate)
  • The computer did blank the screen (it has 15 minute setting for it)
  • When moving the mouse, the system asked to log in with a password! Therefore, there is still some another setting, other than the found Screen Lock setting (ScreenSaverIsSecure from "HKCU:\Control Panel\Desktop") , which controls "Screen saver" type of behavior on enterprise /company owned Windows machines.

fohrloop avatar Jan 20 '24 19:01 fohrloop

@ccrutchf I'm continuing the discussion here. I added my current understanding about the Windows sleep & lock states and the possible transitions in a diagram here. Not sure if it is perfect, but it can serve as basis for discussion.

When the SetThreadExecutionState is used with ES_SYSTEM_REQUIRED (keep.running) it is not possible to automatically get to the Sleep state which takes the upper right corner away (let's also forget manual sleep):

image

Result: Now the screen lock state will only be entered if there is a screen saver with password enabled. Not sure if this a problem of not.

Alternative approaches

1. only prevent suspend

Document well that wakepy keep.running mode only prevents suspend, and it does not guarantee that the system will lock automatically, if a screensaver with password (or a screen lock process) is not enabled.

  • Pros: Easy to explain. Also, easy to implement cross-platform keep.running modes as only need to prevent automatic suspend. Otherwise, need to check which process or program is responsible for automatic screen lock, and then communicate with it and ask if it enabled. Not sure if that is trivial at all for all flavors of unix systems, for example when there is no desktop environment.
  • Cons: Perhaps nicer for user if wakepy keep.running mode could promise that screen will be locked automatically.

2. prevent suspend + guarantee automatic screen lock

wakepy keep.running mode prevents suspend and guarantees that system will lock the screen automatically.

  • Pros & Cons exactly opposite of 1. Slower to get new modes implemented as need to figure out also way to guarantee the automatic screen lock.

3. separate mode for guarantee automatic screen lock

wakepy keep.running mode which only prevents suspend. Add another context manager which guarantees automatic screen lock.

  • Pros: Easy to explain. Easy to expand wakepy to new platforms as keep.running and ensure.screenlock modes could be developed independently. Could also possibly provide some convenience wrapper for the hybrid mode keep.running(ensure_screenlock=True) or keep.running_autoscreenlock()

4. others?

fohrloop avatar Jan 29 '24 21:01 fohrloop

@ccrutchf

PowerToys sets the precedent of not worrying about locking: https://github.com/microsoft/PowerToys/blob/c406a15099584a069374ab5531e1f711c5b53315/src/modules/awake/Awake/Core/Manager.cs

I interpreted your comment as "PowerToys does not lock the screen automatically but just uses SetThreadExecutionState to prevent automatic suspend. Because PowerToys is widely used, it can be used as an example, and people are used to that behavior. In other words, perhaps wakepy should not guarantee automatic screen lock". I agree with that :)

They even have an issue about this: https://github.com/microsoft/PowerToys/issues

Search 27178.

I checked it. It says

  1. Set Awake to keep awake indefinitely + keep screen on.

Let's look at the state transition diagram. Since ES_SYSTEM_REQUIRED and ES_DISPLAY_REQUIRED are both on, the automatic idle timer based transitions (ScreenSaveTimeout and suspend timer) are not possible:

image

  1. Put your computer to sleep.

This means that manual transition from Working to Sleep.

  1. Somehow awake the computer (i.e. via mouse) but do not unlock it (keep lock screen).

Now the system gets to the lock screen. This means that the user had "Require sign-in when PC wakes up from sleep" setting enabled. Now if you look at the situation, since ES_SYSTEM_REQUIRED and ES_DISPLAY_REQUIRED are both set, there are only two ways to get out of the lock screen: (4) unlocking with password (5) putting system manually back to sleep (with power button? not sure if possible otherwise)

image

NB: if Keep screen on is disabled PC will go to sleep again in 2 mins.

I would like to test if this is actually what happens. Note that is "keep screen" is disabled (there is no ES_DISPLAY_REQUIRED flag), the options look like this (additional option 6):

image

Commentary for PowerToys issue 27178

I would guess that most likely the route (6) occurs then automatically after the ScreenSaverTimeout. Then, as it is very common to have "blank" as the Screen Saver, the laptop screen will simply turn black. But the computer is not in sleep state, as it cannot enter sleep state automatically since ES_SYSTEM_REQUIRED prevents it. So without testing myself or seeing more proof, my guess is that what is actually happening is that the system ends up in the Screen saver state with "blank" screen saver.

PC should be able to go to sleep again in 2 mins (default value for Windows 10) if it hasn't been unlocked.

^-- If automatic sleep is prevented, the system will not automatically sleep. It will not matter if the starting state is the screen lock or the working state. I don't think that PowerToys (or wakepy) should disable itself when lock screen is turned on. Otherwise, when someone just starts a long running process with keep.running mode on, and locks the screen (for security), the mode would be terminated immediately and the process would not long more than few minutes.

Then other question is that if the mode should terminate itself if the system is put manually to sleep. I think it should not. The reasoning is that if something is running before (manual) sleep, it should be running after system resumes from sleep. That is how all programs work. Maybe there are some rare use cases where that kind of behavior is beneficial, but that should not be the automatic behaviour in wakepy, I think. And providing similar user experience cross-platform would probably be quite difficult. I guess that in a potential solution one somehow listen the system when it's going to sleep/resuming from sleep.

fohrloop avatar Jan 29 '24 21:01 fohrloop

Labeled this as a bug although not sure if this can be thought of a bug in wakepy or as a feature of the Windows operating system.

fohrloop avatar Apr 29 '24 14:04 fohrloop

A possible solution to this should consider also other systems, which may only lock screen automatically on certain situations. For example, on KDE Plasma on Kubuntu, screenlock is started when returning from sleep, or when it's enabled with a timer and the idle timer reaches the set value. It's possible that people have this automatic screensaver timer completely disabled. What should happen then? Should wakepy at least give a warning by default? See this comment on reddit.

fohrloop avatar Jun 03 '24 21:06 fohrloop