中断(不阻止启动)屏幕保护程序

mor*_*dra 43 python winapi windows-10

我试图通过像这样移动光标以编程方式中断屏幕保护程序:

\n
win32api.SetCursorPos((random.choice(range(100)),random.choice(range(100))))\n
Run Code Online (Sandbox Code Playgroud)\n

它失败并显示以下消息:

\n
pywintypes.error: (0, 'SetCursorPos', 'No error message is available')\n
Run Code Online (Sandbox Code Playgroud)\n

仅当屏幕保护程序正在运行时才会出现此错误。

\n

提出此请求的原因是计算机仅用于通过蓝牙设备(通过Python程序)输入数据。当BT设备向计算机发送数据时,屏幕保护程序不会中断(这意味着我看不到BT设备发送的数据)。因此,当Python程序从BT设备接收数据时,它也应该中断屏幕保护程序。

\n

我已经看到了几种关于如何防止屏幕保护程序启动的解决方案(在我的情况下这不是合适的解决方案),但没有看到关于如何中断正在运行的屏幕保护程序的解决方案。如何使用 Windows\xc2\xa010 和 Python 3.10 执行此操作?

\n

Ano*_*ard 52

Windows 操作系统具有对象层次结构。层次结构的顶部是“Window Station”。其正下方是“桌面”(不要与桌面文件夹混淆,甚至不要与显示该文件夹图标的桌面窗口混淆)。您可以在文档中阅读有关此概念的更多信息。

我提到这一点是因为通常只有一个桌面可以在任何给定时间接收和处理用户输入。而且,当 Windows 由于超时而激活屏幕保护程序时,Windows 会创建一个新桌面来运行屏幕保护程序。

这意味着与任何其他桌面关联的任何应用程序(包括 Python 脚本)将无法将输入发送到新桌面,而无需进行一些额外的工作。这项工作的性质取决于几个因素。假设最简单的情况,创建的屏幕保护程序没有“恢复时显示登录屏幕”,并且没有通过远程连接或本地用户登录创建其他 Window Station,那么您可以向 Windows 请求活动桌面,附加将 Python 脚本复制到该桌面,移动鼠标,然后恢复到之前的桌面,以便脚本的其余部分按预期工作。

值得庆幸的是,执行此操作的代码比解释更容易:

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()
Run Code Online (Sandbox Code Playgroud)

但是,如果屏幕保护程序由于选择了“恢复时显示登录屏幕”而在单独的 Window Station 上运行,或者其他用户通过物理控制台连接或已远程连接,则连接到并附加到活动桌面将需要提升 Python 脚本的权限,即便如此,根据其他因素,它可能需要特殊权限。

虽然这可能对您的具体情况有所帮助,但我将添加一般情况下的核心问题可能更正确地定义为询问“如何在屏幕保护程序不阻止该通知的情况下通知用户某些状态?”。这个问题的答案不是“导致屏幕保护程序结束”,而是“使用类似SetThreadExecutionState()with的东西ES_DISPLAY_REQUIRED来阻止屏幕保护程序运行。并显示一个全屏最顶层窗口,显示当前状态,以及何时您想要提醒用户,闪烁引人注目的图形和/或播放声音来引起他们的注意”。

使用 tkinter 显示窗口如下所示:

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)
Run Code Online (Sandbox Code Playgroud)

虽然需要做更多工作,但这样做可以解决设置“恢复时显示登录屏幕”的安全桌面的问题,并且还可以防止系统进入睡眠状态(如果配置为这样做)。它通常只是允许应用程序更清楚地传达其意图。


Pav*_*rev 21

SetCursorPos失败是因为屏幕保护程序运行时光标可能设置为 NULL。

不要移动光标,而是尝试找到当前屏幕保护程序可执行路径并终止该进程。我想,这将是一个很好的解决方案。

  1. 您可以检查 Windows 注册表记录以获取屏幕保护程序的文件名 ( HKEY_USERS\.DEFAULT\Control Panel\Desktop\SCRNSAVE.EXE( msdn )

  2. 或者您可以检查当前正在运行的进程列表以查找具有.scr扩展名的进程

TerminateProcess然后只需使用或只是终止进程os.system('taskkill /IM "' + ProcessName + '" /F')

  • @KenWilliams 不,屏幕保护程序将再次启动。Windows 在特定时刻启动屏幕保护程序,就像任何其他应用程序一样,当光标移动时,屏幕保护程序会自行终止(某些非默认屏幕保护程序甚至可能不会在鼠标移动时退出并提供一些交互式 UI) (3认同)
  • 终止该进程是否意味着屏幕保护程序在另一段时间不活动后不会恢复?如果是这样,这听起来并不适合 OP 的用例。 (2认同)