REPENTOGON 中 DwmWindowAttribute 枚举全解:用 Lua 控制《以撒的结合:忏悔》游戏窗口的 DWM 属性

原创2026-10-16 10:09:59276 阅读
文章标签:游戏开发插件系统

REPENTOGON 中 DwmWindowAttribute 枚举全解:用 Lua 控制《以撒的结合:忏悔》游戏窗口的 DWM 属性

DwmWindowAttribute 是 REPENTOGON 为《The Binding of Isaac: Repentance》模组脚本新增的一组 Windows DWM(Desktop Window Manager)窗口属性枚举,配合 Isaac.GetDwmWindowAttribute / Isaac.SetDwmWindowAttribute 两个全局 API,模组作者可以直接在 Lua 脚本里读写游戏窗口的标题栏深色模式、圆角、边框颜色、系统背景类型等外观属性。阅读本文后,你将掌握该枚举的完整取值表、底层 C++ 实现原理与安全限制,并能写出可实际运行的窗口自定义脚本。

一、这是什么:DWM 窗口属性与 REPENTOGON 的桥接

DwmWindowAttribute 对应 Windows SDK dwmapi.h 中的 DWMWINDOWATTRIBUTE 枚举。DWM 是 Windows 负责桌面合成与窗口外观的系统组件,其窗口属性(DWMWA_*)控制着每个顶级窗口的标题栏渲染、Aero Peek 行为、深色模式、圆角样式、边框/标题栏/文字颜色、Mica 与 Acrylic 等系统背景材质等细节。

REPENTOGON 将这一整套 Windows 枚举原样暴露给游戏内的 Lua 环境,使模组不再需要依赖外部 DLL 或注入代码,仅靠官方扩展 API 即可调整游戏窗口本身的外观。该功能在 changelog.txt 中有明确记录:

* Isaac:
	- Get/SetDwmWindowAttribute (for use with new DwmWindowAttributes enum)

二、枚举完整取值表

依据 docs/docs/enums/DwmWindowAttribute.md 的定义,该枚举的全部成员如下(原文档注释为 blocked 的两项已被 REPENTOGON 在 API 层禁用):

值 枚举名 备注
1 DWMWA_NCRENDERING_ENABLED
2 DWMWA_NCRENDERING_POLICY
3 DWMWA_TRANSITIONS_FORCEDISABLED
4 DWMWA_ALLOW_NCPAINT
5 DWMWA_CAPTION_BUTTON_BOUNDS
6 DWMWA_NONCLIENT_RTL_LAYOUT
7 DWMWA_FORCE_ICONIC_REPRESENTATION
8 DWMWA_FLIP3D_POLICY
9 DWMWA_EXTENDED_FRAME_BOUNDS
10 DWMWA_HAS_ICONIC_BITMAP
11 DWMWA_DISALLOW_PEEK
12 DWMWA_EXCLUDED_FROM_PEEK
13 DWMWA_CLOAK blocked
14 DWMWA_CLOAKED blocked
15 DWMWA_FREEZE_REPRESENTATION
16 DWMWA_PASSIVE_UPDATE_MODE
17 DWMWA_USE_HOSTBACKDROPBRUSH
20 DWMWA_USE_IMMERSIVE_DARK_MODE
33 DWMWA_WINDOW_CORNER_PREFERENCE
34 DWMWA_BORDER_COLOR
35 DWMWA_CAPTION_COLOR
36 DWMWA_TEXT_COLOR
37 DWMWA_VISIBLE_FRAME_BORDER_THICKNESS
38 DWMWA_SYSTEMBACKDROP_TYPE
39 DWMWA_LAST

值得注意的细节:

  • 取值并不连续:13、14 之后直接跳到 20,再跳到 33 至 39,中间的 18、19、21–32 均未被该枚举收录。这些空缺对应 Windows 不同版本中新增或保留的属性槽位,REPENTOGON 仅暴露了文档表中列出的成员。
  • 39 是哨兵值:DWMWA_LAST 在 Windows DWM 定义中用于标识"最后一个有效属性",用于对属性 ID 做范围校验,本身不是一个可用的外观开关。
  • 13 / 14 被标记为 blocked:DWMWA_CLOAK(将窗口隐藏出 DWM 合成输出)与 DWMWA_CLOAKED(查询窗口是否被 cloaked)在文档和 Lua 枚举定义中均标注为 blocked,调用时会被 API 直接拒绝(详见下文安全机制)。

