-
Notifications
You must be signed in to change notification settings - Fork 500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
缩放时模拟D3D独占全屏 #245
Comments
SHQueryUserNotificationState 只能检测独占式全屏,检测它的原因是系统无法在独占全屏和正常桌面间快速切换,但magpie没有这个问题。 一些用户可能在全屏时不希望被打扰,这个没有好的解决办法,独占全屏应该没法模拟 |
如果游戏模式确实无法达成,那是否可以自动启动专注模式?(以及退出) |
我会尝试一下,不过专注模式只能影响系统通知,其他应用不遵守也没办法 |
研究了一下 QueryUserNotificationState 的实现方式,发现它最终是调用 IsDirectXAppRunningFullScreen 来测试是否独占全屏,而这个函数的实现非常简单,就是尝试获取 也就是说,只需要 (但是并不影响通知推送,因此可以结合上述专注模式来避免通知) |
mpv 曾经做过这方面的 hack mpv-player/mpv@3d8ca93 这种方法很脆弱,但也有尝试的价值 |
mpv的目的是不太一样的,这里显然只是为了让其他(会检测状态的)app认为有全屏游戏在运行,从而禁用一些功能,而mpv是为了检测屏幕混和然后使用不同的渲染策略。目的上就有所不同 |
尝试了 ZwUpdateWnfStateData(和 dnd),在 win11 中没有作用 更新:最后成功调用了这个 API,调用之后任务栏右侧出现了月亮图标,表示已经进入专注模式,但设置里没有变,且经过测试无法屏蔽通知。 获取 __DDrawExclMode__ 后确实可以使 SHQueryUserNotificationState 返回 QUNS_RUNNING_D3D_FULL_SCREEN,但也没法屏蔽系统通知,可能对传统 Win32 有用 |
这些是我实验用的代码,如果你要完善这个功能,希望开一个 pull request FocusModeHack.h #pragma once
#include "pch.h"
#include "Utils.h"
class FocusModeHack {
public:
FocusModeHack();
~FocusModeHack();
private:
Utils::ScopedHandle _exclModeMutex;
}; FocusModeHack.cpp #include "pch.h"
#include "FocusModeHack.h"
#include <winternl.h>
#include <shellapi.h>
typedef struct _WNF_STATE_NAME {
ULONG Data[2];
} WNF_STATE_NAME;
typedef struct _WNF_STATE_NAME* PWNF_STATE_NAME;
typedef const struct _WNF_STATE_NAME* PCWNF_STATE_NAME;
typedef struct _WNF_TYPE_ID {
GUID TypeId;
} WNF_TYPE_ID, * PWNF_TYPE_ID;
typedef const WNF_TYPE_ID* PCWNF_TYPE_ID;
typedef ULONG WNF_CHANGE_STAMP, * PWNF_CHANGE_STAMP;
enum class FocusAssistResult {
not_supported = -2,
failed = -1,
off = 0,
priority_only = 1,
alarms_only = 2
};
typedef NTSTATUS(NTAPI* PNTQUERYWNFSTATEDATA)(
_In_ PWNF_STATE_NAME StateName,
_In_opt_ PWNF_TYPE_ID TypeId,
_In_opt_ const VOID* ExplicitScope,
_Out_ PWNF_CHANGE_STAMP ChangeStamp,
_Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer,
_Inout_ PULONG BufferSize);
typedef NTSTATUS (NTAPI* ZwUpdateWnfStateData)(
_In_ PCWNF_STATE_NAME StateName,
_In_reads_bytes_opt_(Length) const VOID* Buffer,
_In_opt_ ULONG Length,
_In_opt_ PCWNF_TYPE_ID TypeId,
_In_opt_ const PVOID ExplicitScope,
_In_ WNF_CHANGE_STAMP MatchingChangeStamp,
_In_ BOOL CheckStamp
);
// 模拟 D3D 独占全屏模式,以起到免打扰的效果
// SHQueryUserNotificationState 通常被用来检测是否有 D3D 游戏独占全屏,以确定是否应该向用户推送通知/弹窗
// 此函数内部使用名为 __DDrawExclMode__ 的 mutex 检测独占全屏,因此这里直接获取该 mutex 以模拟独占全屏
FocusModeHack::FocusModeHack() {
// note: ntdll is guaranteed to be in the process address space.
const auto h_ntdll = GetModuleHandle(L"ntdll");
// get pointer to function
const auto pNtQueryWnfStateData = PNTQUERYWNFSTATEDATA(GetProcAddress(h_ntdll, "NtQueryWnfStateData"));
if (!pNtQueryWnfStateData) {
OutputDebugString(L"fail");
return ;
}
const auto pNtUpdateWnfStateData = ZwUpdateWnfStateData(GetProcAddress(h_ntdll, "NtUpdateWnfStateData"));
if (!pNtUpdateWnfStateData) {
OutputDebugString(L"fail");
return;
}
// state name for active hours (Focus Assist)
WNF_STATE_NAME WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED{ 0xA3BF1C75, 0xD83063E };
BYTE b[4] = { 0x02, 0x00, 0x00, 0x00 };
if (NT_SUCCESS(pNtUpdateWnfStateData(
&WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED,
b,
4,
0,
0,
0,
0
))) {
OutputDebugString(L"fail");
}
// note: we won't use it but it's required
WNF_CHANGE_STAMP change_stamp = { 0 };
// on output buffer will tell us the status of Focus Assist
DWORD buffer = 0;
ULONG buffer_size = sizeof(buffer);
if (NT_SUCCESS(pNtQueryWnfStateData(&WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED, nullptr, nullptr, &change_stamp,
&buffer, &buffer_size))) {
if (buffer != (DWORD)FocusAssistResult::alarms_only) {
OutputDebugString(L"fail");
}
}
_exclModeMutex.reset(Utils::SafeHandle(
OpenMutex(SYNCHRONIZE, FALSE,L"__DDrawExclMode__")));
if (!_exclModeMutex) {
SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("OpenMutex 失败"));
return;
}
QUERY_USER_NOTIFICATION_STATE state;
HRESULT hr = SHQueryUserNotificationState(&state);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("SHQueryUserNotificationState 失败", hr));
return;
}
if (state != QUNS_ACCEPTS_NOTIFICATIONS) {
SPDLOG_LOGGER_INFO(logger, "已处于免打扰状态");
return;
}
DWORD result = WaitForSingleObject(_exclModeMutex.get(), 0);
if (result != WAIT_OBJECT_0) {
SPDLOG_LOGGER_ERROR(logger, "获取 __DDrawExclMode__ 失败");
_exclModeMutex.reset();
return;
}
hr = SHQueryUserNotificationState(&state);
if (FAILED(hr)) {
SPDLOG_LOGGER_ERROR(logger, MakeComErrorMsg("SHQueryUserNotificationState 失败", hr));
ReleaseMutex(_exclModeMutex.get());
_exclModeMutex.reset();
return;
}
if (state != QUNS_RUNNING_D3D_FULL_SCREEN) {
SPDLOG_LOGGER_INFO(logger, "进入 D3D 全屏模式失败");
ReleaseMutex(_exclModeMutex.get());
_exclModeMutex.reset();
return;
}
SPDLOG_LOGGER_INFO(logger, "已进入 D3D 全屏模式");
}
FocusModeHack::~FocusModeHack() {
if (_exclModeMutex) {
ReleaseMutex(_exclModeMutex.get());
}
}
|
(嘛,其实一开始我就是就是想让PowerToys的那个双击ctrl聚焦鼠标的功能自己禁用掉而已(毕竟ctrl在某些游戏里还是挺重要的一个功能( |
在找到办法之前这个功能暂且搁置 |
刚看了一下,目前release版本在全屏的时候(os: win11 dev),已经会自动启动专注模式了(关闭动画的情况下能看到在全屏瞬间出现月亮图标,并且有观察到在退出全屏后专注助手给出的全屏期间的通知提示。) 只要用ddraw的那个workaround干掉 PowerToys 的双击ctrl聚焦鼠标功能即可(原始需求) |
模拟独占全屏的功能我会添加一个选项 |
已实现这个功能 9328748 |
Expected behavior 预期的功能
游戏模式可以影响其他app的行为,例如不推送通知或者隐藏某些悬浮窗。。
看起来现在检测游戏模式是通过 SHQueryUserNotificationState 获取结果是否等于 QUNS_RUNNING_D3D_FULL_SCREEN 来判断的(例如 https://github.com/microsoft/PowerToys/blob/main/src/common/utils/game_mode.h ),但是这要求必须有一个全屏独占窗口,显然Magpie现在是没法满足这个条件的,不确定是否有其他的实现途径(
Alternative behavior (optional) 近似的功能(可选)
No response
The text was updated successfully, but these errors were encountered: