处理屏幕保护程序
Microsoft Win32 API 支持称为 屏幕保护程序的特殊应用程序。 当鼠标和键盘在指定时间段内处于空闲状态时,屏幕保护程序将启动。 它们用于以下两个原因:
- 保护屏幕免受静态图像引起的磷燃烧。
- 隐藏屏幕上留下的敏感信息。
本主题划分为以下几部分:
关于屏幕保护程序
Windows 控制面板中的桌面应用程序允许用户从屏幕保护程序列表中选择、指定屏幕保护程序启动前应花费的时间、配置屏幕保护程序以及预览屏幕保护程序。 当 Windows 启动时或用户通过控制面板激活屏幕保护程序时,将自动加载屏幕保护程序。
选择屏幕保护程序后,Windows 将监视击键和鼠标移动,然后在一段时间处于非活动状态后启动屏幕保护程序。 但是,如果存在以下任何条件,Windows 不会启动屏幕保护程序:
- 活动应用程序不是基于 Windows 的应用程序。
- 存在基于计算机的训练 (CBT) 窗口。
- 活动应用程序接收 WM_SYSCOMMAND 消息, wParam 参数设置为SC_SCREENSAVE值,但它不会将消息传递给 DefWindowProc 函数。
屏幕保护程序的安全上下文
屏幕保护程序的安全上下文取决于用户是否以交互方式登录。 如果用户在调用屏幕保护程序时以交互方式登录,屏幕保护程序将在交互式用户的安全上下文中运行。 如果没有用户登录,屏幕保护程序的安全上下文取决于所使用的 Windows 版本。
- Windows XP 和 Windows 2000 - 屏幕保护程序在帐户受限的 LocalSystem 上下文中运行。
- Windows 2003 - 屏幕保护程序在 LocalService 的上下文中运行,删除了所有权限并禁用了管理员组。
- 不适用于 Windows NT4。
安全上下文确定可通过屏幕保护程序执行的特权操作级别。
Windows Vista 及更高版本: 如果策略启用了密码保护,则无论应用程序对SC_SCREENSAVE通知执行什么操作,都会启动屏幕保护程序。
屏幕保护程序包含特定的导出函数、资源定义和变量声明。 屏幕保护程序库包含屏幕保护程序所需的main函数和其他启动代码。 当屏幕保护程序启动时,屏幕保护程序库中的启动代码将创建全屏窗口。 此窗口的窗口类声明如下:
WNDCLASS cls;
cls.hCursor = NULL;
cls.hIcon = LoadIcon(hInst, MAKEINTATOM(ID_APP));
cls.lpszMenuName = NULL;
cls.lpszClassName = "WindowsScreenSaverClass";
cls.hbrBackground = GetStockObject(BLACK_BRUSH);
cls.hInstance = hInst;
cls.style = CS_VREDRAW | CS_HREDRAW | CS_SAVEBITS | CS_DBLCLKS;
cls.lpfnWndProc = (WNDPROC) ScreenSaverProc;
cls.cbWndExtra = 0;
cls.cbClsExtra = 0;
为了创建屏幕保护程序,大多数开发人员会创建包含三个必需函数的源代码模块,并将其与屏幕保护程序库链接。 屏幕保护程序模块仅负责配置自身和提供视觉效果。
屏幕保护程序模块中的三个必需功能之一是 ScreenSaverProc。 此函数处理特定消息,并将任何未处理的消息传递回屏幕保护程序库。 以下是 ScreenSaverProc 处理的一些典型消息。
消息 | 含义 |
---|---|
WM_CREATE | 从 Regedit.ini 文件检索任何初始化数据。 为屏幕保护程序窗口设置窗口计时器。 执行任何其他必需的初始化。 |
WM_ERASEBKGND | 擦除屏幕保护程序窗口,并准备后续绘制操作。 |
WM_TIMER | 执行绘图操作。 |
WM_DESTROY | 销毁应用程序处理 WM_CREATE 消息时创建的计时器。 执行任何其他必需的清理。 |
ScreenSaverProc 通过调用 DefScreenSaverProc 函数将未处理的消息传递到屏幕保护程序库。 下表介绍了此函数如何处理各种消息。
消息 | 操作 |
---|---|
WM_SETCURSOR | 将光标设置为 null 游标,将其从屏幕中删除。 |
WM_PAINT | 绘制屏幕背景。 |
WM_LBUTTONDOWN | 终止屏幕保护程序。 |
WM_MBUTTONDOWN | 终止屏幕保护程序。 |
WM_RBUTTONDOWN | 终止屏幕保护程序。 |
WM_KEYDOWN | 终止屏幕保护程序。 |
WM_MOUSEMOVE | 终止屏幕保护程序。 |
WM_ACTIVATE | 如果 wParam 参数设置为 FALSE,则终止屏幕保护程序。 |
屏幕保护程序模块中的第二个必需函数是 ScreenSaverConfigureDialog。 此函数显示一个对话框,使用户能够配置屏幕保护程序, (应用程序必须提供相应的对话框模板) 。 当用户在控制面板的“屏幕保护程序”对话框中选择“设置”按钮时,Windows 将显示配置对话框。
屏幕保护程序模块中的第三个必需函数是 RegisterDialogClasses。 此函数必须由所有屏幕保护程序应用程序调用。 但是,在配置对话框中不需要特殊窗口或自定义控件的应用程序只需返回 TRUE。 需要特殊窗口或自定义控件的应用程序应使用此函数来注册相应的窗口类。
除了创建支持上述三个函数的模块外,屏幕保护程序还应提供图标。 仅当屏幕保护程序作为独立应用程序运行时,此图标才可见。 (要由控制面板运行,屏幕保护程序必须具有 .scr 文件扩展名;要作为独立应用程序运行,它必须具有.exe文件扩展名。) 必须在屏幕保护程序的资源文件中通过常量 ID_APP 标识图标,该常量在 Scrnsave.h 头文件中定义。
最后一个要求是屏幕保护程序说明字符串。 屏幕保护程序的资源文件必须包含控制面板显示为屏幕保护程序名称的字符串。 说明字符串必须是资源文件的字符串表中的第一个字符串, (用序号值 1) 标识。 但是,如果屏幕保护程序具有长文件名,控制面板将忽略说明字符串。 在这种情况下,文件名将用作说明字符串。
使用屏幕保护程序函数
本部分使用从屏幕保护程序应用程序获取的示例代码来说明以下任务:
创建屏幕保护程序
在 1 到 10 秒的间隔内,本示例中的应用程序使用四种颜色之一重新绘制屏幕:白色、浅灰色、深灰色和黑色。 每次收到 WM_TIMER 消息时,应用程序都会绘制屏幕。 用户可以通过选择应用程序的配置对话框并调整单个水平滚动条来调整发送此消息的时间间隔。
屏幕保护程序库
静态屏幕保护程序函数包含在屏幕保护程序库中。 有两个版本的库可用:Scrnsave.lib 和 Scrnsavw.lib。 必须将项目与其中一个链接。 Scrnsave.lib 用于使用 ANSI 字符的屏幕保护程序,Scrnsavw.lib 用于使用 Unicode 字符的屏幕保护程序。 与 Scrnsavw.lib 链接的屏幕保护程序将仅在支持 Unicode 的 Windows 平台上运行,而与 Scrnsave.lib 链接的屏幕保护程序将在任何 Windows 平台上运行。
支持配置对话框
大多数屏幕保护程序都提供一个配置对话框,允许用户指定自定义数据,例如唯一颜色、绘制速度、线条粗细、字体等。 若要支持配置对话框,应用程序必须提供对话框模板,并且还必须支持 ScreenSaverConfigureDialog 函数。 下面是示例应用程序的对话框模板。
DLG_SCRNSAVECONFIGURE DIALOG 6, 18, 160, 63
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Sample Screen-Saver Setup"
FONT 8, "MS Shell Dlg"
BEGIN
GROUPBOX "Redraw Speed", 101, 0, 6, 98, 40
SCROLLBAR ID_SPEED, 5, 31, 89, 10
LTEXT "Fast", 103, 6, 21, 20, 8
LTEXT "Slow", 104, 75, 21, 20, 8
PUSHBUTTON "OK", ID_OK, 117, 10, 40, 14
PUSHBUTTON "Cancel", ID_CANCEL, 117, 32, 40, 14
END
必须使用十进制值 2003 定义用于标识对话框模板的常量,如以下示例所示:
#define DLG_SCRNSAVECONFIGURE 2003
以下示例演示在示例应用程序中找到的 ScreenSaverConfigureDialog 函数。
#define MINVEL 1 // minimum redraw speed value
#define MAXVEL 10 // maximum redraw speed value
#define DEFVEL 5 // default redraw speed value
LONG lSpeed = DEFVEL; // redraw speed variable
extern HINSTANCE hMainInstance; // screen saver instance handle
CHAR szAppName[80]; // .ini section name
CHAR szTemp[20]; // temporary array of characters
CHAR szRedrawSpeed[ ] = "Redraw Speed"; // .ini speed entry
CHAR szIniFile[MAXFILELEN]; // .ini or registry file name
BOOL WINAPI ScreenSaverConfigureDialog(hDlg, message, wParam, lParam)
HWND hDlg;
UINT message;
DWORD wParam;
LONG lParam;
HRESULT hr;
{
static HWND hSpeed; // handle to speed scroll bar
static HWND hOK; // handle to OK push button
switch(message)
{
case WM_INITDIALOG:
// Retrieve the application name from the .rc file.
LoadString(hMainInstance, idsAppName, szAppName,
80 * sizeof(TCHAR));
// Retrieve the .ini (or registry) file name.
LoadString(hMainInstance, idsIniFile, szIniFile,
MAXFILELEN * sizeof(TCHAR));
// TODO: Add error checking to verify LoadString success
// for both calls.
// Retrieve any redraw speed data from the registry.
lSpeed = GetPrivateProfileInt(szAppName, szRedrawSpeed,
DEFVEL, szIniFile);
// If the initialization file does not contain an entry
// for this screen saver, use the default value.
if(lSpeed > MAXVEL || lSpeed < MINVEL)
lSpeed = DEFVEL;
// Initialize the redraw speed scroll bar control.
hSpeed = GetDlgItem(hDlg, ID_SPEED);
SetScrollRange(hSpeed, SB_CTL, MINVEL, MAXVEL, FALSE);
SetScrollPos(hSpeed, SB_CTL, lSpeed, TRUE);
// Retrieve a handle to the OK push button control.
hOK = GetDlgItem(hDlg, ID_OK);
return TRUE;
case WM_HSCROLL:
// Process scroll bar input, adjusting the lSpeed
// value as appropriate.
switch (LOWORD(wParam))
{
case SB_PAGEUP:
--lSpeed;
break;
case SB_LINEUP:
--lSpeed;
break;
case SB_PAGEDOWN:
++lSpeed;
break;
case SB_LINEDOWN:
++lSpeed;
break;
case SB_THUMBPOSITION:
lSpeed = HIWORD(wParam);
break;
case SB_BOTTOM:
lSpeed = MINVEL;
break;
case SB_TOP:
lSpeed = MAXVEL;
break;
case SB_THUMBTRACK:
case SB_ENDSCROLL:
return TRUE;
break;
}
if ((int) lSpeed <= MINVEL)
lSpeed = MINVEL;
if ((int) lSpeed >= MAXVEL)
lSpeed = MAXVEL;
SetScrollPos((HWND) lParam, SB_CTL, lSpeed, TRUE);
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_OK:
// Write the current redraw speed variable to
// the .ini file.
hr = StringCchPrintf(szTemp, 20, "%ld", lSpeed);
if (SUCCEEDED(hr))
WritePrivateProfileString(szAppName, szRedrawSpeed,
szTemp, szIniFile);
case ID_CANCEL:
EndDialog(hDlg, LOWORD(wParam) == ID_OK);
return TRUE;
}
}
return FALSE;
}
除了提供对话框模板和支持 ScreenSaverConfigureDialog 函数外,应用程序还必须支持 RegisterDialogClasses 函数。 此函数注册屏幕保护程序所需的任何非标准窗口类。 由于示例应用程序在其对话框过程中仅使用标准窗口类,因此此函数仅返回 TRUE,如以下示例所示:
BOOL WINAPI RegisterDialogClasses(hInst)
HANDLE hInst;
{
return TRUE;
}
支持屏幕保护程序窗口过程
每个屏幕保护程序必须支持名为 ScreenSaverProc 的窗口过程。 与大多数窗口过程一样, ScreenSaverProc 处理一组特定消息,并将任何未处理的消息传递到默认过程。 但是,ScreenSaverProc 将未处理的消息传递给 DefScreenSaverProc 函数,而不是将它们传递给 DefWindowProc 函数。 ScreenSaverProc 与普通窗口过程之间的另一个区别是传递给 ScreenSaverProc 的句柄标识整个桌面而不是客户端窗口。 以下示例演示示例屏幕保护程序的 ScreenSaverProc 窗口过程。
LONG WINAPI ScreenSaverProc(hwnd, message, wParam, lParam)
HWND hwnd;
UINT message;
DWORD wParam;
LONG lParam;
{
static HDC hdc; // device-context handle
static RECT rc; // RECT structure
static UINT uTimer; // timer identifier
switch(message)
{
case WM_CREATE:
// Retrieve the application name from the .rc file.
LoadString(hMainInstance, idsAppName, szAppName, 80 * sizeof(TCHAR));
// Retrieve the .ini (or registry) file name.
LoadString(hMainInstance, idsIniFile, szIniFile, MAXFILELEN * sizeof(TCHAR));
// TODO: Add error checking to verify LoadString success
// for both calls.
// Retrieve any redraw speed data from the registry.
lSpeed = GetPrivateProfileInt(szAppName, szRedrawSpeed,
DEFVEL, szIniFile);
// Set a timer for the screen saver window using the
// redraw rate stored in Regedit.ini.
uTimer = SetTimer(hwnd, 1, lSpeed * 1000, NULL);
break;
case WM_ERASEBKGND:
// The WM_ERASEBKGND message is issued before the
// WM_TIMER message, allowing the screen saver to
// paint the background as appropriate.
hdc = GetDC(hwnd);
GetClientRect (hwnd, &rc);
FillRect (hdc, &rc, GetStockObject(BLACK_BRUSH));
ReleaseDC(hwnd,hdc);
break;
case WM_TIMER:
// The WM_TIMER message is issued at (lSpeed * 1000)
// intervals, where lSpeed == .001 seconds. This
// code repaints the entire desktop with a white,
// light gray, dark gray, or black brush each
// time a WM_TIMER message is issued.
hdc = GetDC(hwnd);
GetClientRect(hwnd, &rc);
if (i++ <= 4)
FillRect(hdc, &rc, GetStockObject(i));
else
(i = 0);
ReleaseDC(hwnd,hdc);
break;
case WM_DESTROY:
// When the WM_DESTROY message is issued, the screen saver
// must destroy any of the timers that were set at WM_CREATE
// time.
if (uTimer)
KillTimer(hwnd, uTimer);
break;
}
// DefScreenSaverProc processes any messages ignored by ScreenSaverProc.
return DefScreenSaverProc(hwnd, message, wParam, lParam);
}
创建模块定义文件
ScreenSaverProc 和 ScreenSaverConfigureDialog 函数必须导出到应用程序的模块定义文件中;但是,不应导出 RegisterDialogClasses。 以下示例显示了示例应用程序的模块定义文件。
NAME SSTEST.SCR
DESCRIPTION 'SCRNSAVE : Test'
STUB 'WINSTUB.EXE'
EXETYPE WINDOWS
CODE MOVEABLE
DATA MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 4096
EXPORTS
ScreenSaverProc
ScreenSaverConfigureDialog
安装新的屏幕保护程序
编译可用屏幕保护程序列表时,控制面板在 Windows 启动目录中搜索扩展名为 .scr 的文件。 由于屏幕保护程序是具有.exe扩展名的标准 Windows 可执行文件,因此必须重命名它们,使其具有 .scr 扩展名,并将其复制到正确的目录。
向“屏幕保护程序配置”对话框添加帮助
屏幕保护器的配置对话框通常包含 “帮助 ”按钮。 屏幕保护程序应用程序可以检查“帮助”按钮标识符,并按照其他基于 Windows 的应用程序中提供的相同方式调用 WinHelp 函数。