三、各属性的用途速览

下面按功能类别对各成员做简要说明,帮助你在实际调用时选择合适的属性(这些描述遵循 Windows DWM 的通用定义,供选参参考;具体读写效果以系统版本为准)。

渲染与绘制类

  • DWMWA_NCRENDERING_ENABLED(1):是否启用非客户区(标题栏与边框)的 DWM 渲染。
  • DWMWA_NCRENDERING_POLICY(2):非客户区渲染策略,属于枚举值型属性。
  • DWMWA_TRANSITIONS_FORCEDISABLED(3):是否强制禁用窗口过渡动画。
  • DWMWA_ALLOW_NCPAINT(4):是否允许非客户区自绘内容生效。
  • DWMWA_EXTENDED_FRAME_BOUNDS(9):查询窗口扩展帧的边界矩形,常用于把内容延伸到标题栏区域。
  • DWMWA_VISIBLE_FRAME_BORDER_THICKNESS(37):可见窗口边框的厚度。

Aero Peek 与 Cloak 类

  • DWMWA_DISALLOW_PEEK(11):禁止该窗口出现在 Aero Peek 预览中。
  • DWMWA_EXCLUDED_FROM_PEEK(12):从 Aero Peek 预览中排除该窗口。
  • DWMWA_CLOAK(13,blocked):将窗口从 DWM 合成中隐藏。
  • DWMWA_CLOAKED(14,blocked):查询窗口当前是否处于 cloaked 状态。
  • DWMWA_FORCE_ICONIC_REPRESENTATION(7):强制使用"图标化"表示方式。
  • DWMWA_HAS_ICONIC_BITMAP(10):窗口是否提供图标位图供 DWM 使用。
  • DWMWA_FREEZE_REPRESENTATION(15):冻结窗口当前画面表示(快照模式)。
  • DWMWA_PASSIVE_UPDATE_MODE(16):被动更新模式,窗口内容由 DWM 侧按需获取。

现代外观类(深色模式、圆角、颜色)

  • DWMWA_USE_IMMERSIVE_DARK_MODE(20):布尔值,1 启用深色标题栏,0 恢复浅色——这是最常用于"游戏窗口深色化"的属性。
  • DWMWA_WINDOW_CORNER_PREFERENCE(33):窗口圆角偏好,配合 DWMWCP_*(如圆角/直角)数值使用。
  • DWMWA_BORDER_COLOR(34):窗口边框颜色(ARGB)。
  • DWMWA_CAPTION_COLOR(35):标题栏颜色(ARGB)。
  • DWMWA_TEXT_COLOR(36):标题栏文字颜色(ARGB)。
  • DWMWA_SYSTEMBACKDROP_TYPE(38):系统背景材质类型(如 Mica、Acrylic 等),数值对应 DWMSBT_* 系列常量。

其他

  • DWMWA_CAPTION_BUTTON_BOUNDS(5):标题栏按钮区域的边界矩形。
  • DWMWA_NONCLIENT_RTL_LAYOUT(6):非客户区是否使用从右到左的 RTL 布局。
  • DWMWA_FLIP3D_POLICY(8):Windows Flip 3D 切换时的策略。
  • DWMWA_USE_HOSTBACKDROPBRUSH(17):是否使用宿主窗口的背景画笔。
  • DWMWA_LAST(39):最后一个有效属性标记。

四、枚举在 Lua 环境中的定义

该枚举随 REPENTOGON 的扩展 Lua 环境一起注入游戏,定义位于 repentogon/resources/scripts/enums_ex.lua,与文档表格逐项对应:

