求一个禁止 微信、QQ、钉钉等通讯软件 接收消息后,闪烁自己的 托盘区图标的方法
众所周知,基本上所有的通讯软件都没有接入window的通知,
接收了消息后,会在window的托盘区 闪烁自己的图标,有没有办法关闭这个托盘区的 闪烁效果呢?
求一个禁止 微信、QQ、钉钉等通讯软件 接收消息后,闪烁自己的 托盘区图标的方法
众所周知,基本上所有的通讯软件都没有接入window的通知,
接收了消息后,会在window的托盘区 闪烁自己的图标,有没有办法关闭这个托盘区的 闪烁效果呢?
我厌恶的是微信来新消息时会跟随鼠标位置弹窗,火绒弹窗拦截都管不住,只能用ahk无脑杀
哇,我试着搜索一下,没想到模组也有NoFlashWindow - Windhawk
我还以为23年没用了,还有效的, 它提到Windows似乎有内置的此选项
在 Windows 11 中启用或禁用任务栏应用中的显示闪烁 |Windows 11 论坛
这个针对的是任务栏,不是针对托盘区吧。
(针对任务栏图标禁止闪烁,Win11 原生就有,但针对托盘区图标没有这个功能。)
翻到了是这一个 禁用托盘图标 - Windhawk Disable Tray Icons - Windhawk
自定义应用在:高级设置 - 自定义进程包含列表,应用要重启打开,就有效了
好奇问下,win11的托盘自动折叠的,为啥会嫌闪啊?
我不是要隐藏微信这类通讯软件,我还是要微信这类通讯软件在托盘区显示啊,方便打开
放在托盘区方便打开啊,任务栏图标本来就够满了
可以实现。让AI补充了一个修改,我测试没什么问题,你先试试吧
// ==WindhawkMod==
// @id suppress-tray-flashing
// @name 禁止托盘图标闪烁
// @description 阻止指定应用频繁切换或闪烁系统托盘图标,同时完整保留托盘图标与常规功能。
// @version 1.0.0
// @author User
// @include none
// @compilerOptions -luser32 -lshell32
// ==/WindhawkMod==
// ==WindhawkModReadme==
/*
# 功能与使用说明
本模组专门用于解决即时通讯或办公软件(如 QQ、企业微信、飞书、钉钉、邮件客户端等)在收到消息时在任务栏托盘区域无休止跳动、交替闪烁的问题。
### 核心特性
- 保留图标不隐藏:图标依然固定常驻在系统托盘,右键菜单、左键双击唤出窗口、正常退出等功能完全不受影响。
- 杜绝高频闪烁:从源头锁定应用的最初静态图标,屏蔽高频切换空白帧或黄色高亮帧的动作。
- 零后台开销:无后台轮询线程,不驻留监控定时器,仅在 API 触发瞬间执行数纳秒的条件匹配。
- 安全沙盒隔离:默认不注入任何系统进程,绝不影响 Windows 桌面与资源管理器稳定性。
### 配置步骤
1. 安装并编译保存本模组。
2. 进入模组详情页,点击“高级 (Advanced)”选项卡。
3. 找到“Custom process inclusion list”(自定义包含进程列表)。
4. 在输入框中逐行添加需要禁止闪烁的程序进程名(例如 QQ.exe、WeChat.exe)。
5. 点击保存设置,并完全重启一次对应的软件即可生效。
*/
// ==/WindhawkModReadme==
#include <windows.h>
#include <shellapi.h>
#include <unordered_map>
struct TrayKey {
HWND hWnd;
UINT uID;
bool operator==(const TrayKey& other) const {
return hWnd == other.hWnd && uID == other.uID;
}
};
struct TrayKeyHash {
size_t operator()(const TrayKey& k) const {
return std::hash<HWND>()(k.hWnd) ^ (std::hash<UINT>()(k.uID) << 1);
}
};
static std::unordered_map<TrayKey, HICON, TrayKeyHash> g_iconMap;
static SRWLOCK g_lock = SRWLOCK_INIT;
using Shell_NotifyIconW_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAW lpData);
static Shell_NotifyIconW_t pOriginal_Shell_NotifyIconW = nullptr;
using Shell_NotifyIconA_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAA lpData);
static Shell_NotifyIconA_t pOriginal_Shell_NotifyIconA = nullptr;
template <typename TData, typename TOrigFunc>
static BOOL Handle_Shell_NotifyIcon(DWORD dwMessage, TData* lpData, TOrigFunc pOriginalFunc) {
if (!lpData) {
return pOriginalFunc(dwMessage, lpData);
}
TrayKey key = { lpData->hWnd, lpData->uID };
switch (dwMessage) {
case NIM_ADD: {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
if ((lpData->uFlags & NIF_ICON) && lpData->hIcon) {
HICON hCloned = CopyIcon(lpData->hIcon);
if (hCloned) {
g_iconMap[key] = hCloned;
}
}
ReleaseSRWLockExclusive(&g_lock);
return pOriginalFunc(dwMessage, lpData);
}
case NIM_DELETE: {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
ReleaseSRWLockExclusive(&g_lock);
return pOriginalFunc(dwMessage, lpData);
}
case NIM_MODIFY: {
if (lpData->uFlags & NIF_ICON) {
AcquireSRWLockShared(&g_lock);
auto it = g_iconMap.find(key);
HICON hLocked = (it != g_iconMap.end()) ? it->second : nullptr;
ReleaseSRWLockShared(&g_lock);
if (!hLocked && lpData->hIcon) {
AcquireSRWLockExclusive(&g_lock);
auto retryIt = g_iconMap.find(key);
if (retryIt == g_iconMap.end() || !retryIt->second) {
hLocked = CopyIcon(lpData->hIcon);
if (hLocked) {
g_iconMap[key] = hLocked;
}
} else {
hLocked = retryIt->second;
}
ReleaseSRWLockExclusive(&g_lock);
}
TData safeData = *lpData;
if (hLocked) {
safeData.hIcon = hLocked;
} else {
safeData.uFlags &= ~NIF_ICON;
}
return pOriginalFunc(dwMessage, &safeData);
}
return pOriginalFunc(dwMessage, lpData);
}
default:
break;
}
return pOriginalFunc(dwMessage, lpData);
}
static BOOL WINAPI Hook_Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconW);
}
static BOOL WINAPI Hook_Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconA);
}
BOOL Wh_ModInit() {
HMODULE hShell32 = GetModuleHandleW(L"shell32.dll");
if (!hShell32) {
hShell32 = LoadLibraryW(L"shell32.dll");
}
if (hShell32) {
void* pTargetW = (void*)GetProcAddress(hShell32, "Shell_NotifyIconW");
if (pTargetW) {
Wh_SetFunctionHook(pTargetW, (void*)Hook_Shell_NotifyIconW, (void**)&pOriginal_Shell_NotifyIconW);
}
void* pTargetA = (void*)GetProcAddress(hShell32, "Shell_NotifyIconA");
if (pTargetA) {
Wh_SetFunctionHook(pTargetA, (void*)Hook_Shell_NotifyIconA, (void**)&pOriginal_Shell_NotifyIconA);
}
return TRUE;
}
return FALSE;
}
void Wh_ModUninit() {
AcquireSRWLockExclusive(&g_lock);
for (auto& pair : g_iconMap) {
if (pair.second) {
DestroyIcon(pair.second);
}
}
g_iconMap.clear();
ReleaseSRWLockExclusive(&g_lock);
}
ok 感谢大佬 我晚点拿电脑测试一下
话说大佬 做这个用的哪个AI 以及主要投的是哪些相关的资料给AI呢
因为国内网上关于windhawk开发的相关资料并不是那么丰富和完善
且很多AI对windhawk的资料的训练好像也不多导致经常答非所问
能帮到你就好,大佬算不上,有几个有详细的样式指南
常用的任务栏样式定制集合:
A collection of commonly requested taskbar styling customizations for Windows 11
Windows 11 常用的开始菜单样式定制集合:
A collection of commonly requested start menu styling customizations for Windows 11
Windows 11 常用的通知中心样式定制集合:
A collection of commonly requested notification center styling customizations for Windows 11
Windows 11 常用的文件资源管理器样式定制集合:
A collection of commonly requested file explorer styling customizations for Windows 11
最后开发的话,最好看它们论坛或国外社区看看分享与提问,不熟悉直接用弄ai从零出来有一些未知问题的
国内宣传都偏向美化部分,其实它都有很多功能模组优化,还有意想不到的发现
大佬,我加了个屏蔽消息来时 屏蔽 鼠标悬浮图标时 弹出的气泡弹窗
好像不太行
能帮忙看看是怎么回事吗
// ==WindhawkMod==
// @id suppress-tray-flashing
// @name 禁止托盘图标闪烁
// @description 阻止指定应用频繁切换或闪烁系统托盘图标,并可屏蔽托盘提示文字与气球通知。
// @version 1.3.0
// @author User
// @include none
// @compilerOptions -luser32 -lshell32
// ==/WindhawkMod==
// ==WindhawkModSettings==
/*
- BlockFlashing: true
$name: 屏蔽托盘图标闪烁
$description: 锁定托盘图标为首次添加时的静态图标,阻止应用频繁切换图标造成闪烁
- BlockTooltip: true
$name: 屏蔽托盘提示文字
$description: 清空鼠标悬浮在托盘图标上时弹出的提示文字,以及应用推送的气球通知内容
*/
// ==/WindhawkModSettings==
// ==WindhawkModReadme==
/*
# 功能与使用说明
本模组用于解决即时通讯或办公软件在收到消息时,任务栏托盘图标无休止跳动、交替闪烁的问题。
### 核心特性
- 保留图标不隐藏:图标依然固定常驻系统托盘,右键菜单、左键双击唤出窗口、正常退出等功能不受影响。
- 杜绝高频闪烁:锁定应用首次添加的静态图标,屏蔽后续高频切换空白帧或高亮帧的动作。
- 可选屏蔽提示:可单独关闭鼠标悬浮提示文字与右下角气球通知。
- 零后台开销:无后台轮询线程,不驻留监控定时器,仅在 API 触发瞬间执行极短的条件匹配。
- 安全沙盒隔离:默认不注入任何系统进程,绝不影响 Windows 桌面与资源管理器稳定性。
### 配置步骤
1. 安装并编译保存本模组。
2. 进入模组详情页,点击“设置 (Settings)”选项卡,按需打开或关闭两个开关(默认都打开)。
3. 进入“高级 (Advanced)”选项卡。
4. 找到“Custom process inclusion list”(自定义包含进程列表)。
5. 在输入框中逐行添加需要禁止闪烁的程序进程名(例如 QQ.exe、WeChat.exe)。
6. 点击保存设置,并完全重启一次对应的软件即可生效。
*/
// ==/WindhawkModReadme==
#include <windows.h>
#include <shellapi.h>
#include <unordered_map>
#include <cstring>
#include <cstddef>
// ---------------------------------------------------------------------------
// 全局开关:从 Windhawk 设置中读取,默认都为 true
// ---------------------------------------------------------------------------
static bool g_blockFlashing = true; // 是否屏蔽图标闪烁
static bool g_blockTooltip = true; // 是否屏蔽悬浮提示与气球通知
// ---------------------------------------------------------------------------
// 托盘图标缓存 Key:一个托盘图标由 (窗口句柄, 图标ID) 唯一确定
// ---------------------------------------------------------------------------
struct TrayKey {
HWND hWnd;
UINT uID;
bool operator==(const TrayKey& other) const {
return hWnd == other.hWnd && uID == other.uID;
}
};
struct TrayKeyHash {
size_t operator()(const TrayKey& k) const {
return std::hash<HWND>()(k.hWnd) ^ (std::hash<UINT>()(k.uID) << 1);
}
};
// 缓存:记录每个托盘图标“第一次出现时的静态图标”
static std::unordered_map<TrayKey, HICON, TrayKeyHash> g_iconMap;
// 读写缓存用的轻量读写锁,避免多线程同时操作 unordered_map
static SRWLOCK g_lock = SRWLOCK_INIT;
// 原始函数指针:Hook 后用来调用真正的 Shell_NotifyIcon
using Shell_NotifyIconW_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAW lpData);
static Shell_NotifyIconW_t pOriginal_Shell_NotifyIconW = nullptr;
using Shell_NotifyIconA_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAA lpData);
static Shell_NotifyIconA_t pOriginal_Shell_NotifyIconA = nullptr;
// ---------------------------------------------------------------------------
// 工具函数
// ---------------------------------------------------------------------------
// 清空整个图标缓存,并销毁所有保存的 HICON
static void ClearIconCache() {
AcquireSRWLockExclusive(&g_lock);
for (auto& pair : g_iconMap) {
if (pair.second) {
DestroyIcon(pair.second);
}
}
g_iconMap.clear();
ReleaseSRWLockExclusive(&g_lock);
}
// 把一个图标复制并存入缓存;如果该 Key 已有旧图标,先销毁旧的
static void CacheIcon(const TrayKey& key, HICON hIcon) {
if (!hIcon) return;
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
// 必须 CopyIcon,不能直接保存应用传来的 HICON,
// 因为应用可能在调用完成后立刻销毁它。
HICON hCloned = CopyIcon(hIcon);
if (hCloned) {
g_iconMap[key] = hCloned;
}
ReleaseSRWLockExclusive(&g_lock);
}
// 从缓存中移除并销毁指定图标
static void RemoveCachedIcon(const TrayKey& key) {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
ReleaseSRWLockExclusive(&g_lock);
}
// 清空悬浮提示文字和气球通知内容。
//
// 原理:
// - szTip 是鼠标悬停时显示的文字,配合 NIF_TIP 标志。
// 把 szTip[0] 设为 0,并保留 NIF_TIP,Shell 就会用空字符串覆盖旧提示。
// - szInfo / szInfoTitle 是右下角气球通知的文字,配合 NIF_INFO 标志。
// 清空它们并去掉 NIF_INFO,Shell 就不会弹出气球通知。
template <typename TData>
static void StripTooltip(TData* pData) {
// 清空悬浮提示,但保留 NIF_TIP,让 Shell 主动把空提示写进去
pData->szTip[0] = 0;
pData->uFlags |= NIF_TIP;
// 清空气球通知,并移除 NIF_INFO,Shell 就不会弹通知
pData->szInfo[0] = 0;
pData->szInfoTitle[0] = 0;
pData->uFlags &= ~NIF_INFO;
}
// 按 cbSize 安全复制 NOTIFYICONDATA 结构体。
//
// 应用可能传入不同版本的 NOTIFYICONDATA,cbSize 表示它实际分配的大小。
// 直接 `TData safeData = *lpData;` 可能读到调用者没分配的内存,造成越界。
// 这里只复制 cbSize 指定的字节数,剩余部分清零,保证 safeData 完整且安全。
template <typename TData>
static void SafeCopyNotifyData(TData* dst, const TData* src) {
size_t copySize = src->cbSize;
if (copySize > sizeof(TData)) {
copySize = sizeof(TData);
}
memcpy(dst, src, copySize);
if (copySize < sizeof(TData)) {
memset((BYTE*)dst + copySize, 0, sizeof(TData) - copySize);
}
}
// ---------------------------------------------------------------------------
// Hook 主逻辑:所有 Shell_NotifyIconW / Shell_NotifyIconA 都会进入这里
// ---------------------------------------------------------------------------
template <typename TData, typename TOrigFunc>
static BOOL Handle_Shell_NotifyIcon(DWORD dwMessage, TData* lpData, TOrigFunc pOriginalFunc) {
if (!lpData) {
return pOriginalFunc(dwMessage, lpData);
}
// 两个开关都关闭时,完全透传,不做任何额外操作
if (!g_blockFlashing && !g_blockTooltip) {
return pOriginalFunc(dwMessage, lpData);
}
TrayKey key = { lpData->hWnd, lpData->uID };
switch (dwMessage) {
// -------------------------------------------------------------------
// NIM_ADD:应用首次添加托盘图标
// -------------------------------------------------------------------
case NIM_ADD: {
TData safeData;
TData* pData = lpData;
// 如果需要屏蔽提示,先复制一份结构体并清空提示字段
if (g_blockTooltip) {
SafeCopyNotifyData(&safeData, lpData);
StripTooltip(&safeData);
pData = &safeData;
}
// 调用原始函数,让 Shell 真正添加图标
BOOL result = pOriginalFunc(dwMessage, pData);
// 如果需要屏蔽闪烁,原函数成功后再缓存首次图标
if (g_blockFlashing && result && (lpData->uFlags & NIF_ICON) && lpData->hIcon) {
CacheIcon(key, lpData->hIcon);
}
return result;
}
// -------------------------------------------------------------------
// NIM_DELETE:应用删除托盘图标
// -------------------------------------------------------------------
case NIM_DELETE: {
BOOL result = pOriginalFunc(dwMessage, lpData);
// 删除成功后,从缓存里也移除对应图标
if (g_blockFlashing && result) {
RemoveCachedIcon(key);
}
return result;
}
// -------------------------------------------------------------------
// NIM_MODIFY:应用修改图标、提示文字或气球通知
// -------------------------------------------------------------------
case NIM_MODIFY: {
// 判断是否需要锁定图标:
// - 开启了屏蔽闪烁
// - 本次调用带有 NIF_ICON 标志
// - cbSize 足够大,能安全访问 hIcon 字段
bool needIconLock = false;
if (g_blockFlashing) {
bool hasIconFlag = (lpData->uFlags & NIF_ICON) != 0;
bool canModifyIcon = lpData->cbSize >= offsetof(TData, hIcon) + sizeof(HICON);
needIconLock = hasIconFlag && canModifyIcon;
}
// 既不需要锁图标,也不需要清提示,直接透传
if (!needIconLock && !g_blockTooltip) {
return pOriginalFunc(dwMessage, lpData);
}
HICON hUse = nullptr;
if (needIconLock) {
// 锁内只做查找和复制,尽快释放锁。
// 不能持锁调用原函数,因为 Shell_NotifyIcon 可能耗时较长,
// 会阻塞其他线程的托盘操作,导致“不够及时”。
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end() && it->second) {
// 已有缓存:复制一份给本次调用使用
hUse = CopyIcon(it->second);
} else if (lpData->hIcon) {
// 没有缓存:把当前图标存为锁定图标
HICON hCached = CopyIcon(lpData->hIcon);
if (hCached) {
g_iconMap[key] = hCached;
// 再复制一份给本次调用,避免锁外缓存被其他线程销毁
hUse = CopyIcon(hCached);
}
}
ReleaseSRWLockExclusive(&g_lock);
}
// 安全复制结构体
TData safeData;
SafeCopyNotifyData(&safeData, lpData);
// 如果需要屏蔽提示,清空悬浮提示和气球通知
if (g_blockTooltip) {
StripTooltip(&safeData);
}
// 如果需要锁定图标,用缓存中的静态图标替换应用传来的图标
if (needIconLock) {
if (hUse) {
safeData.hIcon = hUse;
safeData.uFlags |= NIF_ICON;
} else {
// 复制失败时移除 NIF_ICON,避免把无效句柄传给 Shell
safeData.uFlags &= ~NIF_ICON;
}
}
// 锁外调用原函数,不阻塞其他托盘操作
BOOL result = pOriginalFunc(dwMessage, &safeData);
// 原函数返回后,临时副本即可销毁
if (hUse) {
DestroyIcon(hUse);
}
return result;
}
default:
break;
}
// 其他消息(NIM_SETFOCUS、NIM_SETVERSION 等)直接透传
return pOriginalFunc(dwMessage, lpData);
}
// ---------------------------------------------------------------------------
// Hook 入口:分别对应 Unicode 和 ANSI 两个版本
// ---------------------------------------------------------------------------
static BOOL WINAPI Hook_Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconW);
}
static BOOL WINAPI Hook_Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconA);
}
// ---------------------------------------------------------------------------
// 设置读取与生命周期
// ---------------------------------------------------------------------------
// 从 Windhawk 设置中读取两个开关
static void LoadSettings() {
g_blockFlashing = Wh_GetIntSetting(L"BlockFlashing") != 0;
g_blockTooltip = Wh_GetIntSetting(L"BlockTooltip") != 0;
}
BOOL Wh_ModInit() {
// 先读设置,再装 Hook
LoadSettings();
HMODULE hShell32 = GetModuleHandleW(L"shell32.dll");
if (!hShell32) {
hShell32 = LoadLibraryW(L"shell32.dll");
}
if (hShell32) {
bool hookW = false;
bool hookA = false;
// Hook Unicode 版本
void* pTargetW = (void*)GetProcAddress(hShell32, "Shell_NotifyIconW");
if (pTargetW) {
if (Wh_SetFunctionHook(pTargetW,
(void*)Hook_Shell_NotifyIconW,
(void**)&pOriginal_Shell_NotifyIconW)) {
hookW = true;
}
}
// Hook ANSI 版本(部分老程序仍在使用)
void* pTargetA = (void*)GetProcAddress(hShell32, "Shell_NotifyIconA");
if (pTargetA) {
if (Wh_SetFunctionHook(pTargetA,
(void*)Hook_Shell_NotifyIconA,
(void**)&pOriginal_Shell_NotifyIconA)) {
hookA = true;
}
}
// 至少成功 Hook 一个版本,模组才算初始化成功
return hookW || hookA;
}
return FALSE;
}
// 用户在 Windhawk 设置页修改开关后,会触发这个回调
void Wh_ModSettingsChanged() {
bool prevFlashing = g_blockFlashing;
LoadSettings();
// 如果从“屏蔽闪烁”切到“不屏蔽”,需要清空缓存,
// 让应用下一次 NIM_MODIFY 能正常更新图标。
if (prevFlashing && !g_blockFlashing) {
ClearIconCache();
}
}
// 模组卸载时清理所有缓存的图标句柄
void Wh_ModUninit() {
ClearIconCache();
}
微信弹出这个黑底悬浮卡片有点特殊,查看还是能解决的,还有个设置里选项:鼠标悬浮时的系统托盘提示文本与气泡,你试试吧
我只测试了微信,如果其他的没效,那我也不知道它们采用怎么实现的了,可能要另外加
// ==WindhawkMod==
// @id suppress-tray-flashing-custom
// @name 禁止托盘图标闪烁与消息悬浮窗
// @description 阻止指定应用频繁切换闪烁托盘图标,并彻底屏蔽微信等软件弹出的消息预览悬停浮窗。
// @version 1.1.0
// @author User
// @include none
// @compilerOptions -luser32 -lshell32
// ==/WindhawkMod==
// ==WindhawkModReadme==
/*
# 功能与使用说明
本模组用于消除指定应用在托盘区域的打扰行为:
1. 锁定原始静态托盘图标,禁止换图闪烁。
2. 屏蔽微信等软件自绘的消息预览悬浮卡片(带“暂不处理”等按钮的浮窗)。
3. 可选鼠标悬浮时的系统托盘提示文本与气泡。
### 配置步骤
1. 保存并编译本模组。
2. 在“设置 (Settings)”选项卡中按需开关对应拦截项。
3. 进入“高级 (Advanced)”选项卡。
4. 找到“Custom process inclusion list”(自定义包含进程列表),例如:WeChat.exeWeixin.exe、QQ.exe
5. 点击保存,彻底退出并重启目标软件即可生效。
*/
// ==/WindhawkModReadme==
// ==WindhawkModSettings==
/*
- suppressPreviewToast: true
$name: 屏蔽软件弹出的消息预览悬停浮窗
$description: 拦截微信等软件收到消息或鼠标悬停时弹出的“暂不处理”等独立悬浮卡片窗口。
- suppressSystemTooltips: true
$name: 托盘系统提示文本与气泡
$description: 关闭后剥离托盘注册数据中的提示文本与通知气泡,彻底阻止鼠标悬浮时触发系统气泡。
*/
// ==/WindhawkModSettings==
#include <windows.h>
#include <shellapi.h>
#include <unordered_map>
struct ModSettings {
bool suppressPreviewToast;
bool suppressSystemTooltips;
} g_settings;
struct TrayKey {
HWND hWnd;
UINT uID;
bool operator==(const TrayKey& other) const {
return hWnd == other.hWnd && uID == other.uID;
}
};
struct TrayKeyHash {
size_t operator()(const TrayKey& k) const {
return std::hash<HWND>()(k.hWnd) ^ (std::hash<UINT>()(k.uID) << 1);
}
};
static std::unordered_map<TrayKey, HICON, TrayKeyHash> g_iconMap;
static SRWLOCK g_lock = SRWLOCK_INIT;
using Shell_NotifyIconW_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAW lpData);
static Shell_NotifyIconW_t pOriginal_Shell_NotifyIconW = nullptr;
using Shell_NotifyIconA_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAA lpData);
static Shell_NotifyIconA_t pOriginal_Shell_NotifyIconA = nullptr;
using ShowWindow_t = BOOL(WINAPI *)(HWND hWnd, int nCmdShow);
static ShowWindow_t pOriginal_ShowWindow = nullptr;
using FlashWindowEx_t = BOOL(WINAPI *)(PFLASHWINFO pfwi);
static FlashWindowEx_t pOriginal_FlashWindowEx = nullptr;
static void LoadModSettings() {
g_settings.suppressPreviewToast = Wh_GetIntSetting(L"suppressPreviewToast") != 0;
g_settings.suppressSystemTooltips = Wh_GetIntSetting(L"suppressSystemTooltips") != 0;
}
static inline bool IsWeChatTipWindow(HWND hWnd) {
if (!hWnd || !g_settings.suppressPreviewToast) {
return false;
}
LONG_PTR style = GetWindowLongPtrW(hWnd, GWL_STYLE);
if ((style & WS_CHILD) || (style & WS_CAPTION)) {
return false;
}
LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
if (!(exStyle & (WS_EX_TOOLWINDOW | WS_EX_TOPMOST))) {
return false;
}
RECT rc;
if (!GetWindowRect(hWnd, &rc)) {
return false;
}
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
if (width <= 50 || width >= 480 || height <= 30 || height >= 320) {
return false;
}
wchar_t className[64] = {0};
if (GetClassNameW(hWnd, className, 64) > 0) {
if (wcsstr(className, L"WeChatTipWnd") ||
wcsstr(className, L"ToolTip") ||
wcsstr(className, L"TrayTip") ||
wcsstr(className, L"NotificationWnd")) {
return true;
}
}
return (exStyle & WS_EX_TOOLWINDOW) && (exStyle & WS_EX_TOPMOST);
}
static BOOL WINAPI Hook_ShowWindow(HWND hWnd, int nCmdShow) {
if (nCmdShow != SW_HIDE && IsWeChatTipWindow(hWnd)) {
return FALSE;
}
return pOriginal_ShowWindow(hWnd, nCmdShow);
}
static BOOL WINAPI Hook_FlashWindowEx(PFLASHWINFO pfwi) {
return TRUE;
}
template <typename TData, typename TOrigFunc>
static BOOL Handle_Shell_NotifyIcon(DWORD dwMessage, TData* lpData, TOrigFunc pOriginalFunc) {
if (!lpData) {
return pOriginalFunc(dwMessage, lpData);
}
TrayKey key = { lpData->hWnd, lpData->uID };
switch (dwMessage) {
case NIM_ADD: {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
if ((lpData->uFlags & NIF_ICON) && lpData->hIcon) {
HICON hCloned = CopyIcon(lpData->hIcon);
if (hCloned) {
g_iconMap[key] = hCloned;
}
}
ReleaseSRWLockExclusive(&g_lock);
if (g_settings.suppressSystemTooltips) {
TData safeData = *lpData;
safeData.uFlags &= ~(NIF_TIP | NIF_INFO);
safeData.szTip[0] = 0;
safeData.szInfo[0] = 0;
return pOriginalFunc(dwMessage, &safeData);
}
return pOriginalFunc(dwMessage, lpData);
}
case NIM_DELETE: {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
ReleaseSRWLockExclusive(&g_lock);
return pOriginalFunc(dwMessage, lpData);
}
case NIM_MODIFY: {
TData safeData = *lpData;
if (safeData.uFlags & NIF_ICON) {
AcquireSRWLockShared(&g_lock);
auto it = g_iconMap.find(key);
HICON hLocked = (it != g_iconMap.end()) ? it->second : nullptr;
ReleaseSRWLockShared(&g_lock);
if (!hLocked && safeData.hIcon) {
AcquireSRWLockExclusive(&g_lock);
auto retryIt = g_iconMap.find(key);
if (retryIt == g_iconMap.end() || !retryIt->second) {
hLocked = CopyIcon(safeData.hIcon);
if (hLocked) {
g_iconMap[key] = hLocked;
}
} else {
hLocked = retryIt->second;
}
ReleaseSRWLockExclusive(&g_lock);
}
if (hLocked) {
safeData.hIcon = hLocked;
} else {
safeData.uFlags &= ~NIF_ICON;
}
}
if (g_settings.suppressSystemTooltips) {
safeData.uFlags &= ~(NIF_TIP | NIF_INFO);
safeData.szTip[0] = 0;
safeData.szInfo[0] = 0;
}
return pOriginalFunc(dwMessage, &safeData);
}
default:
break;
}
return pOriginalFunc(dwMessage, lpData);
}
static BOOL WINAPI Hook_Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconW);
}
static BOOL WINAPI Hook_Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconA);
}
void Wh_ModSettingsChanged() {
LoadModSettings();
}
BOOL Wh_ModInit() {
LoadModSettings();
HMODULE hShell32 = GetModuleHandleW(L"shell32.dll");
if (!hShell32) {
hShell32 = LoadLibraryW(L"shell32.dll");
}
if (hShell32) {
void* pTargetW = (void*)GetProcAddress(hShell32, "Shell_NotifyIconW");
if (pTargetW) {
Wh_SetFunctionHook(pTargetW, (void*)Hook_Shell_NotifyIconW, (void**)&pOriginal_Shell_NotifyIconW);
}
void* pTargetA = (void*)GetProcAddress(hShell32, "Shell_NotifyIconA");
if (pTargetA) {
Wh_SetFunctionHook(pTargetA, (void*)Hook_Shell_NotifyIconA, (void**)&pOriginal_Shell_NotifyIconA);
}
}
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (!hUser32) {
hUser32 = LoadLibraryW(L"user32.dll");
}
if (hUser32) {
void* pTargetShow = (void*)GetProcAddress(hUser32, "ShowWindow");
if (pTargetShow) {
Wh_SetFunctionHook(pTargetShow, (void*)Hook_ShowWindow, (void**)&pOriginal_ShowWindow);
}
void* pTargetFlash = (void*)GetProcAddress(hUser32, "FlashWindowEx");
if (pTargetFlash) {
Wh_SetFunctionHook(pTargetFlash, (void*)Hook_FlashWindowEx, (void**)&pOriginal_FlashWindowEx);
}
}
return TRUE;
}
void Wh_ModUninit() {
AcquireSRWLockExclusive(&g_lock);
for (auto& pair : g_iconMap) {
if (pair.second) {
DestroyIcon(pair.second);
}
}
g_iconMap.clear();
ReleaseSRWLockExclusive(&g_lock);
}
感谢大佬,我发现对win32真的是一窍不通,想用ai深入研究改改mod确实有点难 ![]()
平时有时ai用的顺手可能就是我本来对那方面就比较清楚 ![]()
可能要抽空看看win32的机制了
这里稍微优化了下,加入了是否支持闪烁的开关
(刚才我发的有bug,现已修复)
// ==WindhawkMod==
// @id suppress-tray-flashing-custom
// @name 禁止托盘图标闪烁与消息悬浮窗
// @description 阻止指定应用频繁切换闪烁托盘图标,并彻底屏蔽微信等软件弹出的消息预览悬停浮窗。
// @version 1.2.1
// @author User
// @include none
// @compilerOptions -luser32 -lshell32
// ==/WindhawkMod==
// ==WindhawkModReadme==
/*
# 功能与使用说明
本模组用于消除指定应用在托盘区域的打扰行为:
1. 锁定原始静态托盘图标,禁止换图闪烁(默认开启,可在设置中关闭)。
2. 屏蔽微信等软件自绘的消息预览悬浮卡片(带“暂不处理”等按钮的浮窗)。
3. 可选鼠标悬浮时的系统托盘提示文本与气泡。
### 配置步骤
1. 保存并编译本模组。
2. 在“设置 (Settings)”选项卡中按需开关对应拦截项。其中“锁定原始静态托盘图标,禁止换图闪烁”默认开启。
3. 进入“高级 (Advanced)”选项卡。
4. 找到“Custom process inclusion list”(自定义包含进程列表),逐行填入:
WeChat.exe
WeChatAppEx.exe
QQ.exe
5. 点击保存,彻底退出并重启目标软件即可生效。
*/
// ==/WindhawkModReadme==
// ==WindhawkModSettings==
/*
- lockTrayIcon: true
$name: 锁定原始静态托盘图标,禁止换图闪烁
$description: 保存应用首次注册的托盘图标,并在后续修改图标时强制使用该图标,阻止通过切换图标产生闪烁。
- suppressPreviewToast: true
$name: 屏蔽软件弹出的消息预览悬停浮窗
$description: 拦截微信等软件收到消息或鼠标悬停时弹出的“暂不处理”等独立悬浮卡片窗口。
- suppressSystemTooltips: true
$name: 托盘系统提示文本与气泡
$description: 关闭后剥离托盘注册数据中的提示文本与通知气泡,彻底阻止鼠标悬浮时触发系统气泡。
*/
// ==/WindhawkModSettings==
#include <windows.h>
#include <shellapi.h>
#include <unordered_map>
struct ModSettings {
bool lockTrayIcon;
bool suppressPreviewToast;
bool suppressSystemTooltips;
} g_settings;
struct TrayKey {
HWND hWnd;
UINT uID;
bool operator==(const TrayKey& other) const {
return hWnd == other.hWnd && uID == other.uID;
}
};
struct TrayKeyHash {
size_t operator()(const TrayKey& k) const {
return std::hash<HWND>()(k.hWnd) ^ (std::hash<UINT>()(k.uID) << 1);
}
};
static std::unordered_map<TrayKey, HICON, TrayKeyHash> g_iconMap;
static SRWLOCK g_lock = SRWLOCK_INIT;
using Shell_NotifyIconW_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAW lpData);
static Shell_NotifyIconW_t pOriginal_Shell_NotifyIconW = nullptr;
using Shell_NotifyIconA_t = BOOL(WINAPI *)(DWORD dwMessage, PNOTIFYICONDATAA lpData);
static Shell_NotifyIconA_t pOriginal_Shell_NotifyIconA = nullptr;
using ShowWindow_t = BOOL(WINAPI *)(HWND hWnd, int nCmdShow);
static ShowWindow_t pOriginal_ShowWindow = nullptr;
using FlashWindowEx_t = BOOL(WINAPI *)(PFLASHWINFO pfwi);
static FlashWindowEx_t pOriginal_FlashWindowEx = nullptr;
static void LoadModSettings() {
g_settings.lockTrayIcon = Wh_GetIntSetting(L"lockTrayIcon") != 0;
g_settings.suppressPreviewToast = Wh_GetIntSetting(L"suppressPreviewToast") != 0;
g_settings.suppressSystemTooltips = Wh_GetIntSetting(L"suppressSystemTooltips") != 0;
}
static inline bool IsWeChatTipWindow(HWND hWnd) {
if (!hWnd || !g_settings.suppressPreviewToast) {
return false;
}
LONG_PTR style = GetWindowLongPtrW(hWnd, GWL_STYLE);
if ((style & WS_CHILD) || (style & WS_CAPTION)) {
return false;
}
LONG_PTR exStyle = GetWindowLongPtrW(hWnd, GWL_EXSTYLE);
if (!(exStyle & (WS_EX_TOOLWINDOW | WS_EX_TOPMOST))) {
return false;
}
RECT rc;
if (!GetWindowRect(hWnd, &rc)) {
return false;
}
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
if (width <= 50 || width >= 480 || height <= 30 || height >= 320) {
return false;
}
wchar_t className[64] = {0};
if (GetClassNameW(hWnd, className, 64) > 0) {
// 排除系统菜单窗口
if (wcscmp(className, L"#32768") == 0) {
return false;
}
if (wcsstr(className, L"WeChatTipWnd") ||
wcsstr(className, L"ToolTip") ||
wcsstr(className, L"TrayTip") ||
wcsstr(className, L"NotificationWnd")) {
return true;
}
}
// 移除过于宽泛的兜底判断
return false;
}
static BOOL WINAPI Hook_ShowWindow(HWND hWnd, int nCmdShow) {
if (nCmdShow != SW_HIDE && IsWeChatTipWindow(hWnd)) {
return FALSE;
}
return pOriginal_ShowWindow(hWnd, nCmdShow);
}
static BOOL WINAPI Hook_FlashWindowEx(PFLASHWINFO pfwi) {
return TRUE;
}
template <typename TData, typename TOrigFunc>
static BOOL Handle_Shell_NotifyIcon(DWORD dwMessage, TData* lpData, TOrigFunc pOriginalFunc) {
if (!lpData) {
return pOriginalFunc(dwMessage, lpData);
}
TrayKey key = { lpData->hWnd, lpData->uID };
switch (dwMessage) {
case NIM_ADD: {
if (g_settings.lockTrayIcon) {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
if ((lpData->uFlags & NIF_ICON) && lpData->hIcon) {
HICON hCloned = CopyIcon(lpData->hIcon);
if (hCloned) {
g_iconMap[key] = hCloned;
}
}
ReleaseSRWLockExclusive(&g_lock);
}
if (g_settings.suppressSystemTooltips) {
TData safeData = *lpData;
safeData.uFlags &= ~(NIF_TIP | NIF_INFO);
safeData.szTip[0] = 0;
safeData.szInfo[0] = 0;
return pOriginalFunc(dwMessage, &safeData);
}
return pOriginalFunc(dwMessage, lpData);
}
case NIM_DELETE: {
AcquireSRWLockExclusive(&g_lock);
auto it = g_iconMap.find(key);
if (it != g_iconMap.end()) {
if (it->second) {
DestroyIcon(it->second);
}
g_iconMap.erase(it);
}
ReleaseSRWLockExclusive(&g_lock);
return pOriginalFunc(dwMessage, lpData);
}
case NIM_MODIFY: {
TData safeData = *lpData;
if (g_settings.lockTrayIcon) {
if (safeData.uFlags & NIF_ICON) {
AcquireSRWLockShared(&g_lock);
auto it = g_iconMap.find(key);
HICON hLocked = (it != g_iconMap.end()) ? it->second : nullptr;
ReleaseSRWLockShared(&g_lock);
if (!hLocked && safeData.hIcon) {
AcquireSRWLockExclusive(&g_lock);
auto retryIt = g_iconMap.find(key);
if (retryIt == g_iconMap.end() || !retryIt->second) {
hLocked = CopyIcon(safeData.hIcon);
if (hLocked) {
g_iconMap[key] = hLocked;
}
} else {
hLocked = retryIt->second;
}
ReleaseSRWLockExclusive(&g_lock);
}
if (hLocked) {
safeData.hIcon = hLocked;
} else {
safeData.uFlags &= ~NIF_ICON;
}
}
}
if (g_settings.suppressSystemTooltips) {
safeData.uFlags &= ~(NIF_TIP | NIF_INFO);
safeData.szTip[0] = 0;
safeData.szInfo[0] = 0;
}
return pOriginalFunc(dwMessage, &safeData);
}
default:
break;
}
return pOriginalFunc(dwMessage, lpData);
}
static BOOL WINAPI Hook_Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconW);
}
static BOOL WINAPI Hook_Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA lpData) {
return Handle_Shell_NotifyIcon(dwMessage, lpData, pOriginal_Shell_NotifyIconA);
}
void Wh_ModSettingsChanged() {
LoadModSettings();
}
BOOL Wh_ModInit() {
LoadModSettings();
HMODULE hShell32 = GetModuleHandleW(L"shell32.dll");
if (!hShell32) {
hShell32 = LoadLibraryW(L"shell32.dll");
}
if (hShell32) {
void* pTargetW = (void*)GetProcAddress(hShell32, "Shell_NotifyIconW");
if (pTargetW) {
Wh_SetFunctionHook(pTargetW, (void*)Hook_Shell_NotifyIconW, (void**)&pOriginal_Shell_NotifyIconW);
}
void* pTargetA = (void*)GetProcAddress(hShell32, "Shell_NotifyIconA");
if (pTargetA) {
Wh_SetFunctionHook(pTargetA, (void*)Hook_Shell_NotifyIconA, (void**)&pOriginal_Shell_NotifyIconA);
}
}
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (!hUser32) {
hUser32 = LoadLibraryW(L"user32.dll");
}
if (hUser32) {
void* pTargetShow = (void*)GetProcAddress(hUser32, "ShowWindow");
if (pTargetShow) {
Wh_SetFunctionHook(pTargetShow, (void*)Hook_ShowWindow, (void**)&pOriginal_ShowWindow);
}
void* pTargetFlash = (void*)GetProcAddress(hUser32, "FlashWindowEx");
if (pTargetFlash) {
Wh_SetFunctionHook(pTargetFlash, (void*)Hook_FlashWindowEx, (void**)&pOriginal_FlashWindowEx);
}
}
return TRUE;
}
void Wh_ModUninit() {
AcquireSRWLockExclusive(&g_lock);
for (auto& pair : g_iconMap) {
if (pair.second) {
DestroyIcon(pair.second);
}
}
g_iconMap.clear();
ReleaseSRWLockExclusive(&g_lock);
}
把这些加到允许Windows通知,再打开勿扰就解决了,只会弹窗显示消息内容(右下角)