VC++实现会议中阻止屏保阻止系统自动关闭屏幕阻止系统待机
Posted dvlinker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VC++实现会议中阻止屏保阻止系统自动关闭屏幕阻止系统待机相关的知识,希望对你有一定的参考价值。
目录
4、调用API函数SystemParametersInfo关闭/启用屏保,但存在问题
5、使用API Monitor监测到目标程序对API的调用,找到了问题的突破口
5.2、使用API Monitor监测参考软件对API函数的调用
5.3、到MSDN上查看SystemParametersInfo API函数说明,找到关键参数
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585 最近有人反馈,使用我们的会议软件进入会议后,鼠标一直没有操作,系统就自动进入了屏保状态,打断了当前正在进行中会议。用户觉得这样的体验很不好,在进入会议状态后,软件应该自动阻止屏保、阻止系统自动关闭屏幕、阻止系统自动待机。本文就来讲述实现这些功能的完整过程。
1、概述
在Windows系统中,当鼠标较长时间不动时,会触发系统屏保,触发系统自动关闭屏幕,触发系统待机。在某些场景下,这些动作会打断电脑上正在进行的事务,比如我们当前正在使用会议软件开会,打断会议是不允许的,用户体验也是非常差的,所以我们要在进入开会状态后,阻止系统屏保,阻止子系统自动关闭屏幕,阻止系统自动待机。接下来,我们探究一下实现这一功能的方法,顺便也详细介绍一下API Monitor工具软件的使用。
2、设置屏幕保护程序,修改自动关闭显示器和待机的时间
系统进入屏保状态、系统自动关闭显示器、系统自动待机,都是鼠标长时间不动(没操作)触发的。本文以Win10系统为例,介绍一下设置屏幕保护程序、修改自动关闭显示器和待机时间的入口。后面我们在对上述功能进行测试验证之前,也需要手动设置一下。
2.1、设置屏保程序
Win10系统中,屏保程序在默认情况下是关闭的,如果要启用屏保,则需要手动打开。右键单击桌面空白处,弹出桌面右键菜单:
点击个性化菜单项,进入个性化设置页面,然后点击左边的“锁屏界面”,打开对应子页面,向下滚动,即可找到“屏幕保护程序设置”按钮:
点击即打开如下的屏幕保护程序设置页面:
默认情况下屏保程序是无,可以设置屏保的样式,设置样式后就启动屏保了。还可以设置屏保的启动时间,即鼠标多久不动就自动启用屏保程序。有些用户不会开启屏保,有些用户则喜欢使用屏保。
2.2、修改自动关闭显示器和待机的时间
在Win10系统中,默认情况下,设置了自动关闭显示器和待机的默认时间。用户可以自定义自动关闭显示器和打击的时间,也可以关闭它们。右键单击桌面空白处,弹出桌面右键菜单:
点击“显示设置”菜单项,进入显示设置页面,然后点击左边的“电源和睡眠”,打开对应子页面,即可看到自动关闭显示器和自动待机的时间,可以对时间进行修改,如下所示:
也可以将它们设置为从不,就是关闭自动关闭显示器和待机,用户可以根据自己的喜好去设置。
3、通过屏保的通知消息来阻止屏保
其实,若干年前已经添加了阻止屏保的处理代码,如下所示:
if ( WM_SYSCOMMAND == message )
if ( SC_SCREENSAVE == wParam )
if ( ::IsInConf() )
return TRUE;
当系统即将进入屏保状态时会给窗口投递WM_SYSCOMMAND消息,消息中的wParam参数为SC_SCREENSAVE,现有代码通过在程序的主窗口中拦截这个消息及wParam值,直接return掉,以阻止屏保。
但从用户反馈的问题来看,应该是代码没有生效,或者代码写的有问题。于是测试的同事进行详细的验证测试,发现只有当主窗口处于激活状态时,阻止屏保才能生效。通过添加打印日志得知,只有主窗口处于激活状态时才能收到系统即将屏保的通知消息。当鼠标点击其他窗口,焦点落到其他窗口上时,主窗口处于非激活状态,是收不到消息的。如此看来,目前这种被动接收即将屏保的通知消息的实现方式是有问题的。
4、调用API函数SystemParametersInfo关闭/启用屏保,但存在问题
于是想,既然这种被动通知的方式有缺陷,那我们能不能找到一种主动设置的方式,搜索下来得知可以尝试使用SystemParametersInfo API接口来实现,但调用该函数的具体参数不是很确定。
大体上是这样,调用SystemParametersInfo 时第一个参数设置SPI_SETSCREENSAVEACTIVE,表示设置屏保;第二个参数设置为FALSE,表示关闭屏保。第二个参数设置为TRUE,则表示启动屏保。
4.1、初步确定处理策略
我们大概地定下了这样的处理策略:在进入时将屏保禁用,然后在退出会议时将恢复屏保。具体的做法是,入会时先调用SystemParametersInfo接口,设置SPI_GETSCREENSAVEACTIVE去获取系统有没有设置屏保。如果系统启用了屏保,就再次调用SystemParametersInfo接口,第一个参数设置为SPI_SETSCREENSAVEACTIVE,第二个参数设置FALSE,将屏保禁用,代码如下:
// 先获取是否启用屏幕保护
SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_bScreenSaveActive, 0);
if (m_bScreenSaveActive)
// 阻止屏保
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);
退会时,如果入会前屏保是开启的,则调用SystemParametersInfo接口,第一个参数设置为SPI_SETSCREENSAVEACTIVE,第二个参数设置TRUE启动屏保,如下:
if (m_bScreenSaveActive)
// 取消阻止屏保
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, NULL, 0);
于是将这个实现思路和测试主管讨论了一下,我们决定采用这种主动出击的方式,入会时禁用屏保,退会时恢复屏保。但测试主管提出一个问题,假设入会时我们禁用了屏保,如果入会后软件发生了崩溃,或者用户通过任务管理器强杀进程,那么就没有机会恢复屏保设置了。这样就会引发一个问题,系统本来可以屏保的,但因为运行我们的软件后就变得不能屏保了,就相当于我们软件修改了用户对系统的配置,这是很不好的行为。
4.2、启动监控进程去监控主进程
这个问题其实我们也考虑到了。对于软件崩溃,我们的异常捕获模块CrashReport能捕获到,所以我们大概可以感知到,但有少数崩溃我们的异常捕获模块是捕获不到的。如果用户在任务管理器中强杀进程,我们也是感知不到的!我们是不是可以启动一个监控进程,来实时监测我们的主进程有没有退出?答案是肯定的,我们可以做到的,以前我们说过一种方法,使用WaitForSingleObject函数就能实现,代码如下:(在启动这个监控进程时把主进程的进程id传过去)
// 监控主进程
unsigned __stdcall MonitorMainProcess(void * pParam )
DWORD dwProcessId = (DWORD)pParam;
HANDLE hProcess = OpenProcess( SYNCHRONIZE, FALSE, dwProcessId );
if(hProcess == NULL)
ExitProcess(-1);
// 设置INFINITE无限等待
WaitForSingleObject( hProcess, INFINITE );
CloseHandle( hProcess );
// WaitForSingleObject返回了,就表示主进程已经退出,我们就感知到了
// ...
上述代码通过主进程的id得到主进程的句柄,然后调用WaitForSingleObject等待主进程退出,如果主进程退出,WaitForSingleObject就会返回,否则一直会阻塞在WaitForSingleObject函数上(设置了无限等待)。
我们在启动软件时启动一个监控进程,来实时监测主进程,一旦主进程退出,不管是正常退出,还是崩溃闪退,或者是强杀进程,软件都能感知到。我们可以在入会时记录一下我们是否禁用了屏保,并将该操作记录到配置文件中,然后退会时我们恢复屏保,把已经恢复的标记记录到配置文件中,这是正常情况。对于崩溃或者强杀进程时,配置文件中是没有恢复标记的。所以在监控进程中,我们只要监控到主进程退出了,我们不管它是正常退出,还是异常崩溃闪退,亦或是进程被强杀,我们都去配置文件中查看一下是否要恢复屏保,如果需要,就去恢复一下。
4.3、系统强行关机的情况无法处理
至此,似乎我们已经考虑的非常周全了,但有种情况我们还是无能为力,没法去补救的。比如用户在会议过程中机器卡顿,用户强行关机了,我们的监控程序也没有机会去处理了,这种情况我们没法去补救的,所以还是有问题的。
5、使用API Monitor监测到目标程序对API的调用,找到了问题的突破口
目前的处理办法似乎是存在一些问题的,于是想拿来一款主流会议软件来参考,看看他们的实现效果是什么样的,并用API Monitor监测一下该参考软件对系统API函数的调用情况。
5.1、参考软件的实现效果测试
经测试,这款软件在入会后禁用了屏保,退会后会恢复系统屏保设置。后来我们做了进一步的测试,强杀进程后,保持鼠标不动,系统不能再自动屏保了。但系统重启后,在鼠标不动时,系统还是能进入屏保状态的!
这意味着什么?入会时禁用屏保仅仅是作用于当前登录的用户,不会保存到系统全局配置里面去的,系统重启后还是可以屏保的。如果是强杀进程导致的,这是用户不当操作导致的;如果是程序在会议过程中发生崩溃,这个没有正常恢复也情有可原。如果能实现参考软件这样的效果,肯定也是可以接受的。
5.2、使用API Monitor监测参考软件对API函数的调用
于是想启动API Monitor工具,监测一下该参考软件是否是调用SystemParametersInfo API函数实现的,是不是设置了不同的参数去控制达到上效果的。
启动API Monitor后,先在左边的API函数列表中搜索到SystemParametersInfo函数,勾选上;然后在进程列表中找到目标进程,右键单击目标进程,在弹出的右键菜单中点击Start Monitoring,启动监测,操作步骤如下图所示:
但在进程列表中该参考软件有多个进程(通过进程名称和图标可以看出),不知道哪个是与会议相关的进程。这些进程都要监控,有两个办法。一个是按照上图操作将多个进程一个一个添加到监控列表中,二是在通过API Monitor中点击“Monitor New Process”按钮去启动参考软件:
这样API Monitor就可以监控该参考软件启动的所有进程了。
但好像有时即使通过“Monitor New Process”按钮启动软件,也不会把启动程序的所有进程加入到监控列表中,所以还是需要手动添加到监控进程列表中的。
于是使用参考软件创建一个会议,加入到会议中,然后在监测的进程列表中,切换点击该参考软件的多个进程,看看哪个进程调用SystemParametersInfo函数时传入了SPI_SETSCREENSAVEACTIVE参数,可以按下Ctrl+F快捷键调出搜索窗口,在监测结果中以SPI_SETSCREENSAVEACTIVE为关键字进行搜索,果然监测到了,如下:
- 上图中有监控进程列表,有多个进程时需要切换查看。
- 点击监测结果列表中的某行记录,下方callstack子窗口中会显示该行记录对应的函数调用堆栈的(只显示4帧)。
我们单独对监测结果列表截图,可以看的更清楚一点:
在退出会议时,可以监测到又调用了SystemParametersInfo函数(第一个参数传SPI_SETSCREENSAVEACTIVE,第二个参数传TRUE(1),TRUE即表示开启屏保),恢复系统的屏保,如下:
5.3、到MSDN上查看SystemParametersInfo API函数说明,找到关键参数
API Monitor工具开启监测后不仅看到调用了什么API函数禁用屏保,还能看到调用该API函数时传递了什么参数。参考软件的实际运行效果是,入会时禁用屏保仅仅是作用于当前登录的会话,不会保存到系统全局配置里面去的,所以系统重启后读取系统全局配置,系统全局配置中是启用屏保的。
那我们回过头来看这个API函数在MSDN上的说明,对比API Monitor中调用SystemParametersInfo函数设置的参数,看看是否有不写入系统全局配置文件的参数。
SystemParametersInfo的fWinIni参数,从参数名称上看好像是和Windows系统配置文件有关,于是查看该参数的说明:
说明中指出,此字段决定是否写入到系统user profile中。从API Monitor监测到参考软件调用SystemParametersInfo时给fWinIni参数设置的值为0,综合参考软件的实际表现,所以确定fWinIni字段就是设置是否写入到系统全局配置文件中的。
MSDN上对SystemParametersInfo函数的最后一个参数fWinIni的说明不够清晰,比较隐晦,我们没搞清楚其对应的真实含义,通过这次问题我们才结合实际现象才搞懂该参数的真实用途。
参考软件在加入会议时调用SystemParametersInfo禁用屏保,给fWinIni参数设置的值为0,那么此次禁止屏保,仅仅是作用于当前登录的会话,不会保存到系统全局配置里面去的。如果入会后出现崩溃或者强杀进程,没机会恢复屏保,但当前禁用屏保只作用于当前登录的会话,系统重启后系统会从系统配置文件中读取到屏保启用的配置项,系统还是能继续屏保的。所以我们软件也可以这样处理的。
入会时调用SystemParametersInfo函数,传入SPI_GETSCREENSAVEACTIVE,获取系统有没有设置屏保,如果设置了屏保,就再调用SystemParametersInfo函数,传入SPI_SETSCREENSAVEACTIVE和FALSE参数,临时取消系统屏保,代码如下:
// 先获取是否启用屏幕保护
SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_bScreenSaveActive, 0);
if (m_bScreenSaveActive)
// 阻止屏保
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);
退会时,如果之前取消了屏保,则再调用SystemParametersInfo函数,传入SPI_SETSCREENSAVEACTIVE和TRUE参数,恢复系统屏保,代码如下:
if (m_bScreenSaveActive)
// 取消阻止屏保
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, NULL, 0);
6、查找阻止系统自动关闭显示器和阻止系统自动待机的实现方法
会议中除了阻止屏保,还需要阻止自动关闭屏幕、阻止待机,这两个功能我们是否也能使用主动出击的方式主动去临时禁用呢?确实是可以的,网上搜到了阻止自动关闭屏幕、阻止自动待机的办法,调用SetThreadExecutionState系统API函数设置程序运行状态即可,这样系统就不会自动关闭屏幕、自动待机了。
于是到MSDN上查看SetThreadExecutionState API函数的说明,该函数就是用来通知系统不要自动关闭屏幕、不要待机的:
MSDN上还给出了通知系统不要自动关闭屏幕、不要待机的示例代码:
// Television recording is beginning. Enable away mode and prevent
// the sleep idle time-out.
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED);
// Wait until recording is complete...
// Clear EXECUTION_STATE flags to disable away mode and allow the system to idle to sleep normally.
SetThreadExecutionState(ES_CONTINUOUS);
即,通过下面这句代码就能阻止系统自动关闭屏幕、阻止系统自动待机了:(注意参数)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED);
如果取消阻止,只需要调用下面这句即可:
SetThreadExecutionState(ES_CONTINUOUS);
于是我们又用API monito监测了一下参考软件再入会对SetThreadExecutionState函数的调用,看看参考软件是不是也调用了该函数,并查看调用该函数时传递了什么参数。监测的操作过程,和上面监测SystemParametersInfo函数调用完全一样的,这里就不再赘述了。
API Monitor在参考软件入会时监测到了SetThreadExecutionState函数的调用,如下:
API Monitor在参考软件退会时监测到了SetThreadExecutionState函数的调用,如下:
参考软件在调用SetThreadExecutionState函数时传递的参数,和MSDN上给出的示例代码中传入的参数是完全一致的。
7、最后
API Monitor工具软件确实非常好用,我们在项目中用过多次了,不仅能监测目标软件对系统API函数的调用,还能看到调用API函数时传入了哪些具体的参数。除了能监测系统API函数的调用,还能监测目标软件对第三方库的接口的调用情况。
参考友商的设计不仅仅可以参考友商的UI交互设计,还可以使用API Monitor监测友商在实现某一功能时调用哪些API函数,能给我们在代码上带来直接的参考!
此外,根据多年的经验,网上搜到的内容可以为我们解决问题提供一些线索,但网上提供的源代码很多时候都不够严谨(比如没有申请的资源在函数结束时没有释放),考虑的不太周全,没有经过详细测试验证,需要我们去做进一步的测试和验证。我们多次遇到不够严谨、考虑不够周全的情况,需要对网上提供的代码进行修改或改进,才能真正地使用到正式的商业项目中。这次遇到的问题也不例外,比如此次网上提供的代码,没有考虑不能写入系统全局配置文件的问题,没有考虑程序崩溃闪退或者被强杀无法恢复屏保的问题。
以上是关于VC++实现会议中阻止屏保阻止系统自动关闭屏幕阻止系统待机的主要内容,如果未能解决你的问题,请参考以下文章
VC++如何实现 弹出窗口,然后等窗口关闭后再执行剩下的代码?