获取 Windows 通知中心弹窗通知内容(含工具汉化)

目录

前言

技术原理概述

测试代码和程序下载连接


本文出处链接:https://blog.csdn.net/qq_59075481/article/details/136440280。

前言

从 Windows 8.1 开始,Windows 通知现在以 Toast 而非 Balloon 形式显示( Bollon 通知其实现在是应用通知的一个子集),并记录在通知中心中。到目前为止,为了检索通知的内容,您必须抓取窗口的句柄并尝试读取它的文本内容,或者其他类似的东西。

特别是在 Toast 可用之后,这样做变得很困难,但从 Windows 10 Anniversary Edition (10.0.14393.0) 版本开始, MS 实现了“通知监听器” API(UserNotificationListener),允许您以与获取 Android 通知相同的方式获取 Windows 通知。

Toast 通知

技术原理概述

参考 gpsnmeajp 的代码思路,

(原文翻译:https://blog.csdn.net/qq_59075481/article/details/136433878)

我们使用 UserNotificationListener 这个 WinRT API 来监视系统通知区域的弹窗。该 API 不仅可以拦截 Toast 通知(应用通知),而且可以拦截旧式的 Balloon 通知。

Balloon 通知

下面是异步获取消息的处理代码(await 异步关键字在 C++ 中没有,需要额外的自己构建处理模式,所以一般代码使用 C#):

首先,我们只关心前三个字段 id、title 和 body。title 是通知的标题,body 是通知的内容。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Forms;
using Windows.UI.Notifications.Management;
using Windows.Foundation.Metadata;
using Windows.UI.Notifications;namespace NotificationListenerThrower
{class NotificationMessage{public uint id { get; set; }public string title { get; set; }public string body { get; set; }public NotificationMessage(uint id, string title, string body) {this.id = id;this.title = title != null ? title : "";this.body = body != null ? body : "";}}
}

获取消息的代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Forms;
using Windows.UI.Notifications.Management;
using Windows.Foundation.Metadata;
using Windows.UI.Notifications;namespace NotificationListenerThrower
{class Notification{bool accessAllowed = false;UserNotificationListener userNotificationListener = null;public async Task<bool> Init(){if (!ApiInformation.IsTypePresent("Windows.UI.Notifications.Management.UserNotificationListener")){accessAllowed = false;userNotificationListener = null;return false;}userNotificationListener = UserNotificationListener.Current;UserNotificationListenerAccessStatus accessStatus = await userNotificationListener.RequestAccessAsync();if (accessStatus != UserNotificationListenerAccessStatus.Allowed) {accessAllowed = false;userNotificationListener = null;return false;}accessAllowed = true;return true;}public async Task<List<NotificationMessage>> Get(){if (!accessAllowed) {return new List<NotificationMessage>();}List<NotificationMessage> list = new List<NotificationMessage>();IReadOnlyList<UserNotification> userNotifications = await userNotificationListener.GetNotificationsAsync(NotificationKinds.Toast);foreach (var n in userNotifications){var notificationBinding = n.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);if (notificationBinding != null){IReadOnlyList<AdaptiveNotificationText> textElements = notificationBinding.GetTextElements();string titleText = textElements.FirstOrDefault()?.Text;string bodyText = string.Join("\n", textElements.Skip(1).Select(t => t.Text));list.Add(new NotificationMessage(n.Id, titleText, bodyText));}}return list;}}}

gpsnmeajp 通过将功能写入 ListBox 和转发到 WebSocket 实现向远程客户端分发通知的信息。

NotificationListenerThrower 作为中间人获取 Windows 通知中心的消息内容,并通过 WebSocket 向客户端转发消息内容( 模式-> 外模式)。

软件组织逻辑

下面是该工具的 WebSocket 前/后端实现。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Forms;
using Windows.UI.Notifications.Management;
using Windows.Foundation.Metadata;
using Windows.UI.Notifications;namespace NotificationListenerThrower
{class Websocket{HttpListener httpListener = null;Task httpListenerTask = null;List<WebSocket> WebSockets = new List<WebSocket>();bool localOnly = false;bool viewer = false;public void Open(string port, bool localOnly, bool viewer) {this.localOnly = localOnly;this.viewer = viewer;string host = localOnly ? "127.0.0.1" : "+";httpListener = new HttpListener();httpListener.Prefixes.Add("http://" + host + ":" + port + "/");httpListener.Start();// 连接异步等待任务httpListenerTask = new Task(async () => {try{while (true){HttpListenerContext context = await httpListener.GetContextAsync();if (localOnly == true && context.Request.IsLocal == false){context.Response.StatusCode = 400;context.Response.Close(Encoding.UTF8.GetBytes("400 Bad request"), true);continue;}if (!context.Request.IsWebSocketRequest){if (viewer){context.Response.StatusCode = 200;context.Response.Close(Encoding.UTF8.GetBytes(html), true);}else {context.Response.StatusCode = 404;context.Response.Close(Encoding.UTF8.GetBytes("404 Not found"), true);}continue;}if (WebSockets.Count > 1024){context.Response.StatusCode = 503;context.Response.Close(Encoding.UTF8.GetBytes("503 Service Unavailable"), true);continue;}HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);WebSocket webSocket = webSocketContext.WebSocket;if (localOnly == true && webSocketContext.IsLocal == false){webSocket.Abort();continue;}WebSockets.Add(webSocket);}}catch (HttpListenerException){//Do noting (Closed)}});httpListenerTask.Start();}public async Task Broadcast(string msg) {ArraySegment<byte> arraySegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg));foreach (var ws in WebSockets){try{if (ws.State == WebSocketState.Open){await ws.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);}else{ws.Abort();}}catch (WebSocketException){//Do noting (Closed)}}WebSockets.RemoveAll(ws => ws.State != WebSocketState.Open);}public void Close() {foreach (var ws in WebSockets){ws.Abort();ws.Dispose();}WebSockets.Clear();try{httpListener?.Stop();}catch (Exception) { //Do noting}httpListenerTask?.Wait();httpListenerTask?.Dispose();}public int GetConnected() {return WebSockets.Count;}string html = @"<html>
<head>
<meta charset='UTF-8'/>
<meta name='viewport' content='width=device-width,initial-scale=1'>
</head>
<body>
<input id='ip' value='ws://127.0.0.1:8000'></input>
<button onclick='connect();'>开始连接</button>
<button onclick='disconnect();'>取消连接</button>
<div id='ping'></div>
<div id='log'></div>
<script>
let socket = null;
let lastupdate = new Date();window.onload = function() {document.getElementById('ip').value = 'ws://'+location.host;connect();
};
function connect(){try{if(socket != null){socket.close();}socket = new WebSocket(document.getElementById('ip').value);socket.addEventListener('error', function (event) {document.getElementById('log').innerText = '连接失败';	});socket.addEventListener('open', function (event) {document.getElementById('log').innerText = '持续连接中...';	});socket.addEventListener('message', function (event) {let packet = JSON.parse(event.data);if('ping' in packet){lastupdate = new Date();document.getElementById('ping').innerText = 'ping: '+lastupdate;}else{document.getElementById('log').innerText = packet.id +':'+packet.title+':'+packet.body +'\n'+ document.getElementById('log').innerText;}});socket.addEventListener('onclose', function (event) {document.getElementById('log').innerText = document.getElementById('log').innerText +'\n' +'CLOSED';socket.close();socket = null;});}catch(e){document.getElementById('log').innerHTML = e;	}
}
function disconnect(){socket.close();socket = null;document.getElementById('log').innerText = '正在连接';	
}
function timeout(){if(new Date().getTime() - lastupdate.getTime() > 3000){if(socket != null){document.getElementById('ping').innerText = 'ping: 超时! 正在重新连接...';disconnect();connect();}else{document.getElementById('ping').innerText = 'ping: 超时!';}}
}
setInterval(timeout,1000);</script>
</body>
</html>";}
}

应用程序及后端代码如下。其中在 WatchTimer_Tick 方法内修复了使用默认参数调用 JsonSerializer 在序列化文本时,编码错误的问题。这使得中文文本可以正常显示在应用程序中。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Forms;
using System.Text.Json;
using System.Text.Json.Serialization;namespace NotificationListenerThrower
{public partial class Form1 : Form{class Setting {public string port { get; set; }public bool localonly { get; set; }public bool viewer{ get; set; }}Websocket websocket = new Websocket();Notification notification = new Notification();uint sent = 0;public Form1(){InitializeComponent();}private async void Form1_Load(object sender, EventArgs e){if (!await notification.Init()){PresentTextBox.Text = "载入中";PresentTextBox.BackColor = Color.Red;return;}PresentTextBox.Text = "就绪";PresentTextBox.BackColor = Color.Green;open(load());}private void Form1_FormClosed(object sender, FormClosedEventArgs e){websocket.Close();}List<NotificationMessage> lastNotificationMessage = new List<NotificationMessage>();private async void WatchTimer_Tick(object sender, EventArgs e){List<NotificationMessage> notificationMessage = await notification.Get();DetectedListBox.Items.Clear();foreach (var n in notificationMessage){// 使用 UnsafeRelaxedJsonEscaping 编码器,// 它会在 JSON 字符串中对非 ASCII 字符进行逃逸处理,// 以确保正确的序列化。var options = new JsonSerializerOptions{Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping};string msg = JsonSerializer.Serialize(n, options);// 支持中文编码DetectedListBox.Items.Add(msg);if (lastNotificationMessage.Where(l => l.id == n.id).Count() == 0) {// 新建await websocket.Broadcast(msg);sent = unchecked(sent + 1);}}lastNotificationMessage = notificationMessage;SendTextBox.Text = sent.ToString();}private async void PingTimer_Tick(object sender, EventArgs e){ConnectedTextBox.Text = websocket.GetConnected().ToString();await websocket.Broadcast("{\"ping\":true}");}private void ApplyButton_Click(object sender, EventArgs e){open(save());}private Setting load(){Setting setting;if (File.Exists("setting.json")){string json = File.ReadAllText("setting.json");try{setting = JsonSerializer.Deserialize<Setting>(json);PortTextBox.Text = setting.port;LocalOnlyCheckBox.Checked = setting.localonly;ViewerCheckBox.Checked = setting.viewer;return setting;}catch (JsonException){//Do noting (json error)}}setting = new Setting{port = PortTextBox.Text,localonly = LocalOnlyCheckBox.Checked,viewer = ViewerCheckBox.Checked};return setting;}private Setting save(){Setting setting = new Setting{port = PortTextBox.Text,localonly = LocalOnlyCheckBox.Checked,viewer = ViewerCheckBox.Checked};string json = JsonSerializer.Serialize(setting);File.WriteAllText("setting.json", json);return setting;}private void open(Setting setting){AccessStatusTextBox.Text = "CLOSED";AccessStatusTextBox.BackColor = Color.Red;websocket.Close();try{websocket.Open(setting.port, setting.localonly, setting.viewer);}catch (HttpListenerException e) {MessageBox.Show(e.Message);return;}AccessStatusTextBox.Text = "OPEN";AccessStatusTextBox.BackColor = Color.Green;}}
}

这款软件的汉化界面如下图:

NotificationListenerThrower 工具界面

网页测试界面: 

网页端接收的消息

测试代码和程序下载连接

可以在 Github 上获取原版(不支持友好的中文输入输出)

https://github.com/gpsnmeajp/NotificationListenerThrower?tab=readme-ov-file

或者使用我修复并汉化后的版本:

1. NotificationListenerThrower_0.01_Repack(源代码):

        链接:https://wwz.lanzouo.com/iOESN1q7r1cf        密码:2ym3

2. NotificationListenerThrower-Net6.0_x64_10.0.19041.0(可执行文件):

        链接:https://wwz.lanzouo.com/iGFG11q7r21a        密码:5bcw


本文出处链接:https://blog.csdn.net/qq_59075481/article/details/136440280。

发布于:2024.03.03,更新于:2024.03.04.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/268879.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中小型水库安全监测运营解决方案,筑牢水库安全防线

我国水库大坝具有“六多”的特点。第一&#xff0c;总量多。我国现有水库9.8万座&#xff0c;是世界上水库大坝最多的国家。第二&#xff0c;小水库多。我国现有水库中95%的水库是小型水库。第三&#xff0c;病险水库多。 目前&#xff0c;在我国水库管理中&#xff0c;部分地方…

异常网络下TCP的可靠服务机制(慢启动、拥塞避免、快重传、快恢复)

目录 TCP超时重传拥塞控制概述慢启动和拥塞避免下面讲解发送端如何判断拥塞发生。 快速重传和快速恢复 本文描述TCP在异常网络下的处理方式 以保证其可靠的数据传输的服务 TCP超时重传 tcp服务能够重传其超时时间内没有收到确认的TCP报文段&#xff0c;tcp模块为每一个报文段都…

认识通讯协议——TCP/IP、UDP协议的区别,HTTP通讯协议的理解

目录 引出认识通讯协议1、TCP/IP协议&#xff0c;UDP协议的区别2、HTTP通讯协议的讲解 Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 认识通讯协议——TCP/IP、UDP协议的区别&#xff0c;HTTP通讯协议的理解 认识通讯协议 …

【脑科学相关合集】有关脑影像数据相关介绍的笔记及有关脑网络的笔记合集

【脑科学相关合集】有关脑影像数据相关介绍的笔记及有关脑网络的笔记合集 前言脑模板方面相关笔记清单 基于脑网络的方法方面数据基本方面 前言 这里&#xff0c;我将展开有关我自己关于脑影像数据相关介绍的笔记及有关脑网络的笔记合集。其中&#xff0c;脑网络的相关论文主要…

分享:大数据信用报告查询的价格一般要多少钱?

现在很多人都开始了解自己的大数据信用了&#xff0c;纷纷去查大数据信用报告&#xff0c;由于大数据信用与人行征信有本质的区别&#xff0c;查询方式和价格都不是固定的&#xff0c;本文就为大家详细讲讲大数据信用报告查询的价格一般要多少钱&#xff0c;希望对你有帮助。 大…

用Java语言创建的Spring Boot项目中,如何传递数组呢??

问题&#xff1a; 用Java语言创建的Spring Boot项目中&#xff0c;如何传递数组呢&#xff1f;&#xff1f; 在这个思路中&#xff0c;其实&#xff0c;Java作为一个后端开发的语言&#xff0c;没必要着重于如何传入&#xff0c;我们主要做的便是对传入的数组数据进行处理即可…

Vue开发实例(十一)用户列表的实现与操作

用户列表的实现与操作 一、创建用户页面和路由二、表格优化1、表头自定义2、表格滚动3、加入数据索引4、利用插槽自定义显示 三、功能1、查询功能3、增加4、删除5、修改 一、创建用户页面和路由 创建用户页面 在 src/components/Main 下创建文件夹user&#xff0c;创建文件Us…

从零开始搭建web组态

成果展示&#xff1a;by组态[web组态插件] 一、技术选择 目前只有两种选择&#xff0c;canvas和svg Canvas: 是一个基于像素的渲染引擎&#xff0c;使用JavaScript API在画布上绘制图像&#xff0c;它的优点包括&#xff1a; Canvas渲染速度快&#xff0c;适合处理大量图像和…

数据结构从入门到精通——链表

链表 前言一、链表1.1 链表的概念及结构1.2 链表的分类1.3 链表的实现1.4 链表面试题1.5 双向链表的实现 二、顺序表和链表的区别三、单项链表实现具体代码text.htext.cmain.c单链表的打印空间的开辟链表的头插、尾插链表的头删、尾删链表中元素的查找链表在指定位置之前、之后…

公网IP怎么获取?

公网IP是网络中设备的唯一标识符&#xff0c;用于在Internet上进行通信和定位。对于普通用户来说&#xff0c;了解如何获取自己的公网IP是很有必要的&#xff0c;本文将介绍几种获取公网IP的方法。 方法一&#xff1a;通过路由器查询 大多数家庭和办公室使用的路由器都会有一个…

php httpfs链接hdfs

一.代码&#xff08;有bug&#xff09; GitHub - michaelbutler/php-WebHDFS: A PHP client for WebHDFS 二.调用代码 1.代码1.代码 require_once(../webhdfs/src/org/apache/hadoop/WebHDFS.php);require_once(../webhdfs/src/org/apache/hadoop/tools/Curl.php); require_o…

Machine Vision Technology:Lecture2 Linear filtering

Machine Vision Technology&#xff1a;Lecture2 Linear filtering Types of ImagesImage denoising图像去噪Defining convolution卷积的定义Key properties卷积的关键属性卷积的其它属性Annoying details卷积练习Sharpening锐化Gaussian KernelNoise噪声 分类Gaussian noise高…

一篇了解电阻的使用

目录 一、电阻理论基础 1.电阻的定义 2.欧姆定律 3.电阻决定式 4.电阻的串并联​编辑 5.电阻的功率 6.温度对电阻的影响 二、电阻的选型 1.安装方式 2.电阻值 &#xff08;1&#xff09;电阻值的标称 &#xff08;2&#xff09;电阻值的确定 &#xff08;3&#x…

如何在Linux使用Docker部署Redis并结合内网穿透实现公网远程连接本地数据库

文章目录 前言1. 安装Docker步骤2. 使用docker拉取redis镜像3. 启动redis容器4. 本地连接测试4.1 安装redis图形化界面工具4.2 使用RDM连接测试 5. 公网远程访问本地redis5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 正文开始前给大家推荐个网站…

游戏引擎分层简介

游戏引擎分层架构&#xff08;自上而下&#xff09; 工具层&#xff08;Tool Layer&#xff09; 在一个现代游戏引擎中&#xff0c;我们最先看到的可能不是复杂的代码&#xff0c;而是各种各样的编辑器&#xff0c;利用这些编辑器&#xff0c;我们可以制作设计关卡、角色、动画…

远程调用--webClient

远程调用webClient 前言1、创建webClient2、准备数据3、执行请求4、接收返回响应到的数据整体代码 前言 非阻塞、响应式HTTP客户端 1、创建webClient WebClient client WebClient.create();2、准备数据 Map<String,String> params new HashMap<>();params.pu…

Tabby-全网最详细讲解

1.Tabby简介 Tabby是一个无限可定制的跨平台终端应用程序&#xff0c;适用于local shells、serial、SSH和Telnet的连接。 Tabby是基于TypeScript开发的终端模拟器&#xff0c;可用于Linux、Windows和Mac OS系统。 Tabby (前身是 Terminus) 是一个可高度配置的终端模拟器和 SSH …

攻防世界-get_post

题目信息 相关知识 -G&#xff1a;表示GET请求&#xff0c;缺省POST -d参数用于发送 POST 请求的数据体 使用-d参数以后&#xff0c;HTTP 请求会自动加上标头Content-Type : application/x-www-form-urlencoded。并且会自动将请求转为 POST 方法&#xff0c;因此可以省略-X PO…

智能驾驶规划控制理论学习04-基于车辆运动学的规划方法

目录 一、线性二自由度汽车模型&#xff08;自行车模型&#xff09; 1、二自由度模型概述 2、不同参考点下的状态空间方程 3、前向仿真 二、运动基元生成方法 1、杜宾斯曲线&#xff08;Dubins Curve&#xff09; 2、Reeds Shepp Curve 三、多项式曲线&#xff08;Poly…

如何本地创建websocket服务端并发布到公网实现远程访问

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…