DwmWindowAttribute = {
    DWMWA_NCRENDERING_ENABLED=1,
    DWMWA_NCRENDERING_POLICY=2,
    DWMWA_TRANSITIONS_FORCEDISABLED=3,
    DWMWA_ALLOW_NCPAINT=4,
    DWMWA_CAPTION_BUTTON_BOUNDS=5,
    DWMWA_NONCLIENT_RTL_LAYOUT=6,
    DWMWA_FORCE_ICONIC_REPRESENTATION=7,
    DWMWA_FLIP3D_POLICY=8,
    DWMWA_EXTENDED_FRAME_BOUNDS=9,
    DWMWA_HAS_ICONIC_BITMAP=10,
    DWMWA_DISALLOW_PEEK=11,
    DWMWA_EXCLUDED_FROM_PEEK=12,
    DWMWA_CLOAK=13,		--blocked
    DWMWA_CLOAKED=14,	--blocked
    DWMWA_FREEZE_REPRESENTATION=15,
    DWMWA_PASSIVE_UPDATE_MODE=16,
    DWMWA_USE_HOSTBACKDROPBRUSH=17,
    DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
    DWMWA_WINDOW_CORNER_PREFERENCE = 33,
    DWMWA_BORDER_COLOR=34,
    DWMWA_CAPTION_COLOR=35,
    DWMWA_TEXT_COLOR=36,
    DWMWA_VISIBLE_FRAME_BORDER_THICKNESS=37,
    DWMWA_SYSTEMBACKDROP_TYPE=38,
    DWMWA_LAST=39
}

因此脚本中可以直接用 DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE 这种命名方式引用属性,而不必记忆裸数字。

五、配套 API:Isaac.GetDwmWindowAttribute / Isaac.SetDwmWindowAttribute

该枚举的消费端是 Isaac 全局类下的两个函数,签名记录在 docs/docs/Isaac.md 与 docs/docs/Isaac.md:

DwmWindowAttribute GetDwmWindowAttribute ( )
void SetDwmWindowAttribute ( DwmWindowAttribute Attribute )

5.1 底层实现与真实参数约定

两个函数在 repentogon/LuaInterfaces/LuaIsaac.cpp 中实现,并注册到 Lua 全局类 Isaac(见 repentogon/LuaInterfaces/LuaIsaac.cpp):

LUA_FUNCTION(Lua_SetDWMAttrib)
{
	HWND hwnd = GetActiveWindow();
	DWORD activeProcessId;
	GetWindowThreadProcessId(hwnd, &activeProcessId);
	DWORD currentProcessId = GetCurrentProcessId();
	int32_t attribid = (int32_t)luaL_optinteger(L, 1, 0);
	int32_t attribval = (int32_t)luaL_optinteger(L, 2, 0);

	switch (attribid) {
	case (DWMWINDOWATTRIBUTE)DWMWA_CLOAK:
		return luaL_error(L, "Usage of DWMWA_CLOAK attribute is prohibited!");
		break;
	case (DWMWINDOWATTRIBUTE)DWMWA_CLOAKED:
		return luaL_error(L, "Usage of DWMWA_CLOAKED attribute is prohibited!");
		break;
	};
	if (activeProcessId == currentProcessId) {
		DwmSetWindowAttribute(hwnd, attribid, &attribval, sizeof(attribval));
	};

	return 0;
};

LUA_FUNCTION(Lua_GetDWMAttrib)
{
	HWND hwnd = GetActiveWindow();
	int32_t attribid = (int32_t)luaL_optinteger(L, 1, 0);
	int32_t attribval;
	DwmGetWindowAttribute(hwnd, attribid,&attribval,sizeof(attribval));
	lua_pushinteger(L, attribval);
	return 1;
};

从源码可以归纳出以下几点实际调用约定(文档签名之外的重要信息):

  1. SetDwmWindowAttribute 实际读取两个参数:第一个是属性 ID,第二个是属性值(int32_t),缺省值为 0。也就是说,文档中仅列出的 Attribute 参数是属性 ID,而具体写入的值(如深色模式的 1/0、ARGB 颜色、圆角偏好等)需要作为第二个参数传入。
  2. GetDwmWindowAttribute 同样需要传入属性 ID:实现中通过 luaL_optinteger(L, 1, 0) 读取第一个参数,返回值为整数型,可直接与枚举成员比较。
  3. 读写目标固定为活动窗口:两者都通过 GetActiveWindow() 取得句柄,不提供自定义 HWND 参数。
  4. 颜色类属性按 ARGB 整数值处理:写入 DWMWA_BORDER_COLOR / DWMWA_CAPTION_COLOR / DWMWA_TEXT_COLOR 时,第二个参数应为 0xRRGGBB 或 0xAARRGGBB 格式的 32 位整数。

