DreamScene2 1.3 版本已经发布了,现在支持鼠标和桌面交互功能。这个功能不会影响性能,基本不占用 CPU。这个功能让我对 Windows 消息机制有了更深入的理解,在这篇博客中我会详细介绍实现方式。

欢迎 Star 和 Fork https://github.com/he55/DreamScene2

实现原理

使用 WIN32 API SetWindowsHookEx 函数 Hook 鼠标键盘消息,在钩子处理函数中处理捕获鼠标键盘消息然后调用 PostMessage 函数向动态桌面窗口发送转发消息。

设置鼠标和键盘钩子

函数的第一个参数是钩子类型,Hook 鼠标消息可以传 WH_MOUSE_LL,Hook 键盘消息可以传 WH_KEYBOARD_LL。第二个参数是自定义的钩子消息处理函数地址。函数的第三个参数是钩子函数所在的模块句柄,当钩子类型是 WH_MOUSE_LL 或者 WH_KEYBOARD_LL 时,可以直接传当前模块句柄。函数的第四个参数是线程 Id,传 NULL 捕获所有消息。

设置 Hook 代码。保存 SetWindowsHookEx 函数返回值,卸载 Hook 时需要

HHOOK g_hLowLevelMouseHook = NULL;
HHOOK g_hLowLevelKeyboardHook = NULL; HMODULE hModule = GetModuleHandle(NULL);
g_hLowLevelMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, hModule, NULL);
g_hLowLevelKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hModule, NULL);

卸载 Hook 代码

UnhookWindowsHookEx(g_hLowLevelMouseHook);
UnhookWindowsHookEx(g_hLowLevelKeyboardHook);

编写钩子处理函数

WH_MOUSE_LL 和 WH_KEYBOARD_LL 的钩子处理函数签名相同,wParam 参数是消息类型,lParam 参数是一个指针和钩子函数的类型有关。当钩子类型为 WH_MOUSE_LL 时 lParam 参数是 MSLLHOOKSTRUCT 结构体指针。当钩子类型为 WH_KEYBOARD_LL 时 lParam 参数是 KBDLLHOOKSTRUCT 结构体指针。

钩子处理函数签名

LRESULT CALLBACK xxxProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);

鼠标钩子处理函数

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
return CallNextHookEx(NULL, nCode, wParam, lParam);
}

处理 WM_LBUTTONDOWN 鼠标按下消息

鼠标钩子处理函数的 wParam 参数就是鼠标消息类型,lParam 参数需要转换成 MSLLHOOKSTRUCT 结构体指针,MSLLHOOKSTRUCT 结构体的 pt 字段鼠标相对于屏幕的坐标。想转发鼠标按下消息,需要看 WM_LBUTTONDOWN 消息的定义:WM_LBUTTONDOWN 消息的 wParam 参数为按键的状态,lParam 参数的低字节为光标的 x 坐标、高字节为光标的 y 坐标。需要注意鼠标钩子处理函数和 PostMessage 函数的 wParam 参数、lParam 参数含义不同,需要转换成 PostMessage 函数需要的参数。

WM_LBUTTONDOWN 处理方法

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
LONG lp = MAKELONG(p->pt.x, p->pt.y); // 低字节 x 坐标、高字节 y 坐标 if (wParam == WM_LBUTTONDOWN) {
PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp); // 向动态桌面窗口发送鼠标按下消息
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}

WM_LBUTTONUP 和 WM_MOUSEMOVE 处理方法一样

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
LONG lp = MAKELONG(p->pt.x, p->pt.y); if (wParam == WM_MOUSEMOVE) {
PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
}
else if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONUP) {
PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp);
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}

优化鼠标消息转发

上面的代码会转发所有的鼠标消息,实际上并不想转发所有的鼠标消息。对鼠标按下和松开的消息,只转发焦点在桌面上的鼠标消息。

判断前台窗口是不是桌面

BOOL DS2_IsDesktop(void) {
HWND hProgman = FindWindow("Progman", "Program Manager");
HWND hWorkerW = NULL; HWND hShellViewWin = FindWindowEx(hProgman, NULL, "SHELLDLL_DefView", NULL);
if (!hShellViewWin)
{
HWND hDesktopWnd = GetDesktopWindow();
do
{
hWorkerW = FindWindowEx(hDesktopWnd, hWorkerW, "WorkerW", NULL);
hShellViewWin = FindWindowEx(hWorkerW, NULL, "SHELLDLL_DefView", NULL);
} while (!hShellViewWin && hWorkerW);
} HWND hForegroundWindow = GetForegroundWindow();
return hForegroundWindow == hWorkerW || hForegroundWindow == hProgman;
}

