Interrupt (NOT prevent from starting) screensaver

Question:

I am trying to programmatically interrupt the screensaver by moving the cursor like this:

win32api.SetCursorPos((random.choice(range(100)),random.choice(range(100))))

And it fails with the message:

pywintypes.error: (0, 'SetCursorPos', 'No error message is available')

This error only occurs if the screensaver is actively running.

The reason for this request is that the computer is ONLY used for inputting data through a bluetooth device (via a Python program). When the BT device sends data to the computer the screensaver is not interrupted (which means I cannot see the data the BT device sent). Thus, when the Python program receives data from the BT device it is also supposed to interrupt the screensaver.

I have seen several solution on how to prevent the screensaver from starting (which are not suitable solutions in my case), but none on how to interrupt a running screensaver. How can I do this, using Windows 10 and Python 3.10?

Asked By: mortpiedra

||

Answers:

SetCursorPos is failing because the cursor is probably set to NULL while the screensaver is running.

Instead of moving the cursor, try to find the current screensaver executable path and just kill the process. I think, this will be a fine solution.

  1. you can check the Windows Registry record to obtain a filename of the screensaver (HKEY_USERS.DEFAULTControl PanelDesktopSCRNSAVE.EXE (msdn)

  2. or you can check currently running processes list to find the one with .scr extension

Then just kill the process using TerminateProcess or just os.system('taskkill /IM "' + ProcessName + '" /F')

Answered By: Pavel Shishmarev

The Windows operating system has a hierarchy of objects. At the top of the hierarchy is the "Window Station". Just below that is the "Desktop" (not to be confused with the desktop folder, or even the desktop window showing the icons of that folder). You can read more about this concept in the documentation.

I mention this because ordinarily only one Desktop can receive and process user input at any given time. And, when a screen saver is activated by Windows due to a timeout, Windows creates a new Desktop to run the screen saver.

This means any application associated with any other Desktop, including your Python script, will be unable to send input to the new Desktop without some extra work. The nature of that work depends on a few factors. Assuming the simplest case, a screen saver that’s created without the "On resume, display logon screen", and no other Window Station has been created by a remote connection or local user login, then you can ask Windows for the active Desktop, attach the Python script to that Desktop, move the mouse, and revert back to the previous Desktop so the rest of the script works as expected.

Thankfully, the code to do this is easier than the explanation:

import win32con, win32api, win32service
import random
# Get a handle to the current active Desktop
hdesk = win32service.OpenInputDesktop(0, False, win32con.MAXIMUM_ALLOWED);
# Get a handle to the Desktop this process is associated with
hdeskOld = win32service.GetThreadDesktop(win32api.GetCurrentThreadId())
# Set this process to handle messages and input on the active Desktop
hdesk.SetThreadDesktop()
# Move the mouse some random amount, most Screen Savers will react to this,
# close the window, which in turn causes Windows to destroy this Desktop
# Also, move the mouse a few times to avoid the edge case of moving
# it randomly to the location it was already at.
for _ in range(4):
    win32api.SetCursorPos((random.randint(0, 100), random.randint(0, 100)))
# Revert back to the old desktop association so the rest of this script works
hdeskOld.SetThreadDesktop()

However, if the screen saver is running on a separate Window Station because "On resume, display logon screen" is selected, or another user is connected either via the physical Console or has connected remotely, then connecting to and attaching to the active Desktop will require elevation of the Python script, and even then, depending on other factors, it may require special permissions.

And while this might help your specific case, I will add the the core issue in the general case is perhaps more properly defined as asking "how do I notify the user of the state of something, without the screen saver blocking that notification?". The answer to that question isn’t "cause the screen saver to end", but rather "Use something like SetThreadExecutionState() with ES_DISPLAY_REQUIRED to keep the screen saver from running. And show a full-screen top-most window that shows the current status, and when you want to alert the user, flash an eye-catching graphic and/or play a sound to get their attention".

Here’s what that looks like, using tkinter to show the window:

from datetime import datetime, timedelta
import ctypes
import tkinter as tk

# Constants for calling SetThreadExecutionState
ES_CONTINUOUS = 0x80000000
ES_SYSTEM_REQUIRED = 0x00000001
ES_DISPLAY_REQUIRED= 0x00000002

# Example work, show nothing, but when the timer hits, "alert" the user
ALERT_AT = datetime.utcnow() + timedelta(minutes=2)

def timer(root):
    # Called every second until we alert the user
    # TODO: This is just alerting the user after a set time goes by,
    #       you could perform a custom check here, to see if the user
    #       should be alerted based off other conditions.
    if datetime.utcnow() >= ALERT_AT:
        # Just alert the user
        root.configure(bg='red')
    else:
        # Nothing to do, check again in a bit
        root.after(1000, timer, root)

# Create a full screen window
root = tk.Tk()
# Simple way to dismiss the window
root.bind("<Escape>", lambda e: e.widget.destroy())
root.wm_attributes("-fullscreen", 1)
root.wm_attributes("-topmost", 1)
root.configure(bg='black')
root.config(cursor="none")
root.after(1000, timer, root)
# Disable the screen saver while the main window is shown
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED)
root.mainloop()
# All done, let the screen saver run again
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS)

While more work, doing this will solve issues around the secure desktop with "On resume, display logon screen" set, and also prevent the system from going to sleep if it’s configured to do so. It just generally allows the application to more clearly communicate its intention.

Answered By: Anon Coward

This is a classic XY problem: Say, you manage to stop the screensaver from turning up on your machine/test setup. But there are further questions:

  • What happens if your program runs on a terminal server that doesn’t have an UI session?
  • Does your solution work if the power saving settings are set in such a way that they put the computer to sleep after a certain amount of time?
  • Will it work with future windows versions? With different subproducts? (the creative "look at this undocumented registry key and then kill some random process" solution seems destined for this)

Who knows and definitely hard to test.

What you really need is a way to tell the OS "hey I’m busy and keep the session active even if your normal heuristics would tell you that the user is away". This is a standard problem which video players and presentation software faces all the time.

The standard solution is to use SetThreadExecutionState with something along the lines of ES_DISPLAY_REQUIRED | ES_CONTINUOUS (and possibly other flags as well – the documentation is quite reasonable there) at the start of the program.

Raymond Chen has written about this in the past (no surprise there).

Note that this doesn’t stop an already active screensaver – this is generally not a problem, because you can set the flag at startup (or when the intended action is triggered). It also doesn’t stop the user from putting the computer manually to sleep, but that’s something you shouldn’t generally disable.

Answered By: Voo
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.