5.2 安全机制

源码中内置了两道保护,这是使用该 API 时必须了解的限制:

  • 禁止 Cloak 相关属性:当属性 ID 为 DWMWA_CLOAK(13)或 DWMWA_CLOAKED(14)时,Lua_SetDWMAttrib 会直接调用 luaL_error 抛出运行时错误,提示 "Usage of DWMWA_CLOAK attribute is prohibited!"。这正是文档表格中这两项被标记为 blocked 的原因——防止脚本把游戏窗口从桌面隐藏掉或查询该状态。
  • 仅对当前进程生效:SetDwmWindowAttribute 在执行写入前会比对 activeProcessId 与 currentProcessId,只有游戏自身是当前活动窗口所属进程时才调用 DwmSetWindowAttribute。这避免了脚本误改其他应用窗口,也保证了设置始终作用于 Isaac 自己的窗口。

六、实战示例

以下 Lua 脚本展示了如何利用该枚举调整游戏窗口外观。以 MC_POST_GAME_STARTED(或 MC_POST_RENDER 的一次性初始化)等回调中调用为宜:

-- 1) 启用深色标题栏(值 1 开启,0 关闭)
Isaac.SetDwmWindowAttribute(
    DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE,
    1
)

-- 2) 查询当前深色模式状态并打印
local dark = Isaac.GetDwmWindowAttribute(
    DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE
)
print("immersive dark mode = " .. tostring(dark))

-- 3) 设置标题栏与边框颜色(ARGB 整数)
Isaac.SetDwmWindowAttribute(DwmWindowAttribute.DWMWA_CAPTION_COLOR, 0x1E1E1E)
Isaac.SetDwmWindowAttribute(DwmWindowAttribute.DWMWA_BORDER_COLOR, 0x007ACC)
Isaac.SetDwmWindowAttribute(DwmWindowAttribute.DWMWA_TEXT_COLOR, 0xFFFFFF)

关于窗口圆角偏好,DWMWA_WINDOW_CORNER_PREFERENCE(33)的取值对应 Windows 的 DWMWCP_* 常量:0(DWMWCP_DEFAULT)、1(DWMWCP_DONOTROUND,直角)、2(DWMWCP_ROUND,圆角)、3(DWMWCP_ROUNDSMALL,小圆角)。例如让窗口恢复直角:

Isaac.SetDwmWindowAttribute(DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, 1)

调用被禁用的属性则会中断当前 Lua 调用并报错,可借此在开发期验证枚举的受限项:

-- 以下调用会抛出 "Usage of DWMWA_CLOAK attribute is prohibited!" 错误
Isaac.SetDwmWindowAttribute(DwmWindowAttribute.DWMWA_CLOAK, 1)

七、注意事项与适用限制

  • 仅适用于 Windows 平台:DWM 是 Windows 专属组件,该枚举与两个 API 在非 Windows 环境下无实际意义;REPENTOGON 本身以 Windows 为目标平台。
  • 效果依赖系统版本:DWMWA_USE_IMMERSIVE_DARK_MODE(20)、DWMWA_WINDOW_CORNER_PREFERENCE(33)等较新的属性需要较新的 Windows 10 / Windows 11 版本才有效,旧系统上写入可能被静默忽略,建议写入后通过 GetDwmWindowAttribute 回读校验。
  • 设置不保证持久化:与 REPENTOGON 多数运行时 API 一样,窗口属性是进程级即时设置,随游戏进程退出而失效;需要每次启动时在回调中重新应用。
  • 写入值请使用整数语义:实现按 int32_t 传递属性值,布尔类属性传 1/0,颜色类属性传 ARGB 整数,枚举类属性传对应数值。
  • 不要在模组中依赖 Cloak 能力:13/14 被 API 层硬性禁止,设计功能时应绕开。

八、参考资源

登录后查看全文
REPENTOGON