前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Electron中使用Node-ffi模拟键鼠操作

Electron中使用Node-ffi模拟键鼠操作

作者头像
码客说
发布2020-05-09 14:51:56
4K0
发布2020-05-09 14:51:56
举报
文章被收录于专栏:码客

前言

折腾这个东西就是要实现一个很简单的功能:在我的应用中控制处于后台的PPT进行翻页。

结论:无法实现。

在我测试的过程中无论是wps还是office都无法在后台响应的事件 用Spy++查看无论是窗口句柄还是发送的消息都是完全正确的,都无法响应。处于前台时也依旧没法用PostMessageA或是SendMessageA发送消息,但使用keybd_event是可以的。

也就是说:

  • keybd_event只能在应用在前台时才有效,因为他发送的是全局事件。
  • PostMessageASendMessageA 发送的是应用的事件,但是也可能无论应用在前台或是后台都无效。

工具

Viewdll

查看DLL中的函数 支持WIN10

链接:https://pan.baidu.com/s/19emkiUdbCdaPRKrY9ahyWw 提取码:i32n

Microsoft Spy++

查看应用的按键消息等

链接:https://pan.baidu.com/s/1TV5c4cTOiG7E-OdMtHA_mA 提取码:scew

模拟单个按键

模拟单个按键,如按下键A

在一般情况下可以,即使目标程序在后台运行也可以。 但正如你等下在下面看到的文章所说,在某些程序里第四个参数需要特别注意,否则发送按键将无效。

代码语言:javascript
复制
PostMessageA(hWnd,WM_KEYDOWN,'A',0);

模拟ALT+A

向后台程序发送组合键ALT+按键 是可行的。记住,只可以是ALT,不能是Ctrl或Shift 操作如下:发送ALT+A

代码语言:javascript
复制
PostMessageA(hWnd,WM_SYSKEYDOWN,'A',1<<29);

模拟其他组合按键

我现在的做法只能是激活目标窗口使其成为前台窗口后再模拟发送组合按键,如下:

代码语言:javascript
复制
SetForegroundWindow(g_OperaWnd);
keybd_event(VK_CONTROL,0,0,0);
keybd_event(65,0,0,0);
keybd_event(65,0,KEYEVENTF_KEYUP,0);
keybd_event(VK_CONTROL,0,KEYEVENTF_KEYUP,0);

模拟鼠标的行为

模拟鼠标的行为最好用SendMessageA(不要用PostMessageA),这样可以把消息直接发送到目的窗口的窗口处理过程,成功率会高很多。

代码语言:javascript
复制
SendMessageA(GetWnd(),WM_LBUTTONDOWN,NULL,MAKELPARAM(47,11));

方法介绍

PostMessage

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea

声明PostMessage函数的时候(其实很多很多API函数都是这样),有两种版本:A结尾的是ANSI版本,W结尾的是Unicode版本。一般用A结尾的。

可见PostMessage是我们为了方便起的名字,PostMessageA才是人家的原名。

代码语言:javascript
复制
static extern bool PostMessageW(int hwnd, int msg, uint wParam, uint lParam);
  • 第一参数是窗口句柄
  • 第二个参数是消息windows消息,在C#中需要定义WM_CHAR或者直接填WM_CHAR的值0x0102
  • 第三个参数填键码
  • 第四个参数
    • 0-15位:指定当前消息的重复次数。其值就是用户按下该键后自动重复的次数,但是重复次数不累积
    • 16-23位:指定其扫描码,其值依赖于OEM厂商
    • 24位:指定该按键是否为扩展按键,所谓扩展按键就是Ctrl,Alt之类的,如果是扩展按键,其值为1,否则为0
    • 25-28位:保留字段,暂时不可用
    • 29位:指定按键时的上下文,其值为1时表示在按键时Alt键被按下,其值为0表示WM_SYSKEYDOWN消息因没有任何窗口有键盘焦点而被发送到当前活动窗口。
    • 30位:指定该按键之前的状态,其值为1时表示该消息发送前,该按键是被按下的,其值为0表示该消息发送前该按键是抬起的。
    • 31位:指定其转换状态,对WM_SYSKEYDOWN消息而言,其值总为0。

    请看位29的说明!! 当值为1时表示ALT键被按下!这不正是我需要的吗?于是把29位设置为1,函数调用变成 PostMessage(hWnd,WM_SYSKEYDOWN,0x41,1<<29); 经过测试,发现这个就是Alt+A的效果!!

