獲取 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.03.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717954.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717954.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717954.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在ubuntu上安裝hadoop完分布式

準備工作 Xshell安裝包 Xftp7安裝包 虛擬機安裝包 Ubuntu鏡像源文件 Hadoop包 Java包 一、安裝虛擬機 創建ubuntu系統 完成之后會彈出一個新的窗口 跑完之后會重啟一下 按住首先用ctrlaltf3進入命令界面&#xff0c;輸入root&#xff0c;密碼登錄管理員賬號 按Esc 然后輸入 …

數據結構常用的字符串函數(中英雙釋)

頭文件&#xff1a;string.h 1.strchr const char * strchr ( const char * str, int character ); Locate first occurrence of character in string str C string. character Character to be located. Return Value A pointer to the first occurrence of character in s…

適用于恢復iOS數據的 10 款免費 iPhone 恢復軟件

現在&#xff0c;您可以獲得的 iPhone 的存儲容量比大多數人的筆記本電腦和臺式電腦的存儲容量還要大。雖然能夠存儲數千張高分辨率照片和視頻文件、安裝數百個應用程序并隨身攜帶大量音樂庫以供離線收聽固然很棒&#xff0c;但在一個地方擁有如此多的數據可能會帶來毀滅性的后…

2.2_5 調度算法

文章目錄 2.2_5 調度算法一、適用于早期的批處理系統&#xff08;一&#xff09;先來先服務&#xff08;FCFS&#xff0c;First Come First Serve&#xff09;&#xff08;二&#xff09;短作業優先&#xff08;SJF&#xff0c;Shortest Job First&#xff09;&#xff08;三&a…

SpringMVC總結

SpringMVC SpringMVC是隸屬于Spring框架的一部分&#xff0c;主要是用來進行Web開發&#xff0c;是對Servlet進行了封裝。 對于SpringMVC我們主要學習如下內容: SpringMVC簡介 請求與響應 REST風格 SSM整合(注解版) 攔截器 SpringMVC是處理Web層/表現層的框架&#xff…

易語言源代碼5000例

僅供學習研究交流使用 加群下載

探索MyBatis-Plus的高階用法

引言 MyBatis-Plus 是 MyBatis 的增強工具包&#xff0c;提供了許多方便快捷的功能來簡化開發&#xff0c;提高效率。除了基本的 CRUD 操作外&#xff0c;MyBatis-Plus 還提供了一些高級功能&#xff0c;本文將探討 MyBatis-Plus 的高階用法&#xff0c;幫助開發者更好地利用該…

Linux服務器搭建超簡易跳板機連接阿里云服務器

簡介 想要規范內部連接阿里云云服務器的方式&#xff0c;但是最近懶病犯了&#xff0c;先搞一個簡易式的跳板機過渡一下&#xff0c;順便在出一個教程&#xff0c;其他以后再說&#xff01; 配置方法 創建密鑰 登錄阿里云&#xff0c;找到云服務器ECS控制臺&#xff0c;點擊…

【小白友好】LeetCode 打家劫舍 III

https://leetcode.cn/problems/house-robber-iii/description/ 前言 建議還是先看看動態規劃的基礎題再看這個。動態規劃是不刷題&#xff0c;自己100%想不出來的。 基礎題&#xff1a; 23 小白想法 現在我們想遍歷的數據結構不是數組了&#xff0c;而是一顆樹。在樹上的d…

C++遞推

統計每個月兔子的總數 #include<bits/stdc.h> using namespace std; int n,sum0; void f(int); int main() {int a[1000];cin>>n;a[1]1;a[2]2;for(int i3;i<1000;i){a[i]a[i-1]a[i-2];}cout<<a[n];return 0; } void f(int n){}猴子吃桃子 #include<b…

2024年華為OD機試真題-電腦病毒感染-Python-OD統一考試(C卷)

題目描述: 一個局域網內有很多臺電腦,分別標注為0 - N-1的數字。相連接的電腦距離不一樣,所以感染時間不一樣,感染時間用t表示。 其中網絡內一個電腦被病毒感染,其感染網絡內所有的電腦需要最少需要多長時間。如果最后有電腦不會感染,則返回-1 給定一個數組times表示一個…

在Spring Boot中如何實現異常處理?

在Spring Boot中&#xff0c;異常處理可以通過幾種方式實現&#xff0c;以提高應用程序的健壯性和用戶體驗。這些方法包括使用ControllerAdvice注解、ExceptionHandler注解、實現ErrorController接口等。下面是一些實現Spring Boot異常處理的常用方法&#xff1a; 1. 使用Cont…

Git實戰(2)

git work flow ------------------------------------------------------- ---------------------------------------------------------------- 場景問題及處理 問題1&#xff1a;最近提交了 a,b,c,d記錄&#xff0c;想把b記錄刪掉其他提交記錄保留&#xff1a; git reset …

【C++ 編程指南】

C 編程指南 ■ C環境安裝■ C 基本語法■ 預定義宏■ # 和 ## 運算符■ C 引用■ C 命名空間■ 定義命名空間■ using 指令■ 嵌套的命名空間 ■ String類■ 類■ 類的static靜態成員 ■ C 繼承■ 繼承類型 public、protected 或 private■ 訪問控制和繼承■ 多繼承■ 數據抽象…

機器學習-面經

經歷了2023年的秋招&#xff0c;現在也已經入職半年了&#xff0c;空閑時間將面試中可能遇到的機器學習問題整理了一下&#xff0c;可能答案也會有錯誤的&#xff0c;希望大家能指出&#xff01;另外&#xff0c;不論是實習&#xff0c;還是校招&#xff0c;都祝福大家能夠拿到…

990-28產品經理:Different types of IT risk 不同類型的IT風險

Your IT systems and the information that you hold on them face a wide range of risks. If your business relies on technology for key operations and activities, you need to be aware of the range and nature of those threats. 您的IT系統和您在其中持有的信息面臨…

數據結構c版(2)——二叉樹

本章我們來了解一下二叉樹這一概念。 目錄 1.樹概念及結構 1.1樹的概念??????? 1.2 樹的特點&#xff1a; 1.3 樹的相關概念 1.4 樹的表示??????? 1.5 樹在實際中的運用&#xff08;表示文件系統的目錄樹結構&#xff09; 2.二叉樹概念及結構 2.1概念 …

Qt 簡約美觀的動畫 擺鐘風格 第十季

&#x1f60a; 今天給大家分享一個擺鐘風格的加載動畫 &#x1f60a; 效果如下: 最近工作忙起來了 , 后續再分享其他有趣的加載動畫吧. 一共三個文件 , 可以直接編譯運行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <Q…

【C++】用文件流的put和get成員函數讀寫文件

題目 編寫一個mycopy程序&#xff0c;實現文件復制的功能。用法是在控制臺輸入&#xff1a; mycooy 源文件名 目標文件名 參數介紹 m a i n main main 函數的參數有兩個&#xff0c;一個int類型參數和一個指針數組。 a r g c argc argc 表示參數的個數。參數為void時 a r g …

機器人 標準DH與改進DH

文章目錄 1 建立機器人坐標系1.1 連桿編號1.2 關節編號1.3 坐標系方向2 標準DH(STD)2.1 確定X軸方向2.2 建模步驟2.3 變換順序2.4 變換矩陣3 改進DH(MDH)3.1 確定X軸方向3.2 建模步驟3.3 變換順序3.4 變換矩陣4 標準DH與改進DH區別5 Matlab示例參考鏈接1 建立機器人坐標系 1.1…