43

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?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
mortpiedra
  • 687
  • 4
  • 13
  • 4
    I'm a little confused as to why you want to do this. What will be the program's condition for interrupting the screensaver? I had understood that the point of a screensaver is to reduce power consumption *while the computer is not being attended by a human*. Why not let a human interrupt it, to indicate that the computer is attended again? – Karl Knechtel May 29 '22 at 16:33
  • 4
    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. Note: previously I had this running on a Raspi and there screensaver interrupt worked by 'default'. – mortpiedra Jun 14 '22 at 18:22
  • 3
    So... basically you want the screen to be off normally, but turn on when BT data is sent? And you're going to be around all the time when the data might be sent... staring at a screensaver until it actually happens? I'm still struggling to understand the use case. – Karl Knechtel Aug 07 '22 at 22:52
  • 3
    Does https://stackoverflow.com/questions/864129 help? It's written for C#, but I assume you'd be using the same Windows API calls just through a Python wrapper instead of a C# wrapper. I got this as the first result by trying `interrupt screensaver` [in a search engine](https://duckduckgo.com/?q=interrupt+screensaver); I'm a little confused as to how you went about your [research](https://meta.stackoverflow.com/questions/261592) if you didn't find this but had no problem finding stuff about disabling screensavers. – Karl Knechtel Aug 07 '22 at 22:54
  • 15
    This seems an XY problem. Why do you need to see in the screen the data being sent by the bluetooth device immediately is received? What are you trying to do that requires immediate attention and can't be solved by simply disabling the screensaver or using a sound clue instead? – Braiam Aug 08 '22 at 10:47
  • Is the desktop locked during the screensaver? You might need to turn that off. Unclear why you have a screensaver in the first place. – Charlieface Aug 08 '22 at 12:10
  • 3
    @Braiam I imagine someone would be around, but with the screen only at peripheral vision, doing something else. And would like to have the screen to turn on, so the data can be glanced quickly, then move on. Having only sound but not the screen turn on won't enable this use case, there might not be any input to the screen other than the bluetooth device. – justhalf Aug 08 '22 at 12:51
  • Maybe you already tried this one, but have you run your code with *Administrative Privileges*? – D.Kastier Aug 08 '22 at 14:31
  • 8
    This question is being [discussed on Meta](https://meta.stackoverflow.com/questions/419686/got-no-helpful-answers-got-too-low-reputation-for-a-bounty-just-give-up) – TylerH Aug 08 '22 at 15:10
  • 1
    You could also try using a library like [wakepy](https://github.com/np-8/wakepy) – dan1st Aug 09 '22 at 08:12
  • PowerPoint prevents the screen saver from coming on during a presentation, so it's programmatically possible. This discusses options: https://stackoverflow.com/questions/3665332/how-do-i-prevent-screen-savers-and-sleeps-during-my-program-execution – Flydog57 Aug 09 '22 at 16:07
  • @Braiam Why did you remove the [windows10] tag? The OP clearly mentions Windows 10 in their question. – cigien Aug 09 '22 at 18:33
  • @cigien because it also mentions screensaver and I'm not adding such tag. Winapi implies already Windows API, and answerers should be advised the differences between all versions of Windows were relevant. (Which isn't it on this nor other cases, where it's rarely removed something from the api) TL;dr: it's a irrelevant detail. – Braiam Aug 09 '22 at 18:54
  • duplicate of : https://stackoverflow.com/questions/864129/interrupt-an-active-screensaver-programmatically – jmullee Aug 09 '22 at 18:58
  • 1
    The windows-10 tag is relevant. The winapi tag implies Windows API, yes, but the windows-10 tag helps focus on the actual platform the OP is asking about. – Grodriguez Aug 09 '22 at 20:49
  • @jmullee No, that is not a question about Python. – Unmitigated Aug 09 '22 at 22:46
  • 1
    **Mod note:** The windows-10 tag is fine. The question is about the Windows API on specifically Windows 10, making it very much relevant to the question. Leave it alone (and to everyone else: flag if it's removed again anyway) – Zoe Aug 10 '22 at 07:13
  • duplicate of: https://stackoverflow.com/questions/27197544/program-prevents-mouse-movement – Welbog Aug 10 '22 at 11:50
  • 2
    @Welbog Why post a comment that the question is a duplicate, yet not vote to close the question as a duplicate? – skomisa Aug 10 '22 at 16:53
  • 1
    @KarlKnechtel I appreciate your involvement and will try to explain. The application is in a production environment (of ice cream) and there are screens around (walls and ceiling), but no key-boards. The data from various production relevant BT devices (machines, scanners, scales, printers) are displayed on separate/dedicated screens. We are very small and try to use IT technology as a tool to help us survive/thrive, but have to make this technology easy and efficient to use for those working in production (who mostly have no knowledge/interest/patience with IT-technology). Helps? – mortpiedra Aug 11 '22 at 13:24
  • @Braiam pls see my answer to Karl-Knechtel – mortpiedra Aug 11 '22 at 13:24
  • @KarlKnechtel I did indeed see the proposal in C that you referenced, but since I know nothing about C (and barely anything about Python!) I am very reluctant to go down that path. – mortpiedra Aug 11 '22 at 13:28
  • @Flydog57 Please note the thread title clearly states that I do not want to prevent the screensaver from starting, but interrupt it once it has started. – mortpiedra Aug 11 '22 at 13:31
  • 1
    why does the system need to have a screensaver? – user253751 Aug 11 '22 at 14:17
  • @ZoestandswithUkraine Is it established that the version of Windows actually matters for the relevant API calls? I'm under the impression that WinAPI questions *usually* aren't version-dependent, because Microsoft bends over backwards to ensure backwards compatibility. – Karl Knechtel Aug 12 '22 at 00:06
  • @KarlKnechtel The behavior mentioned in the question was introduced with (I think) Windows 7, and the answer I provided will only work with XP onward (basically, any NT flavor of Windows). While they maintain backwards compatibility, they do introduce new features, behavior, and APIs. So, it's a "recent versions of Windows" question, whatever the tag for that should be. – Anon Coward Aug 12 '22 at 16:53
  • @user253751 because we have several screens in a small space for various devices which are not operated simultaneously. Hence, in order to make life easier for the operator the screen showing data (as opposed to a screensaver) is the screen on which to concentrate. – mortpiedra Aug 16 '22 at 13:29
  • 2
    @mortpiedra Aha, there we have the XY problem. The X problem is not about a screensaver at all. It's basically about showing something that will not catch the attention of the operator when there is no new data to be shown. – Abdul Aziz Barkat Aug 16 '22 at 13:36
  • @mortpiedra you could make your own app just blank itself out when there's nothing interesting to display, then, no? – user253751 Aug 16 '22 at 13:51
  • @AbdulAzizBarkat yes, exactly – mortpiedra Aug 17 '22 at 16:30
  • @user253751 I suppose so yes, but I never thought about it (probably since I have no idea how to do it). It sounds to me awfully much like learning a whole new heap of technologies and I am already drowning trying to wrap my head around GAS and Python. If you have a suggestion, it would be much appreciated of course. – mortpiedra Aug 17 '22 at 16:35

3 Answers3

51

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.

Anon Coward
  • 9,784
  • 3
  • 26
  • 37
21

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\.DEFAULT\Control Panel\Desktop\SCRNSAVE.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')

Voo
  • 29,040
  • 11
  • 82
  • 156
Pavel Shishmarev
  • 1,335
  • 9
  • 24
  • 2
    Will killing the process mean that the screensaver won't resume after another period of inactivity? If so, that doesn't sound like it fits the OP's use case. – Ken Williams Aug 08 '22 at 22:28
  • 3
    @KenWilliams No, the screensaver will start again. Windows starts a screensaver process just like any other application in a specific moment and when the cursor is moved, the screensaver just terminates by itself (some non default screensavers even may not exit on mouse move and offer some interactive UI) – Pavel Shishmarev Aug 09 '22 at 07:40
1

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.

Voo
  • 29,040
  • 11
  • 82
  • 156