那么再来看看如何确定键盘消息中的wParam 和lParam 这两个参数。

wParam 参数的含义较简单,它表示你要发送的键盘事件的按键虚拟码,比如你要对目标程序模拟按下A键,那么wParam 参数的值就设为VK_A 。

lParam 这个参数就比较复杂了,因为它包含了多个信息,一般可以把它设为0,但是如果你想要你的模拟更真实一些,那么建议你还是设置一下这个参数。那么我们就详细了解一下lParam 吧。

lParam 是一个long类型的参数,它在内存中占4个字节,写成二进制就是00000000 00000000 00000000 00000000 一共是32位,我们从右向左数,假设最右边那位为第0位(注意是从0而不是从1开始计数),最左边的就是第31位,

那么该参数的的

0-15位表示键的发送次数等扩展信息,

16-23位为按键的扫描码,

24-31位表示是按下键还是释放键。

大家一般习惯写成16进制的,那么就应该是00 00 00 00

  • 0-15位一般为0001
  • 16-23位的扫描码
  • 24-31位如果是按下键为00,释放键则为C0,

那么16-23位的扫描码怎么得呢

代码语言:javascript
复制
MapVirtualKeyA(0x41,0)

我们先看看VB怎样写

代码语言:javascript
复制
Function MakeKeyLparam(ByVal VirtualKey As Long, ByVal flag As Long) As Long
'参数VirtualKey表示按键虚拟码,flag表示是按下键还是释放键,用WM_KEYDOWN和WM_KEYUP这两个常数表示
Dim s As String
Dim Firstbyte As String 'lparam参数的24-31位
If flag = WM_KEYDOWN Then '如果是按下键
Firstbyte = "00"
Else
Firstbyte = "C0" '如果是释放键
End If
Dim Scancode As Long
'获得键的扫描码
Scancode = MapVirtualKey(VirtualKey, 0)
Dim Secondbyte As String 'lparam参数的16-23位,即虚拟键扫描码
Secondbyte = Right("00" & Hex(Scancode), 2)
s = Firstbyte & Secondbyte & "0001" '0001为lparam参数的0-15位,即发送次数和其它扩展信息
MakeKeyLparam = Val("&H" & s)
End Function

JS写法

16进制转换

代码语言:javascript
复制
//10进制字符串
console.info(0x41.toString());
//16进制字符串
console.info(0x41.toString(16));
//16进制字符串转数字
console.info(parseInt("0x41"));

上面的方法JS的写法

代码语言:javascript
复制
// flag 0按下 1释放
function makeKeyLparam(virtualKey, flag) {
    let firstbyte = "";
    let scancode = user32.MapVirtualKeyA(virtualKey, 0).toString(16);
    if (flag === 0) {
        firstbyte = "00"
    } else {
        firstbyte = "C0"
    }
    let keyLparamStr = "0x" + firstbyte + scancode + "0001";
    console.info("keyLparamStr", keyLparamStr);
    let keyLparam = parseInt(keyLparamStr);
    console.info("keyLparam", keyLparam);
    return keyLparam
}

PPT放映

代码语言:javascript
复制
const ffi = require('ffi');
const ref = require('ref');
const iconv = require('iconv-lite');

const voidPtr = ref.refType(ref.types.void);
const stringPtr = ref.refType(ref.types.CString);
const user32 = ffi.Library('user32.dll', {
    GetSystemMenu: ['int', ['int', 'bool']],
    EnumWindows: ['bool', [voidPtr, 'int32']],
    GetWindowTextA: ['long', ['long', stringPtr, 'long']],
    SetForegroundWindow: ['bool', ['long']],
    MapVirtualKeyA: ['long', ['long', 'long']],
    PostMessageW: ['bool', ['long', 'long', 'long', ref.types.ulong]],
    SendMessageA: ['bool', ['long', 'long', 'long', ref.types.ulong]],
    keybd_event:['bool', ['int', 'int', 'int', 'int']]
});