对鼠标移动的消息,转发鼠标在桌面上的鼠标移动消息。

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
LONG lp = MAKELONG(p->pt.x, p->pt.y); if (wParam == WM_MOUSEMOVE) {
RECT rect;
GetWindowRect(GetForegroundWindow(), &rect); if (!PtInRect(&rect, p->pt)) {
PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}

完整的鼠标钩子处理函数代码

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
LONG lp = MAKELONG(p->pt.x, p->pt.y); if (DS2_IsDesktop()) {
if (wParam == WM_MOUSEMOVE) {
PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
}
else if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONUP) {
PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp);
}
else if (wParam == WM_MOUSEWHEEL) {
// TODO:
}
}
else if (wParam == WM_MOUSEMOVE) {
RECT rect;
GetWindowRect(GetForegroundWindow(), &rect); if (!PtInRect(&rect, p->pt)) {
PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
}
} return CallNextHookEx(NULL, nCode, wParam, lParam);
}

键盘钩子处理函数

键盘钩子处理函数的 wParam 参数就是键盘消息类型,lParam 参数需要转换成 KBDLLHOOKSTRUCT 结构体指针。KBDLLHOOKSTRUCT 结构体中用到的有 scanCode 字段和 vkCode 字段。键盘消息 WM_KEYDOWNWM_KEYUP 消息的 wParam 参数为 vkCode,lParam 参数的含义比较复杂。

WM_KEYDOWN 消息的 lParam 参数 bit 位说明

Bits 说明
0-15 当前消息的重复计数。
16-23 扫描代码
24 指示该键是扩展键。如果它是扩展键则值为 1,否则为 0。
25-28 保留,不使用。
29 上下文代码。对于 WM_KEYDOWN 消息该值始终为 0。
30 之前的键状态。如果在发送消息之前键关闭则值为 1,如果键已启动则值为 0。
31 转换状态。对于 WM_KEYDOWN 消息该值始终为 0。

WM_KEYUP 消息的 lParam 参数 bit 位说明

Bits 说明
0-15 当前消息的重复计数。对于 WM_KEYUP 消息,重复计数始终为1。
16-23 扫描代码
24 指示该键是扩展键。如果它是扩展键则值为 1,否则为 0。
25-28 保留,不使用。
29 上下文代码。对于 WM_KEYUP 消息该值始终为 0。
30 之前的键状态。对于 WM_KEYUP 消息该值始终为 1。
31 转换状态。对于 WM_KEYUP 消息该值始终为 1。

完整的键盘钩子处理函数代码

LRESULT CALLBACK LowLevelKeyboardProc(int    nCode, WPARAM wParam, LPARAM lParam) {
if (DS2_IsDesktop()) {
KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam; if (wParam == WM_KEYDOWN) {
int lp = 1 | (p->scanCode << 16) | (1 << 24) | (0 << 29) | (0 << 30) | (0 << 31);
PostMessage(g_hWnd, (UINT)wParam, p->vkCode, lp);
}
else if (wParam == WM_KEYUP) {
int lp = 1 | (p->scanCode << 16) | (1 << 24) | (0 << 29) | (1 << 30) | (1 << 31);
PostMessage(g_hWnd, (UINT)wParam, p->vkCode, lp);
}
} return CallNextHookEx(NULL, nCode, wParam, lParam);
}

所有代码

https://github.com/he55/DreamScene2



看板娘使用方法 https://www.cnblogs.com/he55/p/15705047.html

写在最后

下一步会增加 ffmpeg 视频播放引擎