// flag 0按下 1释放
function makeKeyLparam(virtualKey, flag) {
    let firstbyte = "";
    let scancode = user32.MapVirtualKeyA(virtualKey, 0).toString(16);
    if (flag === 0) {
        firstbyte = "00"
    } else {
        firstbyte = "C0"
    }
    let keyLparamStr = "0x" + firstbyte + scancode + "0001";
    console.info("keyLparamStr:", keyLparamStr);
    let keyLparam = parseInt(keyLparamStr);
    return keyLparam
}

windowProc = ffi.Callback('bool', ['long', 'int32'], function (hwnd, lParam) {
    let buf, name, ret;
    buf = new Buffer(255);
    ret = user32.GetWindowTextA(hwnd, buf, 255);
    name = ref.readCString(buf, 0);
    let text = iconv.decode(buf, 'gbk');
    if (text.indexOf("ppt") !== -1) {
        console.log(text);
        console.info(hwnd.toString(16));
        console.log("");
        user32.SetForegroundWindow(hwnd);
        setTimeout(function () {
           user32.keybd_event(0x74,0,0,0);
        }, 500);
    }
    return false;
});

下面的代码并不起作用 但点击F5的消息确实是发送了

代码语言:javascript
复制
let lp1 = makeKeyLparam(0x74, 0);
let lp2 = makeKeyLparam(0x74, 1);
user32.PostMessageW(hwnd, 0x0100, 0x74, lp1);
setTimeout(function () {
	user32.PostMessageW(hwnd, 0x0101, 0x74, lp2);
}, 500);

Shift+F5

代码语言:javascript
复制
user32.keybd_event(0x10,0,0,0);
user32.keybd_event(0x74,0,0,0);
user32.keybd_event(0x74,0,2,0);
user32.keybd_event(0x10,0,2,0);

按键表

https://docs.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes

https://docs.microsoft.com/zh-cn/windows/win32/inputdev/wm-syskeydown

按键方式码

常用名称

十六进制值

十进制值

作用

WM_KEYDOWN

0x0100

256

表示一个普通键被按下

WM_KEYUP

0x0101

257

表示一个普通键被释放

WM_SYSKEYDOWN

0x0104

260

表示一个系统键被按下,比如Alt键

WM_SYSKEYUP

0x0105

261

表示一个系统键被释放,比如Alt键

WM_KEYDOWNWM_KEYUP之间的区别就很容易区别了,一个是键的按下,一个是键的释放。

WM_SYSKEYDOWNWM_KEYDOWN的区别在于WM_SYSKEYDOWNWM_SYSKEYUP消息经常由与Alt相组合的按键产生,这些按键启动程序菜单或者系统菜单上的选项,或者用于切换活动窗口等系统功能(Alt-Tab或者Alt-Esc),也可以用作系统菜单加速键(Alt键与一个功能键相结合,例如Alt-F4用于关闭应用程序)。程序通常忽略WM_SYSKEYUPWM_SYSKEYDOWN消息,并将它们传送到DefWindowProc。由于Windows要处理所有Alt键的功能,所以您无需拦截这些消息。您的窗口消息处理程序将最后收到关于这些按键结果(如菜单选择)的其它消息。如果您想在自己的窗口消息处理程序中加上拦截系统按键的程序码,那么在处理这些消息之后再传送到DefWindowProc,Windows就仍然可以将它们用于通常的目的。当然我们完全可以在响应WM_KEYDOWNWM_KEYUP消息的lParam参数时,判断第29位来判断Alt键是否按下,如果在按键的时候同时按下ALT键,那么该位为1, 否则为0;或者通过GetKeyState(VK_MENU)来判断ALT也是可以的。

我们开发时主要用WM_KEYDOWNWM_KEYUP

按键码

常用名称

十六进制值

十进制值

对应按键

VK_LBUTTON

0x01

1

鼠标的左键

VK_RBUTTON

0x02

2

鼠标的右键

VK-CANCEL

0x03

3

Contol-break执行

VK_MBUTTON

0x04

4

鼠标的中键(三按键鼠标)

VK_BACK

0x08

8

Backspace键

VK_TAB

0x09

9

Tab键

VK_CLEAR

0x0C

12

Clear键

VK_RETURN

0x0D

13

Enter键

VK_SHIFT

0x10

16

Shift键

VK_CONTROL

0x11

17

Ctrl键

VK_MENU

0x12

18

Alt键

VK_PAUSE

0x13

19

Pause键

VK_CAPITAL

0x14

20

Caps Lock键

VK_ESCAPE

0x1B

27

Ese键

VK_SPACE

0x20

32

Spacebar键

VK_PRIOR

0x21

33

Page Up键

VK_NEXT

0x22

34

Page Domw键

VK_END

0x23

35

End键

VK_HOME

0x24

36

Home键

VK_LEFT

0x25

37

LEFT ARROW键(←)

VK_UP

0x26

38

UP ARROW键(↑)

VK_RIGHT

0x27

39

RIGHT ARROW键(→)

VK_DOWN

0x28

40

DOWN ARROW键(↓)

VK_SELECT

0x29

41

SELECT键

VK_EXECUTE

0x2B

43

EXECUTE键

VK_SNAPSHOT

0x2C

44

Print Screen键

VK_INSERT

0x2D

45

Ins键

VK_DELETE

0x2E

46

Del键

VK_HELP

0x2F

47

Help键

VK_0

0x30

48

0键

VK_1

0x31

49

1键

VK_2

0x32

50

2键

VK_3

0x33

51

3键

VK_4

0x34

52

4键

VK_5

0x35

53

5键

VK_6

0x36

54

6键

VK_7

0x37

55

7键

VK_8

0x38

56

8键

VK_9

0x39

57

9键

VK_A

0x41

65

A键

VK_B

0x42

66

B键

VK_C

0x43

67

C键

VK_D

0x44

68

D键

VK_E

0x45

69

E键

VK_F

0x46

70

F键

VK_G

0x47

71

G键

VK_H

0x48

72

H键

VK_I

0x49

73

I键

VK_J

0x4A

74

J键

VK_K

0x4B

75

K键

VK_L

0x4C

76

L键

VK_M

0x4D

77

M键

VK_N

0x4E

78

N键

VK_O

0x4F

79

O键

VK_P

0x50

80

P键

VK_Q

0x51

81

Q键

VK_R

0x52

82

R键

VK_S

0x53

83

S键

VK_T

0x54

84

T键

VK_U

0x55

85

U键

VK_V

0x56

86

V键

VK_W

0x57

87

W键

VK_X

0x58

88

X键

VK_Y

0x59

89

Y键

VK_Z

0x5A

90

Z键

VK_NUMPAD0

0x60

96

数字键0键

VK_NUMPAD1

0x61

97

数字键1键

VK_NUMPAD2

0x62

98

数字键2键

VK_NUMPAD3

0x63

99

数字键3键

VK_NUMPAD4

0x64

100

数字键4键

VK_NUMPAD5

0x65

101

数字键5键

VK_NUMPAD6

0x66

102

数字键6键

VK_NUMPAD7

0x67

103

数字键7键

VK_NUMPAD8

0x68

104

数字键8键

VK_NUMPAD9

0x69

105

数字键9键

VK_MULTIPLY

0x6A

106

*键

VK_ADD

0x6B

107

=+键

VK_SEPARATOR

0x6C

108

Separator键

VK_SUBTRACT

0x6D

109

=-键

VK_DECIMAL

0x6E

110

.键

VK_DIVIDE

0x6F

111

VK_F1

0x70

112

F1键

VK_F2

0x71

113

F2键

VK_F3

0x72

114

F3键

VK_F4

0x73

115

F4键

VK_F5

0x74

116

F5键

VK_F6

0x75

117

F6键

VK_F7

0x76

118

F7键

VK_F8

0x77

119

F8键

VK_F9

0x78

120

F9键

VK_F10

0x79

121

F10键

VK_F11

0x7A

122

F11键

VK_F12

0x7B

123

F12键

VK_NUMLOCK

0x90

144

Num Lock键

VK_SCROLL

0x91

145

Scroll Lock键

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 工具
    • Viewdll
      • Microsoft Spy++
      • 模拟单个按键
      • 模拟ALT+A
      • 模拟其他组合按键
      • 模拟鼠标的行为
      • 方法介绍
      • PPT放映
      • 按键表
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档