C# 编写 Windows 动态桌面软件实现(一)之桌面交互功能的更多相关文章

  1. C# 编写一个小巧快速的 Windows 动态桌面软件

    开源自己前段时间使用 C# 编写的 Windows 动态桌面软件,在接下来的博客我将描写一些技术细节和遇到的一些坑.这个软件可以把视频设置成桌面背景播放,不仅如此而且还可以把网页或一个网页文件设置成桌 ...

  2. 去掉Windows桌面软件的快捷图标的箭头

    去掉Windows桌面软件的快捷图标的箭头 怎么去除桌面快捷方式图标箭头 cmd /k reg delete "HKEY_CLASSES_ROOT\lnkfile" /v IsSh ...

  3. nw.js桌面软件开发系列 第0.1节 HTML5和桌面软件开发的碰撞

    第0.1节 HTML5和桌面软件开发的碰撞 当我们谈论桌面软件开发技术的时候,你会想到什么?如果不对技术本身进行更为深入的探讨,在我的世界里,有这么多技术概念可以被罗列出来(请原谅我本质上是一个Win ...

  4. 【转】分析Linux和windows动态库

    原文地址:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html 摘要:动态链接库技术实现和设计程序常用的技术,在Windows和Lin ...

  5. Linux和windows动态库

    转载:http://www.cnblogs.com/chio/archive/2008/11/13/1333119.html 态链接库技术实现和设计程序常用的技术,在Windows和Linux系 统中 ...

  6. windows动态库与Linux动态库

    Linux动态库和windows动态库的目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同.但是尽管有差异Linux动态库的windows动态库还是可以移植的,有一些规则以及经验是必须 ...

  7. Windows个人常用软件推荐

    一.必装软件 浏览器:Google chrome Google Chrome是一款可让您更快速.轻松且安全地使用网络的浏览器,它的设计超级简洁,使用起来更加方便,支持多标签浏览,同时也支持扩展插件.下 ...

  8. Windows动态库学习心得

    最近在工作中需要给项目组其他成员提供调用函数,决心抛弃以前“拷贝头文件/源文件”的简陋方法,采用动态库的方式对自己开发的接口进行模块化管理.因之前一直没有机会从事Windows动态库的开发,现借助这个 ...

  9. Atitit 桌面软件跨平台gui解决方案 javafx webview

    Atitit 桌面软件跨平台gui解决方案 javafx webview 1.1. 双向js交互1 1.2. 新弹出窗口解决1 1.3. 3.文档对象入口dom解析1 1.4. 所以果断JavaFX, ...

随机推荐

  1. 学习java的第二十一天

    一.今日收获 1.java完全学习手册第三章算法的3.2排序,比较了跟c语言排序上的不同 2.观看哔哩哔哩上的教学视频 二.今日问题 1.快速排序法的运行调试多次 2.哔哩哔哩教学视频的一些术语不太理 ...

  2. 1小时学会Git玩转GitHub

    版权声明:原创不易,本文禁止抄袭.转载,侵权必究! 本次教程建议一边阅读一边用电脑实操 目录 一.了解Git和Github 1.1 什么是Git 1.2 什么是版本控制系统 1.3 什么是Github ...

  3. flink---实时项目----day03---1.练习讲解(全局参数,数据以parquet格式写入hdfs中) 2 异步查询 3 BroadcastState

    1 练习讲解(此处自己没跑通,以后debug) 题目见flink---实时项目---day02 kafka中的数据,见day02的文档 GeoUtils package cn._51doit.flin ...

  4. 商业爬虫学习笔记day8-------json的使用

    一. 简介 JSON,全称为JavaScript Object Notation(JavaScript对象标记),它通过对象和数组的组合来表示数据,是一种轻量级的数据交换格式.它基于 ECMAScri ...

  5. Linux学习 - Bash变量

    一.用户自定义变量(本地名) 用户自定义变量只有在当前的shell中生效 1 定义变量 name="zheng huiwei" aa=123 2 变量叠加 aa="$aa ...

  6. ORACLE dba_objects

    dba_objects OWNER 对象所有者 OBJECT_NAME 对象名称 SUBOBJECT_NAME 子对象名称 OBJECT_ID 对象id DATA_OBJECT_ID 包含该对象的se ...

  7. HelloWorldDynamic

    package mbeanTest; import java.lang.reflect.Method; import javax.management.Attribute; import javax. ...

  8. BigDecimal中要注意的一些事

    一.关于public BigDecimal(double val) BigDecimal中三个主要的构造函数 1 public BigDecimal(double val) 将double表示形式转换 ...

  9. 3.Vue.js-目录结构

    Vue.js 目录结构 上一章节中我们使用了 npm 安装项目,我们在 IDE(Eclipse.Atom等) 中打开该目录,结构如下所示: 目录解析 目录/文件 说明 build 项目构建(webpa ...

  10. 【C/C++】01背包问题/动态规划

    按小蓝书上写的大数据情况下没过,按解答区一个大佬的修改了过了 #include <bits/stdc++.h> using namespace std; class Solution { ...